feat: 评分标准保存为模板功能

This commit is contained in:
2026-07-16 13:21:47 +08:00
parent cc0f80d020
commit e84b0705e5
5 changed files with 161 additions and 23 deletions
+51 -5
View File
@@ -72,8 +72,10 @@ function getDb() {
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
course TEXT,
questions_json TEXT DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`).run()
try { db.prepare(`ALTER TABLE exam ADD COLUMN questions_json TEXT DEFAULT ''`).run() } catch (_) {}
db.prepare(`CREATE TABLE IF NOT EXISTS question (
id INTEGER PRIMARY KEY AUTOINCREMENT,
exam_id INTEGER,
@@ -161,7 +163,12 @@ export function setupIpc(win: BrowserWindow): void {
return result
} catch (err: any) {
log.error('AI grade failed:', err)
throw new Error(err.message || 'AI grading failed')
const apiMsg = err?.response?.data?.error?.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 等)')
}
throw new Error(apiMsg || err.message || 'AI grading failed')
}
})
@@ -258,17 +265,24 @@ export function setupIpc(win: BrowserWindow): void {
ipcMain.handle('template:list', () => {
try {
const db = getDb()
const exams = db.prepare('SELECT * FROM exam ORDER BY created_at DESC').all()
return exams
const exams = db.prepare('SELECT * FROM exam ORDER BY created_at DESC').all() as any[]
const result = exams.map((e: any) => {
const questions = e.questions_json ? JSON.parse(e.questions_json) : []
log.info('template:list item', { id: e.id, name: e.name, jsonLen: (e.questions_json || '').length, questionCount: questions.length })
return { ...e, questions }
})
return result
} catch {
return []
}
})
ipcMain.handle('template:save', (_event, data: { name: string; course: string }) => {
ipcMain.handle('template:save', (_event, data: { name: string; course: string; questions?: any[] }) => {
try {
const db = getDb()
const result = db.prepare('INSERT INTO exam (name, course) VALUES (?, ?)').run(data.name, data.course)
const questionsJson = JSON.stringify(data.questions || [])
log.info('template:save', { name: data.name, course: data.course, questionCount: data.questions?.length ?? 0, jsonLen: questionsJson.length })
const result = db.prepare('INSERT INTO exam (name, course, questions_json) VALUES (?, ?, ?)').run(data.name, data.course, questionsJson)
return result.lastInsertRowid
} catch (err) {
log.error('template:save error:', err)
@@ -276,6 +290,38 @@ export function setupIpc(win: BrowserWindow): void {
}
})
ipcMain.handle('template:delete', (_event, id: number) => {
try {
getDb().prepare('DELETE FROM exam WHERE id = ?').run(id)
return true
} catch {
return false
}
})
ipcMain.handle('template:apply', (_event, id: number) => {
try {
const db = getDb()
const row = db.prepare('SELECT * FROM exam WHERE id = ?').get(id) as any
if (!row) throw new Error('模板不存在')
const questions = row.questions_json ? JSON.parse(row.questions_json) : []
if (!questions.length) throw new Error('模板中没有题目')
db.prepare('DELETE FROM rubric').run()
const insert = db.prepare(
'INSERT INTO rubric (question_title, max_score, reference_answer, rubric, sort_order, reference_answer_image) VALUES (?, ?, ?, ?, ?, ?)'
)
for (let i = 0; i < questions.length; i++) {
const q = questions[i]
insert.run(q.questionTitle || '', q.maxScore ?? 100, q.referenceAnswer || '', q.rubric || '', i, q.referenceAnswerImage || '')
}
rubricNextIndex = 0
return true
} catch (err) {
log.error('template:apply error:', err)
return false
}
})
// --- Dialog child windows ---
ipcMain.handle('dialog:open', (_event, { type, data }: { type: string; data?: any }) => {
log.info('IPC: dialog:open', type)
+3 -1
View File
@@ -62,7 +62,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
template: {
list: () => ipcRenderer.invoke('template:list'),
save: (data: { name: string; course: string }) => ipcRenderer.invoke('template:save', data)
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)
},
dialog: {