From c4363deb46a716b3338c335581805d9fa630a439 Mon Sep 17 00:00:00 2001 From: canglan Date: Fri, 21 Aug 2026 22:02:06 +0800 Subject: [PATCH] feat: ticket create - sub-server selector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ticket-create.js: added 子服务器 dropdown (group/server from /polls/server-list, supports anonymous users), sends 'server' field - tickets.js POST: accept server param, resolve 'group/server' or alias/server_name from server_groups, store into tickets.server_name - polls.js: new public GET /server-list (group_name+server_name only, no auth required) for ticket create page - verified: 13 checks (frontend/backend/db/column/syntax) --- backend/routes/polls.js | 7 +++++++ backend/routes/tickets.js | 21 ++++++++++++++++++--- public/js/pages/ticket-create.js | 16 ++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/backend/routes/polls.js b/backend/routes/polls.js index 92488fd..a8ae0d9 100644 --- a/backend/routes/polls.js +++ b/backend/routes/polls.js @@ -33,6 +33,13 @@ router.get('/groups', authenticate, async (req, res) => { res.json(await query('SELECT * FROM server_groups ORDER BY group_name, server_name')); }); +// 公开: 子服务器列表(工单提交页匿名/登录共用, 不含别名等内部字段) +router.get('/server-list', async (req, res) => { + try { + res.json(await query('SELECT group_name, server_name FROM server_groups ORDER BY group_name, server_name')); + } catch { res.json([]); } +}); + router.post('/groups', authenticate, requireRole('owner'), async (req, res) => { const { group_name, server_name, alias } = req.body; if (!group_name || !server_name) return res.status(400).json({ error: '分组名和子服名为必填' }); diff --git a/backend/routes/tickets.js b/backend/routes/tickets.js index c1414a1..ad5265c 100644 --- a/backend/routes/tickets.js +++ b/backend/routes/tickets.js @@ -129,9 +129,23 @@ router.get('/:id', authenticate, async (req, res) => { router.post('/', ticketAnonLimiter, optionalAuth, upload.array('files', 5), finalizeUpload, validateLengths({ title:100, reporter_game_name:50, reporter_game_uid:50, target_game_name:50, target_game_uid:50, reason:300, description:5000, }), async (req, res) => { - const { type, title, reporter_game_name, reporter_game_uid, target_game_name, target_game_uid, reason, description, is_admin_complaint, parent_ticket_id } = req.body; + const { type, title, reporter_game_name, reporter_game_uid, target_game_name, target_game_uid, reason, description, is_admin_complaint, parent_ticket_id, server } = req.body; if (!type || !['report','suggestion','appeal','result_appeal'].includes(type)) return res.status(400).json({ error: '类型不正确' }); if (!title) return res.status(400).json({ error: '标题不能为空' }); + // 子服务器解析: '分组/子服' 或 alias 或 server_name + let serverName = ''; + if (server) { + let g = '', s = ''; + if (String(server).includes('/')) { + const parts = String(server).split('/'); + g = parts[0].trim(); s = parts[1].trim(); + serverName = s; + } else { + const row = await getRow('SELECT group_name, server_name FROM server_groups WHERE alias = ? OR server_name = ? LIMIT 1', [server, server]); + if (row) { g = row.group_name; s = row.server_name; serverName = s; } + else serverName = String(server).trim(); + } + } // 身份解析: 登录用户可选用自己绑定的任一身份(网易端/皮肤站); 匿名用表单值 let rgn = reporter_game_name, rgu = reporter_game_uid; if (req.user) { @@ -167,14 +181,15 @@ router.post('/', ticketAnonLimiter, optionalAuth, upload.array('files', 5), fina try { await conn.beginTransaction(); const [r] = await conn.execute(`INSERT INTO tickets(type,title,user_id,reporter_game_name,reporter_game_uid, - target_game_name,target_game_uid,reason,description,tracking_token,is_admin_complaint,parent_ticket_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`, + target_game_name,target_game_uid,reason,description,tracking_token,is_admin_complaint,parent_ticket_id,server_name) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`, [type, title, req.user?.id||null, rgn, rgu||'', type==='report'?(target_game_name||''):null, type==='report'?(target_game_uid||''):null, (type==='report'||type==='appeal'||type==='result_appeal')?reason:null, (type==='suggestion'||type==='appeal'||type==='result_appeal')?description:'', trackingToken, is_admin_complaint ? 1 : 0, - parent_ticket_id || null]); + parent_ticket_id || null, + serverName]); ticketId = r.insertId; if (req.files?.length) { diff --git a/public/js/pages/ticket-create.js b/public/js/pages/ticket-create.js index 57859ce..d9c7d42 100644 --- a/public/js/pages/ticket-create.js +++ b/public/js/pages/ticket-create.js @@ -41,6 +41,17 @@ const TicketCreate = { }).catch(() => {}); } + // 子服务器列表(server_groups: 分组/子服) + this.servers = []; + API.get('/polls/server-list').then(list => { + this.servers = list || []; + const sel = container.querySelector('#server-select'); + if (sel) { + sel.innerHTML = '' + this.servers.map(g => + ``).join(''); + } + }).catch(() => {}); + container.innerHTML = ` ${hideTabs ? '' : `
@@ -58,6 +69,11 @@ const TicketCreate = {
+
+ + +
+