feat: 添加公告功能
This commit is contained in:
@@ -264,6 +264,18 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
text-align: center;
|
||||
padding: 30px 15px;
|
||||
@@ -732,6 +744,7 @@
|
||||
<button class="tab" onclick="switchTab('feedbacks')">反馈管理 <span id="feedbackBadge" style="background:#e74c3c; color:white; padding:2px 6px; border-radius:10px; font-size:11px; margin-left:3px; display:none;">0</span></button>
|
||||
<button class="tab" onclick="switchTab('stats')">统计</button>
|
||||
<button class="tab" onclick="switchTab('logs')">任务日志</button>
|
||||
<button class="tab" onclick="switchTab('announcements')">公告管理</button>
|
||||
<button class="tab" onclick="switchTab('email')">邮件配置</button>
|
||||
<button class="tab" onclick="switchTab('system')">系统配置</button>
|
||||
<button class="tab" onclick="switchTab('settings')">设置</button>
|
||||
@@ -1200,6 +1213,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 公告管理 -->
|
||||
<div id="tab-announcements" class="tab-content">
|
||||
<h3 style="margin-bottom: 15px; font-size: 16px;">公告管理</h3>
|
||||
|
||||
<!-- 创建公告 -->
|
||||
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
|
||||
<div class="form-group">
|
||||
<label>公告标题</label>
|
||||
<input type="text" id="announcementTitle" placeholder="请输入公告标题">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>公告内容</label>
|
||||
<textarea id="announcementContent" rows="5" placeholder="请输入公告内容(将以弹窗形式展示)"></textarea>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<button class="btn btn-primary" onclick="createAnnouncement(true)">发布并启用</button>
|
||||
<button class="btn btn-secondary" onclick="createAnnouncement(false)">保存但不启用</button>
|
||||
<button class="btn" onclick="clearAnnouncementForm()" style="background: #eee;">清空</button>
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #666; margin-top: 10px;">
|
||||
说明:启用公告后,用户登录进入系统将弹窗提示;用户可选择“当次关闭”或“永久关闭本次公告”。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 公告列表 -->
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; flex-wrap: wrap; gap: 10px;">
|
||||
<h4 style="font-size: 14px; margin: 0;">公告列表</h4>
|
||||
<button class="btn btn-primary" onclick="loadAnnouncements()" style="padding:8px 15px;">刷新</button>
|
||||
</div>
|
||||
<div id="announcementsList"></div>
|
||||
</div>
|
||||
|
||||
<!-- 邮件配置 -->
|
||||
<div id="tab-email" class="tab-content">
|
||||
<h3 style="margin-bottom: 15px; font-size: 16px;">邮件功能设置</h3>
|
||||
@@ -1455,12 +1500,23 @@
|
||||
<script>
|
||||
let allUsers = [];
|
||||
let pendingUsers = [];
|
||||
let announcements = [];
|
||||
|
||||
function escapeHtml(text) {
|
||||
return String(text ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
window.addEventListener('load', () => {
|
||||
loadStats();
|
||||
loadPendingUsers();
|
||||
loadAllUsers();
|
||||
loadAnnouncements();
|
||||
loadSystemConfig();
|
||||
loadProxyConfig();
|
||||
loadPasswordResets(); // 修复: 初始化时也加载密码重置申请
|
||||
@@ -1506,6 +1562,11 @@
|
||||
loadFeedbacks();
|
||||
}
|
||||
|
||||
// 切换到公告管理标签时加载公告
|
||||
if (tabName === 'announcements') {
|
||||
loadAnnouncements();
|
||||
}
|
||||
|
||||
// 切换到邮件配置标签时加载邮件相关数据
|
||||
if (tabName === 'email') {
|
||||
loadEmailSettings();
|
||||
@@ -1515,6 +1576,160 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 公告管理 ====================
|
||||
|
||||
async function loadAnnouncements() {
|
||||
try {
|
||||
const response = await fetch('/yuyx/api/announcements');
|
||||
if (!response.ok) {
|
||||
showNotification('加载公告失败', 'error');
|
||||
return;
|
||||
}
|
||||
announcements = await response.json();
|
||||
renderAnnouncements();
|
||||
} catch (e) {
|
||||
showNotification('加载公告失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function renderAnnouncements() {
|
||||
const container = document.getElementById('announcementsList');
|
||||
if (!container) return;
|
||||
|
||||
if (!announcements || announcements.length === 0) {
|
||||
container.innerHTML = '<div class="empty-message">暂无公告</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 70px;">ID</th>
|
||||
<th>标题</th>
|
||||
<th style="width: 90px;">状态</th>
|
||||
<th style="width: 170px;">创建时间</th>
|
||||
<th style="width: 220px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${announcements.map(a => `
|
||||
<tr>
|
||||
<td>${a.id}</td>
|
||||
<td>${escapeHtml(a.title || '')}</td>
|
||||
<td>
|
||||
<span class="status-badge ${a.is_active ? 'status-approved' : 'status-rejected'}">
|
||||
${a.is_active ? '启用' : '停用'}
|
||||
</span>
|
||||
</td>
|
||||
<td>${a.created_at || '-'}</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-small btn-secondary" onclick="viewAnnouncement(${a.id})">查看</button>
|
||||
${a.is_active
|
||||
? `<button class="btn btn-small btn-secondary" onclick="deactivateAnnouncement(${a.id})">停用</button>`
|
||||
: `<button class="btn btn-small btn-success" onclick="activateAnnouncement(${a.id})">启用</button>`
|
||||
}
|
||||
<button class="btn btn-small btn-danger" onclick="deleteAnnouncement(${a.id})">删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function clearAnnouncementForm() {
|
||||
const title = document.getElementById('announcementTitle');
|
||||
const content = document.getElementById('announcementContent');
|
||||
if (title) title.value = '';
|
||||
if (content) content.value = '';
|
||||
}
|
||||
|
||||
function viewAnnouncement(id) {
|
||||
const announcement = announcements.find(a => a.id === id);
|
||||
if (!announcement) return;
|
||||
alert(`标题:${announcement.title || ''}\n\n内容:\n${announcement.content || ''}`);
|
||||
}
|
||||
|
||||
async function createAnnouncement(isActive) {
|
||||
const title = (document.getElementById('announcementTitle')?.value || '').trim();
|
||||
const content = (document.getElementById('announcementContent')?.value || '').trim();
|
||||
if (!title || !content) {
|
||||
showNotification('标题和内容不能为空', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/yuyx/api/announcements', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title, content, is_active: !!isActive })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
showNotification(data.error || '发布失败', 'error');
|
||||
return;
|
||||
}
|
||||
showNotification('保存成功', 'success');
|
||||
clearAnnouncementForm();
|
||||
await loadAnnouncements();
|
||||
} catch (e) {
|
||||
showNotification('发布失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function activateAnnouncement(id) {
|
||||
if (!confirm('确定启用该公告吗?启用后将自动停用其他公告。')) return;
|
||||
try {
|
||||
const response = await fetch(`/yuyx/api/announcements/${id}/activate`, { method: 'POST' });
|
||||
const data = await response.json();
|
||||
if (!response.ok || !data.success) {
|
||||
showNotification(data.error || '启用失败', 'error');
|
||||
return;
|
||||
}
|
||||
showNotification('已启用', 'success');
|
||||
await loadAnnouncements();
|
||||
} catch (e) {
|
||||
showNotification('启用失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function deactivateAnnouncement(id) {
|
||||
if (!confirm('确定停用该公告吗?')) return;
|
||||
try {
|
||||
const response = await fetch(`/yuyx/api/announcements/${id}/deactivate`, { method: 'POST' });
|
||||
const data = await response.json();
|
||||
if (!response.ok || !data.success) {
|
||||
showNotification(data.error || '停用失败', 'error');
|
||||
return;
|
||||
}
|
||||
showNotification('已停用', 'success');
|
||||
await loadAnnouncements();
|
||||
} catch (e) {
|
||||
showNotification('停用失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAnnouncement(id) {
|
||||
if (!confirm('确定删除该公告吗?删除后无法恢复。')) return;
|
||||
try {
|
||||
const response = await fetch(`/yuyx/api/announcements/${id}`, { method: 'DELETE' });
|
||||
const data = await response.json();
|
||||
if (!response.ok || !data.success) {
|
||||
showNotification(data.error || '删除失败', 'error');
|
||||
return;
|
||||
}
|
||||
showNotification('已删除', 'success');
|
||||
await loadAnnouncements();
|
||||
} catch (e) {
|
||||
showNotification('删除失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// VIP functions
|
||||
function isVip(user) {
|
||||
if (!user.vip_expire_time) return false;
|
||||
@@ -3250,4 +3465,4 @@
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user