// ==UserScript== // @name 题库助手 - 保存与历史转发合并版 // @namespace pocketbase-answer-toolkit // @version 2.0.0 // @description 在教师端统一完成当前工单解答保存到 PocketBase 题库、历史解答读取与转发。 // @author ChatGPT + hyb // @match https://chath5.kaoshids.com/* // @match https://chatteacher.kaoshids.com/* // @connect chatteacher.kaoshids.com // @connect pocketbase.hellomaggie.top // @connect 127.0.0.1 // @connect localhost // @connect * // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @run-at document-idle // ==/UserScript== (function () { 'use strict'; const CONFIG = { PB_BASE_DEFAULT: 'https://pocketbase.hellomaggie.top', PB_COLLECTION: 'issue_problems', TEACHER_API_BASE: 'https://chatteacher.kaoshids.com', SOCKET_URL: 'wss://chatteacher.kaoshids.com/socket.io/?EIO=4&transport=websocket', GRADE_FIELD: 'grade', CHAT_PAGE_SIZE: 100, DEFAULT_DELAY_MS: 800 }; const state = { pbBase: GM_getValue('pbBase', GM_getValue('pb_base', CONFIG.PB_BASE_DEFAULT)), pbToken: GM_getValue('pbToken', ''), teacherToken: GM_getValue('teacherHttpToken', ''), delayMs: Number(GM_getValue('delayMs', CONFIG.DEFAULT_DELAY_MS)) || CONFIG.DEFAULT_DELAY_MS, activeTab: GM_getValue('pbtActiveTab', 'save'), collapsed: GM_getValue('pbtCollapsed', true), currentIssueId: null, currentRoomId: null, currentIssueInfo: null, profile: null, messages: [], existingOrder: null, existingProblem: null, existingProblems: [], oldProblemId: '', oldProblemRecord: null, solutionItems: [], socketToken: '', ws: null, namespaceConnected: false, ackSeq: 0, ackMap: new Map(), isSending: false, stopSending: false }; GM_addStyle(` #pbt-panel, .pbt-mask { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", Arial, sans-serif; color: #172033; } #pbt-panel *, .pbt-mask * { box-sizing: border-box; } #pbt-panel { position: fixed; right: 18px; bottom: 18px; z-index: 2147483645; width: min(420px, calc(100vw - 24px)); max-height: min(88vh, 760px); background: #ffffff; border: 1px solid #d9e2ec; border-radius: 8px; box-shadow: 0 18px 46px rgba(15, 23, 42, 0.2); overflow: hidden; } .pbt-head { min-height: 56px; padding: 11px 12px; display: flex; align-items: center; justify-content: space-between; gap: 10px; background: linear-gradient(135deg, #1d4ed8, #0f766e); color: #fff; user-select: none; cursor: move; } .pbt-title { font-size: 15px; line-height: 1.2; font-weight: 800; } .pbt-sub { margin-top: 3px; font-size: 12px; line-height: 1.25; color: rgba(255, 255, 255, .82); max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .pbt-head-actions, .pbt-actions, .pbt-btn-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .pbt-head-btn { border: 1px solid rgba(255, 255, 255, .28); background: rgba(255, 255, 255, .16); color: #fff; border-radius: 8px; padding: 6px 9px; font-size: 12px; line-height: 1; font-weight: 700; cursor: pointer; } .pbt-head-btn:hover { background: rgba(255, 255, 255, .24); } .pbt-body { max-height: calc(min(88vh, 760px) - 56px); overflow: auto; background: #f6f8fb; padding: 12px; } .pbt-hidden { display: none !important; } .pbt-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; margin-bottom: 10px; padding: 4px; border: 1px solid #dde5ee; border-radius: 8px; background: #eef3f8; } .pbt-tab { border: none; border-radius: 6px; background: transparent; color: #475569; padding: 8px 6px; font-size: 13px; font-weight: 800; cursor: pointer; } .pbt-tab.active { background: #ffffff; color: #0f172a; box-shadow: 0 1px 3px rgba(15, 23, 42, .08); } .pbt-card { background: #fff; border: 1px solid #dde5ee; border-radius: 8px; padding: 11px; margin-bottom: 10px; } .pbt-card-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 9px; color: #243244; font-size: 13px; font-weight: 900; } .pbt-small { font-size: 12px; line-height: 1.55; color: #64748b; word-break: break-word; } .pbt-meta { display: grid; gap: 5px; font-size: 12px; line-height: 1.55; color: #334155; word-break: break-word; } .pbt-meta b { color: #111827; } .pbt-field { margin-bottom: 10px; } .pbt-label { display: block; margin-bottom: 5px; font-size: 12px; color: #475569; font-weight: 800; } .pbt-input, .pbt-select, .pbt-textarea { width: 100%; border: 1px solid #cbd5e1; border-radius: 8px; padding: 9px 10px; color: #111827; background: #fff; outline: none; font-size: 13px; line-height: 1.4; } .pbt-input:focus, .pbt-select:focus, .pbt-textarea:focus { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37, 99, 235, .13); } .pbt-textarea { min-height: 84px; resize: vertical; } .pbt-btn { border: 1px solid #cbd5e1; border-radius: 8px; background: #fff; color: #334155; min-height: 34px; padding: 7px 11px; font-size: 13px; line-height: 1.2; font-weight: 800; cursor: pointer; } .pbt-btn:hover { background: #f8fafc; } .pbt-btn:disabled { opacity: .55; cursor: not-allowed; } .pbt-btn-primary { border-color: #2563eb; background: #2563eb; color: #fff; } .pbt-btn-primary:hover { background: #1d4ed8; } .pbt-btn-success { border-color: #15803d; background: #15803d; color: #fff; } .pbt-btn-success:hover { background: #166534; } .pbt-btn-danger { border-color: #dc2626; background: #dc2626; color: #fff; } .pbt-btn-warning { border-color: #f59e0b; background: #f59e0b; color: #fff; } .pbt-badge { display: inline-flex; align-items: center; justify-content: center; border-radius: 999px; background: #eef2f7; color: #475569; padding: 3px 8px; min-height: 22px; font-size: 12px; line-height: 1; font-weight: 800; white-space: nowrap; } .pbt-badge-blue { background: #e8f1ff; color: #1d4ed8; } .pbt-badge-green { background: #dcfce7; color: #166534; } .pbt-badge-orange { background: #fff7ed; color: #c2410c; } .pbt-badge-red { background: #fee2e2; color: #b91c1c; } .pbt-preview { display: grid; gap: 8px; } .pbt-history-item { border: 1px solid #e2e8f0; border-radius: 8px; background: #fff; padding: 9px; } .pbt-history-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 7px; } .pbt-check-line { display: inline-flex; align-items: center; gap: 7px; min-width: 0; font-size: 13px; color: #334155; font-weight: 700; } .pbt-item-content { max-height: 120px; overflow: auto; border-radius: 8px; background: #f8fafc; padding: 7px; color: #334155; font-size: 12px; line-height: 1.55; word-break: break-word; white-space: pre-wrap; } .pbt-item-content img { display: block; max-width: 100%; border-radius: 8px; border: 1px solid #e2e8f0; } .pbt-status { font-size: 12px; color: #64748b; font-weight: 800; white-space: nowrap; } .pbt-status.ok { color: #15803d; } .pbt-status.err { color: #dc2626; } .pbt-status.wait { color: #d97706; } .pbt-log { height: 128px; overflow: auto; border-radius: 8px; background: #111827; color: #dbeafe; padding: 9px; font-size: 12px; line-height: 1.55; white-space: pre-wrap; } .pbt-empty { border: 1px dashed #cbd5e1; border-radius: 8px; background: #f8fafc; color: #64748b; padding: 12px; text-align: center; font-size: 13px; line-height: 1.55; } .pbt-toast { position: fixed; right: 22px; bottom: 88px; z-index: 2147483647; max-width: min(420px, calc(100vw - 36px)); border-radius: 8px; background: #111827; color: #fff; padding: 10px 13px; font-size: 13px; line-height: 1.45; box-shadow: 0 16px 40px rgba(15, 23, 42, .28); } .pbt-mask { position: fixed; inset: 0; z-index: 2147483647; display: flex; align-items: center; justify-content: center; padding: 18px; background: rgba(15, 23, 42, .46); } .pbt-modal { width: min(1120px, 96vw); height: min(880px, 92vh); display: flex; flex-direction: column; overflow: hidden; border-radius: 8px; background: #f6f8fb; box-shadow: 0 26px 80px rgba(15, 23, 42, .32); } .pbt-modal-head, .pbt-modal-footer { background: #fff; border-color: #dde5ee; padding: 12px 14px; display: flex; align-items: center; justify-content: space-between; gap: 12px; } .pbt-modal-head { border-bottom: 1px solid #dde5ee; } .pbt-modal-footer { border-top: 1px solid #dde5ee; } .pbt-modal-title { color: #111827; font-size: 16px; font-weight: 900; line-height: 1.2; } .pbt-modal-sub { margin-top: 3px; color: #64748b; font-size: 12px; line-height: 1.35; } .pbt-save-layout { flex: 1; min-height: 0; display: grid; grid-template-columns: 370px minmax(0, 1fr); } .pbt-save-left, .pbt-save-right { min-height: 0; overflow: auto; padding: 14px; } .pbt-save-left { border-right: 1px solid #dde5ee; background: #f6f8fb; } .pbt-save-right { background: #fff; } .pbt-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; } .pbt-value { min-height: 32px; border: 1px solid #e2e8f0; border-radius: 8px; background: #f8fafc; padding: 7px 9px; font-size: 12px; line-height: 1.45; color: #334155; word-break: break-word; } .pbt-imgs { display: flex; flex-wrap: wrap; gap: 8px; } .pbt-img { width: 86px; height: 86px; border: 1px solid #e2e8f0; border-radius: 8px; object-fit: cover; cursor: zoom-in; background: #fff; } .pbt-msg-list { display: grid; gap: 8px; margin-top: 10px; } .pbt-msg { display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 9px; border: 1px solid #dde5ee; border-radius: 8px; background: #fff; padding: 10px; cursor: pointer; } .pbt-msg.checked { border-color: #2563eb; background: #f0f7ff; } .pbt-msg.disabled { opacity: .55; cursor: not-allowed; background: #f8fafc; } .pbt-msg-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 5px; color: #64748b; font-size: 12px; } .pbt-msg-content { color: #111827; font-size: 13px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; } @media (max-width: 760px) { #pbt-panel { right: 10px; bottom: 10px; width: calc(100vw - 20px); } .pbt-save-layout { grid-template-columns: 1fr; } .pbt-save-left { border-right: none; border-bottom: 1px solid #dde5ee; } .pbt-modal-head, .pbt-modal-footer { align-items: flex-start; flex-direction: column; } } `); function qs(selector, root = document) { return root.querySelector(selector); } function qsa(selector, root = document) { return Array.from(root.querySelectorAll(selector)); } function cleanText(text) { return String(text || '') .replace(/\r/g, '') .replace(/[ \t]+\n/g, '\n') .trim(); } function escapeHtml(value) { return String(value ?? '').replace(/[<>&"']/g, char => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[char])); } function normalizeUrl(url) { if (!url) return ''; return String(url).trim().replace(/^http:\/\//i, 'https://'); } function safeJsonParse(text, fallback = null) { try { return JSON.parse(text); } catch (_) { return fallback; } } function toArray(value) { if (Array.isArray(value)) return value; if (typeof value === 'string') { const parsed = safeJsonParse(value, []); return Array.isArray(parsed) ? parsed : []; } return []; } function toNumber(value, fallback = 0) { const number = Number(value); return Number.isFinite(number) ? number : fallback; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function formatTime(ts) { if (!ts) return ''; const date = new Date(Number(ts) * 1000); return Number.isNaN(date.getTime()) ? '' : date.toLocaleString(); } function toast(text, duration = 2600) { const old = qs('.pbt-toast'); if (old) old.remove(); const el = document.createElement('div'); el.className = 'pbt-toast'; el.textContent = text; document.body.appendChild(el); setTimeout(() => el.remove(), duration); } function log(message, type = 'info') { const el = qs('#pbt-log'); if (!el) return; const prefix = { info: '信息', success: '成功', warn: '提醒', error: '错误' }[type] || '信息'; const time = new Date().toLocaleTimeString(); el.textContent += `[${time}] ${prefix}:${message}\n`; el.scrollTop = el.scrollHeight; } function saveLog(message, type = 'info') { const el = qs('#pbt-save-log'); if (el) { if (el.textContent === '等待操作...') el.textContent = ''; el.textContent += `${message}\n`; el.scrollTop = el.scrollHeight; } log(message, type); } function getIssueIdFromUrl() { const text = String(location.href || ''); const match = text.match(/issueId[=/](\d+)/i) || text.match(/issue_id[=/](\d+)/i); return match ? Number(match[1]) : null; } function decodeJwtPayload(token) { try { const raw = String(token || '').replace(/^Bearer\s+/i, ''); const parts = raw.split('.'); if (parts.length < 2) return null; const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/'); const json = decodeURIComponent( atob(payload) .split('') .map(char => '%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2)) .join('') ); return JSON.parse(json); } catch (_) { return null; } } function collectJwtCandidates() { const root = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; const stores = []; try { stores.push(root.localStorage); } catch (_) {} try { stores.push(root.sessionStorage); } catch (_) {} try { stores.push(window.localStorage); } catch (_) {} try { stores.push(window.sessionStorage); } catch (_) {} const result = []; for (const store of stores) { if (!store) continue; for (let index = 0; index < store.length; index++) { const key = store.key(index); const value = store.getItem(key) || ''; const matches = value.match(/eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g); if (!matches) continue; for (const token of matches) { result.push({ key, token, payload: decodeJwtPayload(token) }); } } } return result; } function readSettingsFromPanel() { const pbBase = cleanText(qs('#pbt-pb-base')?.value || state.pbBase || CONFIG.PB_BASE_DEFAULT); const pbToken = cleanText(qs('#pbt-pb-token')?.value || state.pbToken || ''); const teacherToken = cleanText(qs('#pbt-teacher-token')?.value || state.teacherToken || ''); const delayMs = Math.max(300, Number(qs('#pbt-delay')?.value || state.delayMs || CONFIG.DEFAULT_DELAY_MS)); state.pbBase = pbBase; state.pbToken = pbToken.replace(/^Bearer\s+/i, ''); state.teacherToken = teacherToken.replace(/^Bearer\s+/i, ''); state.delayMs = delayMs; GM_setValue('pbBase', state.pbBase); GM_setValue('pb_base', state.pbBase); GM_setValue('pbToken', state.pbToken); GM_setValue('teacherHttpToken', state.teacherToken); GM_setValue('delayMs', state.delayMs); } function findTeacherToken() { if (state.teacherToken && state.teacherToken.length > 40) { return state.teacherToken.replace(/^Bearer\s+/i, ''); } const saved = GM_getValue('teacherHttpToken', ''); if (saved && saved.length > 40) { state.teacherToken = saved.replace(/^Bearer\s+/i, ''); return state.teacherToken; } const candidates = collectJwtCandidates(); const teacherToken = candidates.find(item => item.payload && item.payload.teacherId); const byKey = candidates.find(item => /token|auth|authorization/i.test(item.key || '')); const found = teacherToken || byKey; if (found?.token) { state.teacherToken = found.token.replace(/^Bearer\s+/i, ''); GM_setValue('teacherHttpToken', state.teacherToken); const input = qs('#pbt-teacher-token'); if (input && !input.value) input.value = state.teacherToken; return state.teacherToken; } return ''; } function ensureTeacherToken(ask = false) { const token = findTeacherToken(); if (token) return token; if (!ask) return ''; const manual = prompt('没有自动找到教师端 Token,请粘贴 Bearer Token:') || ''; state.teacherToken = manual.replace(/^Bearer\s+/i, '').trim(); if (state.teacherToken) { GM_setValue('teacherHttpToken', state.teacherToken); const input = qs('#pbt-teacher-token'); if (input) input.value = state.teacherToken; } return state.teacherToken; } function gmRequestJson(options) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || 'GET', url: options.url, headers: options.headers || {}, data: options.data === undefined ? undefined : JSON.stringify(options.data), timeout: options.timeout || 30000, onload: response => { const text = response.responseText || ''; let json = null; if (response.response && typeof response.response === 'object') { json = response.response; } else if (typeof response.response === 'string' && response.response.trim()) { json = safeJsonParse(response.response, null); } if (!json && text) { json = safeJsonParse(text, null); } if (response.status < 200 || response.status >= 300) { reject(new Error(`HTTP ${response.status}: ${text.slice(0, 500)}`)); return; } if (!json) { reject(new Error(`返回不是 JSON:${text.slice(0, 500)}`)); return; } resolve(json); }, onerror: err => reject(new Error('网络请求失败:' + JSON.stringify(err))), ontimeout: () => reject(new Error('请求超时:' + options.url)) }); }); } function normalizeTeacherResponse(json) { if (typeof json === 'string') { json = safeJsonParse(json, json); } if (json && json.errcode === 1) return json.data; if (json && json.data && json.data.errcode === 1) return json.data.data; throw new Error('教师端接口返回异常:' + JSON.stringify(json).slice(0, 500)); } async function teacherGet(path, askForToken = false) { const token = ensureTeacherToken(askForToken); if (!token) { throw new Error('没有教师端 Token,无法请求教师端接口。'); } const raw = await gmRequestJson({ method: 'GET', url: `${CONFIG.TEACHER_API_BASE}${path}`, headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }); return normalizeTeacherResponse(raw); } function pbBase() { return String(state.pbBase || CONFIG.PB_BASE_DEFAULT).replace(/\/+$/, ''); } function pbHeaders() { const headers = { 'Content-Type': 'application/json' }; if (state.pbToken) { headers.Authorization = `Bearer ${state.pbToken.replace(/^Bearer\s+/i, '')}`; } return headers; } async function pbGetList(collection, filter) { const url = `${pbBase()}/api/collections/${collection}/records?filter=${encodeURIComponent(filter)}`; const json = await gmRequestJson({ method: 'GET', url, headers: pbHeaders() }); return json.items || []; } async function pbGetOne(collection, id) { return gmRequestJson({ method: 'GET', url: `${pbBase()}/api/collections/${collection}/records/${encodeURIComponent(id)}`, headers: pbHeaders() }); } async function pbCreate(collection, payload) { return gmRequestJson({ method: 'POST', url: `${pbBase()}/api/collections/${collection}/records`, headers: pbHeaders(), data: payload }); } async function pbUpdate(collection, id, payload) { return gmRequestJson({ method: 'PATCH', url: `${pbBase()}/api/collections/${collection}/records/${id}`, headers: pbHeaders(), data: payload }); } function isAiMessage(message) { return ( message?.msg_type === 'ai' || message?.sender_id === 1 || message?.teacher_sender?.nickname === 'AI' ); } function isTeacherTemplateMessage(message) { const body = message?.msg_body || ''; return /老师已经收到你的题目|15-30\s*分钟|转接人工|已为你转接|请你检查并补充完整|会在\s*15/.test(body); } function isValidSolutionMessage(message) { if (!message) return false; if (!['text', 'photo', 'voice'].includes(message.msg_type)) return false; if (isAiMessage(message)) return false; return true; } function isDefaultChecked(message) { if (!isValidSolutionMessage(message)) return false; if (message.sender_type !== 1) return false; if (isTeacherTemplateMessage(message)) return false; return true; } function getSenderName(message) { if (isAiMessage(message)) return 'AI'; if (message.sender_type === 1) return message.teacher_sender?.nickname || `老师${message.sender_id || ''}`; if (message.sender_type === 2) return message.student_sender?.nickname || `学生${message.sender_id || ''}`; return `发送者${message.sender_id || ''}`; } function getTypeName(type) { return { text: '文字', photo: '图片', voice: '语音', ai: 'AI' }[type] || type || '未知'; } function previewBody(message) { let body = message.msg_body || ''; if (message.msg_type === 'photo') body = '[图片] ' + body; if (message.msg_type === 'voice') body = '[语音] ' + body; if (message.msg_type === 'ai') body = '[AI] ' + body; if (body.length > 160) body = body.slice(0, 160) + '...'; return body; } function getTeacherId(issue, messages) { return ( issue?.assignments?.[0]?.teacher_id || messages.find(message => message.sender_type === 1 && !isAiMessage(message))?.sender_id || 0 ); } function getTeacherName(messages, profile) { const message = messages.find(item => item.sender_type === 1 && !isAiMessage(item) && item.teacher_sender?.nickname); return message?.teacher_sender?.nickname || profile?.nickname || ''; } function buildProblemImages(issue, messages) { const result = []; const seen = new Set(); function add(url) { if (!url) return; const normalized = normalizeUrl(url); if (seen.has(normalized)) return; seen.add(normalized); result.push({ index: result.length + 1, msg_type: 'photo', msg_body: normalized }); } add(issue?.issue_pic); messages .filter(message => message.sender_type === 2 && message.msg_type === 'photo' && message.msg_body) .forEach(message => add(message.msg_body)); return result; } function buildSolution(messages) { return messages.map((message, index) => { const item = { index: index + 1, msg_type: message.msg_type, msg_body: message.msg_type === 'text' ? cleanText(message.msg_body) : normalizeUrl(message.msg_body), explain: message.msg_type === 'text' ? '老师文字解答' : message.msg_type === 'photo' ? '老师解答图片' : message.msg_type === 'voice' ? '老师语音讲解' : '老师解答记录' }; if (message.msg_type === 'voice' && message.duration) { item.duration = toNumber(message.duration); } if (message.msg_id) { item.source_msg_id = message.msg_id; } return item; }); } async function fetchCurrentIssueInfo(options = {}) { const askForToken = Boolean(options.askForToken); const withMessages = Boolean(options.withMessages); const readExisting = Boolean(options.readExisting); const issueId = getIssueIdFromUrl(); if (!issueId) { state.currentIssueId = null; state.currentRoomId = null; state.currentIssueInfo = null; renderCurrentInfo(); throw new Error('当前页面 URL 中没有识别到 issueId,请确认你在教师端工单或聊天页。'); } state.currentIssueId = issueId; const issue = await teacherGet(`/apis/teacher/issue/${issueId}`, askForToken); state.currentIssueInfo = issue; state.currentRoomId = issue.room_id; if (!state.currentRoomId) { renderCurrentInfo(); throw new Error('当前工单没有返回 room_id,无法确定聊天房间。'); } if (withMessages) { const [chatData, profile] = await Promise.all([ teacherGet(`/apis/teacher/chat-message?room_id=${issue.room_id}&page_size=${CONFIG.CHAT_PAGE_SIZE}&direction=backward`, askForToken), teacherGet('/apis/teacher/profile', askForToken).catch(() => null) ]); state.profile = profile; state.messages = Array.isArray(chatData) ? chatData.slice().sort((a, b) => (a.client_time || 0) - (b.client_time || 0)) : []; } if (readExisting) { try { const orders = await pbGetList('work_orders', `issue_id=${Number(issue.issue_id)}`); state.existingOrder = orders[0] || null; if (state.existingOrder?.id) { const problems = await pbGetList(CONFIG.PB_COLLECTION, `issue="${state.existingOrder.id}"`); state.existingProblems = problems || []; state.existingProblem = null; } else { state.existingProblem = null; state.existingProblems = []; } } catch (err) { state.existingOrder = null; state.existingProblem = null; state.existingProblems = []; log('读取 PocketBase 已有记录失败,不影响继续操作:' + err.message, 'warn'); } } renderCurrentInfo(); return issue; } function renderCurrentInfo() { const info = state.currentIssueInfo || {}; const student = info.student_name || info.studentName || '-'; const title = cleanText(info.issue_title || '').replace(/\s+/g, ' '); const html = `
`; qsa('.pbt-current-info').forEach(el => { el.innerHTML = state.currentIssueInfo ? html : '${escapeHtml(err.message || String(err))}
等待操作...