/* * MC Report System * Copyright (C) 2026 Sea Network Technology Studio * Author: CangLan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ // 匿名工单追踪: 公开页(无需登录) // 通过 cookie 中的 tracking_token 识别提交者, 可查看工单列表与单工单完整对话/处理日志, 并匿名补充回复 const TrackingPage = { token: '', async render(param) { this.token = document.cookie.split('; ').find(r => r.startsWith('tracking_token='))?.split('=')[1] || ''; const directId = param && !param.startsWith('?') ? param : ''; return `

使用提交工单时下发的追踪标识, 查看工单进展与双方回复。如未找到工单, 请确认使用同一浏览器提交。

`; }, mount(param) { const directId = param && !param.startsWith('?') ? param : ''; if (directId) { this.showDetail(directId); } else if (this.token) { this.search(); } }, // ---- 列表 ---- async search() { const input = document.getElementById('track-token'); const token = (input ? input.value.trim() : this.token) || this.token; if (!token) return alert('请输入追踪标识'); this.token = token; const ct = document.getElementById('track-result'); U.loading(ct); try { const tickets = await API.get(`/tickets/tracking?token=${encodeURIComponent(token)}`); ct.innerHTML = tickets.length ? `
我的工单 (${tickets.length})点击查看完整对话与处理进度
${tickets.map(t => ` `).join('')}
#类型标题状态提交时间操作
${t.id}${U.badge(t.type,'type')} ${U.esc(t.title)} ${U.badge(t.status,'status')}${U.date(t.created_at)}

如需邮件通知与完整功能, 请注册账号

` : '

未找到工单

'; } catch (e) { ct.innerHTML = `
${e.message}
`; } }, // ---- 单工单详情(对话流 + 处理日志 + 匿名回复) ---- async showDetail(id) { const ct = document.getElementById('track-result'); U.loading(ct); try { const t = await API.get(`/tickets/tracking/${id}`); this.renderDetail(ct, t); } catch (e) { ct.innerHTML = `
${e.message}
`; } }, renderDetail(ct, t) { const isRep = t.type === 'report'; const isAppeal = t.type === 'appeal' || t.type === 'result_appeal'; const isSystemMsg = r => r.is_staff === 1 && /^(【|状态变更为)/.test(r.content); const chat = (t.responses||[]).filter(r => !isSystemMsg(r)); const logItems = [ ...(t.responses||[]).filter(isSystemMsg).map(r => ({ time: r.created_at, text: r.content, who: r.username||'系统' })), ...(t.transfers||[]).map(tf => ({ time: tf.created_at, text: `转交: ${tf.from_game_name||tf.from_username} → ${tf.to_game_name||tf.to_username}${tf.reason?' ('+tf.reason+')':''}`, who: tf.from_username||'' })), ]; if (t.claimed_at) logItems.push({ time: t.claimed_at, text: `工单被认领 (${t.assignee_name||''})`, who: '' }); logItems.sort((a, b) => new Date(a.time) - new Date(b.time)); ct.innerHTML = `
对话记录 (${chat.length})${U.badge(t.status,'status')} ${U.badge(t.type,'type')} ${U.badge(t.priority,'priority')}
${chat.length ? `
${chat.map(r => `
${U.esc(r.username||(r.is_staff?'客服':'我'))}${U.date(r.created_at)}
${U.esc(r.content)}
`).join('')}
` : '

暂无对话记录

'}
补充信息
基本信息
标题${U.esc(t.title)}
提交者${U.esc(t.reporter_game_name)}${t.reporter_game_uid?' (UID: '+U.esc(t.reporter_game_uid)+')':''}
${isRep?`
被举报人${U.esc(t.target_game_name||'?')} (UID: ${U.esc(t.target_game_uid||'?')})
`:''} ${t.reason?`
${isRep?'原因':isAppeal?'申诉理由':'说明'}${U.esc(t.reason)}
`:''} ${t.description?`
${isRep?'详情':isAppeal?'详细说明':'建议内容'}${U.esc(t.description)}
`:''}
提交时间${U.date(t.created_at)}
${t.attachments?.length?`
附件 (${t.attachments.length})
${t.attachments.map(a=>a.mime_type.startsWith('image')?``:` ${U.esc(a.original_name)}`).join('')}
`:''}
处理日志 (${logItems.length})
${logItems.length ? `
${logItems.map(li => `
${U.esc(li.who||'系统')}${U.date(li.time)}
${U.esc(li.text)}
`).join('')}
` : '

暂无处理记录

'}
处理信息
负责人${t.assignee_name?`${U.esc(t.assignee_name)}`:'待认领'}
更新时间${U.date(t.updated_at)}
`; document.getElementById('trk-reply-form')?.addEventListener('submit', async e => { e.preventDefault(); const c = document.getElementById('trk-reply-content').value.trim(); if (!c) return; try { await API.post(`/tickets/${t.id}/anon-response`, { content: c }); alert('已提交'); this.showDetail(t.id); } catch (ex) { alert(ex.message); } }); } };