From 09f0d8f2aafbccf2cbf4c65b5270c5b159b3bdaf Mon Sep 17 00:00:00 2001 From: canglan Date: Thu, 16 Jul 2026 23:04:15 +0800 Subject: [PATCH] fix: 12 issues - admin permission scope, player sidebar nav, SMTP label, user create servers --- backend/routes/users.js | 1 + public/js/app.js | 28 ++++++++++++++-------------- public/js/pages/settings-page.js | 2 +- public/js/pages/users.js | 16 +++++++++++++++- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/backend/routes/users.js b/backend/routes/users.js index 0ce52ea..d3ee410 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -24,6 +24,7 @@ router.post('/', authenticate, requireRole('owner','admin'), async (req, res) => const { username, password, email, game_name, game_uid, role, source } = req.body; if (!username||!password||!email||!game_name||!game_uid||!role) return res.status(400).json({ error: '所有字段必填' }); if (!['owner','admin','player'].includes(role)) return res.status(400).json({ error: '无效角色' }); + if (role === 'owner' && req.user.role !== 'owner') return res.status(403).json({ error: '仅服主可创建服主账号' }); if (password.length < 6) return res.status(400).json({ error: '密码至少6位' }); if (await getRow('SELECT id FROM users WHERE username = ?', [username])) return res.status(400).json({ error: '用户名已存在' }); const hashed = bcrypt.hashSync(password, 10); diff --git a/public/js/app.js b/public/js/app.js index 1962fa0..33a8951 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,17 +1,17 @@ const App = { navItems: [ { id: 'dashboard', label: '控制台', icon: 'fa-chart-simple', roles: ['owner','admin','player'] }, - { id: 'unclaimed', label: '待处理工单', icon: 'fa-inbox', roles: ['owner','admin'] }, + { id: 'tickets-create', label: '提交工单', icon: 'fa-plus-circle', roles: ['player'] }, { id: 'tickets', label: '工单列表', icon: 'fa-ticket-alt', roles: ['owner','admin','player'] }, - { id: 'tracking', label: '追踪工单', icon: 'fa-search', roles: ['owner','admin','player'] }, - { id: 'users', label: '用户管理', icon: 'fa-users', roles: ['owner','admin'] }, - { id: 'notifications', label: '通知配置', icon: 'fa-bell', roles: ['owner','admin'] }, - { id: 'templates', label: '邮件模板', icon: 'fa-envelope', roles: ['owner','admin'] }, - { id: 'settings', label: '系统设置', icon: 'fa-cog', roles: ['owner','admin'] }, + { id: 'unclaimed', label: '待处理工单', icon: 'fa-inbox', roles: ['owner','admin'] }, + { id: 'users', label: '用户管理', icon: 'fa-users', roles: ['owner'] }, + { id: 'servers', label: '服务器管理', icon: 'fa-server', roles: ['owner'] }, + { id: 'polls', label: '投票', icon: 'fa-poll', roles: ['owner'] }, + { id: 'features', label: '更新内容', icon: 'fa-lightbulb', roles: ['owner'] }, + { id: 'notifications', label: '通知配置', icon: 'fa-bell', roles: ['owner'] }, + { id: 'templates', label: '邮件模板', icon: 'fa-envelope', roles: ['owner'] }, + { id: 'settings', label: '系统设置', icon: 'fa-cog', roles: ['owner'] }, { id: 'export', label: '数据导出', icon: 'fa-download', roles: ['owner'] }, - { id: 'polls', label: '投票', icon: 'fa-poll', roles: ['owner','admin','player'] }, - { id: 'features', label: '更新内容', icon: 'fa-lightbulb', roles: ['owner','admin','player'] }, - { id: 'servers', label: '服务器管理', icon: 'fa-server', roles: ['owner','admin'] }, ], async init() { @@ -81,6 +81,7 @@ const App = { switch (page) { case 'dashboard': this.renderMain('控制台', Dashboard, param); break; case 'unclaimed': this.renderMain('待处理工单', UnclaimedPage, param); break; + case 'tickets-create': App.navigate('tickets','create?type=report'); break; case 'tickets': if (param === 'create') { const q = new URLSearchParams((location.hash.split('?')[1] || '')); @@ -120,10 +121,9 @@ const App = { if (Auth.logged()) { const u = Auth.user(); el.innerHTML = `${U.esc(u.game_name||u.username)} - 控制台 - 投票 - 更新 - ${Auth.isAdmin() ? `待处理`:''} + ${Auth.isAdmin() ? ` 控制台` : ''} + ${Auth.isAdmin() ? `待处理` : ''} + ${Auth.isOwner() ? `` : ''} `; } else { el.innerHTML = ` 登录 注册`; @@ -136,7 +136,7 @@ const App = { document.getElementById('public-page').classList.add('hidden'); document.getElementById('main-layout').classList.remove('hidden'); const u = Auth.user(); - document.getElementById('user-info').innerHTML = `
${U.esc(u.game_name||u.username).charAt(0).toUpperCase()}
${U.esc(u.game_name||u.username)}
${U.ROLE[u.role]}
`; + document.getElementById('user-info').innerHTML = `
${U.esc(u.game_name||u.username)}
`; document.getElementById('sidebar-logout').onclick = () => App.logout(); this.loadFooter(); }, diff --git a/public/js/pages/settings-page.js b/public/js/pages/settings-page.js index 80c70d7..e1beaea 100644 --- a/public/js/pages/settings-page.js +++ b/public/js/pages/settings-page.js @@ -25,7 +25,7 @@ const SettingsPage = {
-
+
diff --git a/public/js/pages/users.js b/public/js/pages/users.js index afdb371..259485e 100644 --- a/public/js/pages/users.js +++ b/public/js/pages/users.js @@ -30,23 +30,37 @@ const UsersPage = { }, createUser() { + this.loadServersForCreate(); + }, + + async loadServersForCreate() { + let groups = []; + try { groups = await API.get('/polls/groups'); } catch {} + const checks = groups.map(g => { + return ``; + }).join(''); + U.modal('添加用户', `
-
+
+
`); document.getElementById('cu-form').onsubmit = async e => { e.preventDefault(); + const servers = []; + document.querySelectorAll('.cu-srv:checked').forEach(cb => { const [g,s] = cb.value.split('::'); servers.push({group_name:g,server_name:s}); }); try { await API.post('/users', { username: document.getElementById('cu-u').value, password: document.getElementById('cu-p').value, email: document.getElementById('cu-e').value, role: document.getElementById('cu-r').value, game_name: document.getElementById('cu-gn').value, game_uid: document.getElementById('cu-gu').value, source: document.getElementById('cu-s').value, + admin_servers: servers, }); App.closeModal(); this.load(); } catch (ex) { alert(ex.message); }