feat: 模板导出导入 + 模板应用自动刷新主窗口 + 窗口放大 + 状态栏可拖拽

This commit is contained in:
2026-07-16 13:41:58 +08:00
parent e84b0705e5
commit 7d4551e7f2
6 changed files with 123 additions and 62 deletions
+55 -2
View File
@@ -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)
+5 -5
View File
@@ -62,11 +62,11 @@ export function openDialogWindow(type: string): BrowserWindow | null {
}
const sizes: Record<string, { width: number; height: number }> = {
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 }
+9 -2
View File
@@ -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: {