diff --git a/frontend/assets/dashboard.svg b/frontend/assets/dashboard.svg index 25e6bfa..b6f49ab 100644 --- a/frontend/assets/dashboard.svg +++ b/frontend/assets/dashboard.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/assets/favicon.svg b/frontend/assets/favicon.svg index a7fc8fc..8189c6e 100644 --- a/frontend/assets/favicon.svg +++ b/frontend/assets/favicon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/frontend/assets/styles.css b/frontend/assets/styles.css index cd2862a..3fd370c 100644 --- a/frontend/assets/styles.css +++ b/frontend/assets/styles.css @@ -538,8 +538,8 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{ /* Dashboard icon - SVG */ .icon-dashboard { - width: 20px; - height: 20px; + width: 30px; + height: 30px; display: inline-block; background-image: url('../assets/dashboard.svg'); background-size: contain; diff --git a/frontend/js/api.js b/frontend/js/api.js index 4b53321..2c7f9ba 100644 --- a/frontend/js/api.js +++ b/frontend/js/api.js @@ -155,6 +155,7 @@ const API = (() => { adminUsers: () => request('/admin/users'), resetPassword: (username, new_password) => request('/admin/reset-password', { method: 'POST', body: JSON.stringify({ username, new_password }) }), changePassword: (username, new_password) => request('/admin/change-password', { method: 'POST', body: JSON.stringify({ username, new_password }) }), + deleteUser: username => request('/admin/delete-user', { method: 'POST', body: JSON.stringify({ username }) }), clearModule: module => request('/admin/clear', { method: 'POST', body: JSON.stringify({ module }) }), getNotifications: () => requestQuiet('/notifications'), getUnreadCount: () => requestQuiet('/notifications/unread-count'), diff --git a/frontend/js/components/dashboard.js b/frontend/js/components/dashboard.js index 2afe826..96e42cf 100644 --- a/frontend/js/components/dashboard.js +++ b/frontend/js/components/dashboard.js @@ -35,6 +35,11 @@ const Dashboard = (() => { window.removeEventListener('resize', window.__dashboardResizeHandler); window.__dashboardResizeHandler = null; } + // 清理主题切换监听器 + if(window.__dashboardThemeHandler){ + window.removeEventListener('themeChanged', window.__dashboardThemeHandler); + window.__dashboardThemeHandler = null; + } // 清理全局变量 window.__auditBusy = false; window.__pddParams = null; @@ -271,9 +276,30 @@ const Dashboard = (() => { ctx.fillText('圆通', padding.left + 98, 15); }; + // 初始化缓存数据 + window.__auditCache = window.__auditCache || {pdd: [], yt: []}; + if(pdd.list && pdd.list.length > 0) window.__auditCache.pdd = pdd.list; + if(yt.list && yt.list.length > 0) window.__auditCache.yt = yt.list; + // 初始绘制 drawTrendChart(pdd.list, yt.list); + // 监听主题切换事件,立即重绘图表 + const themeChangeHandler = () => { + // 使用缓存数据或初始数据 + const pddData = (window.__auditCache && window.__auditCache.pdd && window.__auditCache.pdd.length > 0) + ? window.__auditCache.pdd + : pdd.list; + const ytData = (window.__auditCache && window.__auditCache.yt && window.__auditCache.yt.length > 0) + ? window.__auditCache.yt + : yt.list; + drawTrendChart(pddData, ytData); + }; + window.addEventListener('themeChanged', themeChangeHandler); + + // 保存监听器引用以便清理 + window.__dashboardThemeHandler = themeChangeHandler; + // 添加鼠标悬停事件 const canvas = document.getElementById('trend-chart'); const tooltip = document.getElementById('chart-tooltip'); @@ -361,8 +387,7 @@ const Dashboard = (() => { }; } - // 资源管理:缓存数据(已禁用自动清理) - window.__auditCache = window.__auditCache || {pdd: [], yt: []}; + // 资源管理:缓存数据(已在初始化时创建) // 优化的刷新函数:一次请求同时更新趋势图和列表 const refreshAll = async() => { diff --git a/frontend/js/components/settings.js b/frontend/js/components/settings.js index 6457261..d7bd048 100644 --- a/frontend/js/components/settings.js +++ b/frontend/js/components/settings.js @@ -1,7 +1,7 @@ Router.register('/settings', async () => { const me = await API.me().catch(()=>({})); const users = (me && me.role === 'superadmin') ? await API.adminUsers().catch(()=>({list:[]})) : {list:[]}; - const userList = (users.list||[]).map(u=>`
  • ${u.username}${u.role}
  • `).join('') || '
  • 暂无用户
  • '; + const userList = (users.list||[]).map(u=>`
  • ${u.username}${u.role}
  • `).join('') || '
  • 暂无用户
  • '; const html = `
    账户设置
    @@ -311,6 +311,28 @@ Router.register('/settings', async () => { API.toast(enabled ? '水印已启用' : '水印已关闭'); } }); + + // 删除用户 + document.querySelectorAll('button[data-delete-user]')?.forEach(btn => { + btn.addEventListener('click', async () => { + const username = btn.getAttribute('data-delete-user'); + if (!confirm(`确定要删除用户 "${username}" 吗?此操作不可恢复。`)) { + return; + } + + btn.disabled = true; + try { + await API.deleteUser(username); + API.toast('用户已删除'); + // 刷新页面以更新用户列表 + setTimeout(() => Router.navigate('/settings'), 1000); + } catch(e) { + API.toast('删除失败'); + } finally { + btn.disabled = false; + } + }); + }); document.querySelectorAll('button[data-clear]')?.forEach(btn => { btn.addEventListener('click', async () => { const mod = btn.getAttribute('data-clear'); diff --git a/server/app.py b/server/app.py index bf2e468..7f050ab 100644 --- a/server/app.py +++ b/server/app.py @@ -1541,6 +1541,53 @@ def add_user(): return jsonify({'error': f'创建用户失败:{str(e)}'}), 500 +@app.post('/api/admin/delete-user') +@require_login +@require_any_role('superadmin') +def delete_user(): + """删除用户""" + data = request.get_json() or {} + username = (data.get('username') or '').strip() + + if not username: + return jsonify({'error': '用户名不能为空'}), 400 + + # 获取当前登录用户 + current_user = session.get('user') + if current_user and current_user.get('username') == username: + return jsonify({'error': '不能删除当前登录的用户'}), 400 + + conn = get_db() + c = conn.cursor() + + # 检查用户是否存在 + c.execute('SELECT id, role FROM users WHERE username=?', (username,)) + user = c.fetchone() + if not user: + conn.close() + return jsonify({'error': '用户不存在'}), 404 + + # 检查是否是最后一个超级管理员 + if user['role'] == 'superadmin': + c.execute('SELECT COUNT(*) as cnt FROM users WHERE role=?', ('superadmin',)) + count = c.fetchone()['cnt'] + if count <= 1: + conn.close() + return jsonify({'error': '不能删除最后一个超级管理员'}), 400 + + # 删除用户 + try: + c.execute('DELETE FROM users WHERE username=?', (username,)) + conn.commit() + conn.close() + + log('delete_user', f'username={username}') + return jsonify({'ok': True, 'message': f'用户 {username} 已删除'}) + except Exception as e: + conn.close() + return jsonify({'error': f'删除用户失败:{str(e)}'}), 500 + + @app.post('/api/admin/clear') @require_login @require_any_role('superadmin')