From 7d4551e7f24ed817583b946c72b5bafc82730f60 Mon Sep 17 00:00:00 2001 From: brianling Date: Thu, 16 Jul 2026 13:41:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A8=A1=E6=9D=BF=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=20+=20=E6=A8=A1=E6=9D=BF=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=88=B7=E6=96=B0=E4=B8=BB=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=20+=20=E7=AA=97=E5=8F=A3=E6=94=BE=E5=A4=A7=20+=20=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=A0=8F=E5=8F=AF=E6=8B=96=E6=8B=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main/ipc.ts | 57 ++++++++++++++++++++++- electron/main/window.ts | 10 ++-- electron/preload/preload.ts | 11 ++++- src/components/FloatingToolbar.vue | 31 +++++++++++-- src/pages/TemplateDialog.vue | 73 ++++++++++-------------------- src/types/index.ts | 3 ++ 6 files changed, 123 insertions(+), 62 deletions(-) diff --git a/electron/main/ipc.ts b/electron/main/ipc.ts index 0983939..019e1df 100644 --- a/electron/main/ipc.ts +++ b/electron/main/ipc.ts @@ -111,7 +111,7 @@ export function setupIpc(win: BrowserWindow): void { ipcMain.handle('window:resizeMain', (_event, width: number) => { const [wx, wy] = win.getPosition() - win.setBounds({ x: wx, y: wy, width: Math.max(200, Math.min(width, 1200)), height: 64 }) + win.setBounds({ x: wx, y: wy, width: Math.max(200, Math.min(width, 1200)), height: 76 }) }) ipcMain.handle('screenshot:start', () => { @@ -163,7 +163,8 @@ export function setupIpc(win: BrowserWindow): void { return result } catch (err: any) { log.error('AI grade failed:', err) - const apiMsg = err?.response?.data?.error?.message || '' + const res = err?.response?.data + const apiMsg = res?.error?.message || res?.error || res?.message || '' const allMsg = (apiMsg + ' ' + (err.message || '')).toLowerCase() if (allMsg.includes('does not support image') || allMsg.includes('image input') || allMsg.includes('not support image')) { throw new Error('当前模型不支持图片输入,请在设置中更换为支持多模态的模型(如 gpt-4o、qwen-vl 等)') @@ -277,6 +278,56 @@ export function setupIpc(win: BrowserWindow): void { } }) + ipcMain.handle('template:exportJson', async () => { + try { + const d = getDb() + const rows: any[] = d.prepare('SELECT * FROM exam ORDER BY created_at DESC').all() + if (!rows.length) throw new Error('暂无模板可导出') + const templates = rows.map(r => ({ + name: r.name, + course: r.course, + questions: r.questions_json ? JSON.parse(r.questions_json) : [], + created_at: r.created_at + })) + const result = await dialogBox.showSaveDialog({ + defaultPath: '评分模板.json', + filters: [{ name: 'JSON', extensions: ['json'] }] + }) + if (result.canceled || !result.filePath) return false + fs.writeFileSync(result.filePath, JSON.stringify(templates, null, 2), 'utf-8') + return true + } catch (err) { + log.error('template:exportJson error:', err) + return false + } + }) + + ipcMain.handle('template:importJson', async () => { + try { + const result = await dialogBox.showOpenDialog({ + properties: ['openFile'], + filters: [{ name: 'JSON', extensions: ['json'] }] + }) + if (result.canceled || result.filePaths.length === 0) return null + const content = fs.readFileSync(result.filePaths[0], 'utf-8') + const data = JSON.parse(content) + if (!Array.isArray(data)) throw new Error('JSON 格式错误:应为数组') + const db = getDb() + const insert = db.prepare('INSERT INTO exam (name, course, questions_json) VALUES (?, ?, ?)') + let count = 0 + for (const tpl of data) { + if (tpl.name && tpl.course) { + insert.run(tpl.name, tpl.course, JSON.stringify(tpl.questions || [])) + count++ + } + } + return { count } + } catch (err) { + log.error('template:importJson error:', err) + return null + } + }) + ipcMain.handle('template:save', (_event, data: { name: string; course: string; questions?: any[] }) => { try { const db = getDb() @@ -315,6 +366,8 @@ export function setupIpc(win: BrowserWindow): void { insert.run(q.questionTitle || '', q.maxScore ?? 100, q.referenceAnswer || '', q.rubric || '', i, q.referenceAnswerImage || '') } rubricNextIndex = 0 + const mainWin = getMainWindow() + if (mainWin) mainWin.webContents.send('rubric:updated') return true } catch (err) { log.error('template:apply error:', err) diff --git a/electron/main/window.ts b/electron/main/window.ts index daa139f..c7c9c5a 100644 --- a/electron/main/window.ts +++ b/electron/main/window.ts @@ -62,11 +62,11 @@ export function openDialogWindow(type: string): BrowserWindow | null { } const sizes: Record = { - settings: { width: 520, height: 560 }, - history: { width: 800, height: 600 }, - templates: { width: 640, height: 520 }, - rubric: { width: 640, height: 700 }, - result: { width: 520, height: 700 } + settings: { width: 640, height: 680 }, + history: { width: 1000, height: 750 }, + templates: { width: 720, height: 600 }, + rubric: { width: 750, height: 800 }, + result: { width: 600, height: 780 } } const size = sizes[type] || { width: 500, height: 500 } diff --git a/electron/preload/preload.ts b/electron/preload/preload.ts index 8c814dc..a2556ba 100644 --- a/electron/preload/preload.ts +++ b/electron/preload/preload.ts @@ -57,14 +57,21 @@ contextBridge.exposeInMainWorld('electronAPI', { importJson: () => ipcRenderer.invoke('rubric:importJson'), getNextIndex: () => ipcRenderer.invoke('rubric:getNextIndex'), setNextIndex: (idx: number) => ipcRenderer.invoke('rubric:setNextIndex', idx), - resetNextIndex: () => ipcRenderer.invoke('rubric:resetNextIndex') + resetNextIndex: () => ipcRenderer.invoke('rubric:resetNextIndex'), + onUpdated: (callback: () => void) => { + const handler = () => callback() + ipcRenderer.on('rubric:updated', handler) + return () => ipcRenderer.removeListener('rubric:updated', handler) + } }, template: { list: () => ipcRenderer.invoke('template:list'), save: (data: { name: string; course: string; questions?: any[] }) => ipcRenderer.invoke('template:save', data), delete: (id: number) => ipcRenderer.invoke('template:delete', id), - apply: (id: number) => ipcRenderer.invoke('template:apply', id) + apply: (id: number) => ipcRenderer.invoke('template:apply', id), + exportJson: () => ipcRenderer.invoke('template:exportJson'), + importJson: () => ipcRenderer.invoke('template:importJson') }, dialog: { diff --git a/src/components/FloatingToolbar.vue b/src/components/FloatingToolbar.vue index fef017f..5c38f5c 100644 --- a/src/components/FloatingToolbar.vue +++ b/src/components/FloatingToolbar.vue @@ -41,11 +41,12 @@ 设置 +
{{ statusText }}
diff --git a/src/types/index.ts b/src/types/index.ts index e265c21..fc306eb 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -115,12 +115,15 @@ export interface ElectronAPI { getNextIndex: () => Promise setNextIndex: (idx: number) => Promise resetNextIndex: () => Promise + onUpdated: (callback: () => void) => () => void } template: { list: () => Promise save: (data: { name: string; course: string; questions?: QuestionItem[] }) => Promise delete: (id: number) => Promise apply: (id: number) => Promise + exportJson: () => Promise + importJson: () => Promise<{ count: number } | null> } dialog: { open: (type: string, data?: any) => Promise