优化题库代码

This commit is contained in:
李志强 2025-11-18 17:11:18 +08:00
parent ff9a181c99
commit 5fddba8a30
13 changed files with 138 additions and 116 deletions

1
.gitignore vendored
View File

@ -24,6 +24,7 @@ dist
dist-ssr dist-ssr
*.local *.local
server/uploads server/uploads
server/conf/
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

View File

@ -0,0 +1,3 @@
<template>
<div class="p-4">Certificate</div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div class="p-4">Create Exam Placeholder</div>
</template>

View File

@ -124,7 +124,8 @@ import '@wangeditor/editor/dist/css/style.css'
const props = defineProps({ const props = defineProps({
visible: { type: Boolean, default: false }, visible: { type: Boolean, default: false },
mode: { type: String, default: 'create' }, mode: { type: String, default: 'create' },
model: { type: Object, default: null } model: { type: Object, default: null },
bankId: { type: [Number, String], default: null }
}) })
const emit = defineEmits(['close', 'saved']) const emit = defineEmits(['close', 'saved'])
@ -380,6 +381,7 @@ function handleSubmit() {
options: showOptions.value ? form.options.map(o => ({ key: o.key, label: o.label, content: o.content })) : [], options: showOptions.value ? form.options.map(o => ({ key: o.key, label: o.label, content: o.content })) : [],
answer: isMultipleType.value ? [...multiAnswer.value] : form.answer answer: isMultipleType.value ? [...multiAnswer.value] : form.answer
} }
if (props.bankId) payload.bank_id = props.bankId
emit('saved', payload) emit('saved', payload)
} catch (e) { } catch (e) {
ElMessage.error('保存失败') ElMessage.error('保存失败')

View File

@ -4,7 +4,7 @@
<!-- 顶部工具栏返回 + 标题 --> <!-- 顶部工具栏返回 + 标题 -->
<div class="topbar"> <div class="topbar">
<el-button :icon="Back" @click="emit('back')">返回</el-button> <el-button :icon="Back" @click="emit('back')">返回</el-button>
<div class="title">题目列表</div> <div class="title">{{ bankName }}</div>
</div> </div>
<el-divider /> <el-divider />
@ -16,36 +16,38 @@
<el-input v-model="filters.keyword" placeholder="请输入题干关键词" clearable <el-input v-model="filters.keyword" placeholder="请输入题干关键词" clearable
@keyup.enter="handleSearch" /> @keyup.enter="handleSearch" />
</el-form-item> </el-form-item>
<el-form-item label="题型"> <el-divider />
<el-select v-model="filters.type" placeholder="请选择题型" clearable style="width: 200px;"> <div class="tools">
<el-option v-for="opt in typeOptions" :key="opt.dict_value" :label="opt.dict_label" <el-form-item>
:value="opt.dict_value" /> <el-button type="primary" :icon="Plus" @click="handleCreate">新建题目</el-button>
</el-select> <el-button type="primary" :icon="Plus" @click="handleSmartCreate">智能新建</el-button>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button> <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
<el-button @click="handleReset">重置</el-button> <el-button @click="handleReset">重置</el-button>
<el-button :icon="Refresh" @click="fetchList">刷新</el-button>
<el-button :icon="Upload" @click="handleBatchImport">批量导入</el-button>
</el-form-item> </el-form-item>
</div>
</el-form> </el-form>
</div> </div>
</div> </div>
<div class="header"> <!-- <div class="header">
<!-- 操作栏 -->
<div class="toolbar"> <div class="toolbar">
<el-button type="primary" :icon="Plus" @click="handleCreate">新建题目</el-button>
<el-button type="primary" :icon="Plus" @click="handleSmartCreate">智能新建</el-button>
<div style="flex:1" /> <div style="flex:1" />
<el-button :icon="Refresh" @click="fetchList">刷新</el-button>
<el-button :icon="Upload" @click="handleBatchImport">批量导入</el-button>
</div> </div>
</div> </div> -->
<!-- 列表 --> <!-- 列表 -->
<el-table v-loading="loading" :data="list" stripe border> <el-table v-loading="loading" :data="list" stripe border>
<el-table-column prop="id" label="ID" width="80" align="center" /> <el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="question_title" label="题目" min-width="260" show-overflow-tooltip /> <el-table-column prop="question_title" label="题目" min-width="260" show-overflow-tooltip>
<template #default="{ row }">
<div v-html="row.question_title"></div>
</template>
</el-table-column>
<el-table-column prop="question_type" label="题型" width="120" align="center"> <el-table-column prop="question_type" label="题型" width="120" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-tag>{{ getTypeLabel(row.question_type) }}</el-tag> <el-tag>{{ getTypeLabel(row.question_type) }}</el-tag>
@ -72,7 +74,7 @@
</div> </div>
<!-- 弹窗仅用于创建/编辑题目与查看详情 --> <!-- 弹窗仅用于创建/编辑题目与查看详情 -->
<EditDialog :visible="editVisible" :mode="editMode" :model="editModel" @close="editVisible = false" <EditDialog :visible="editVisible" :mode="editMode" :model="editModel" :bank-id="props.bankId" @close="editVisible = false"
@saved="handleSaved" /> @saved="handleSaved" />
<DetailDialog :visible="viewVisible" :model="viewModel" @close="viewVisible = false" /> <DetailDialog :visible="viewVisible" :model="viewModel" @close="viewVisible = false" />
@ -86,15 +88,11 @@
</div> </div>
<el-divider content-position="left">上传 Excel 文件</el-divider> <el-divider content-position="left">上传 Excel 文件</el-divider>
<el-upload <el-upload class="upload-block" :show-file-list="false" accept=".xls,.xlsx" :auto-upload="false"
class="upload-block" :on-change="handleImportFileChange">
:show-file-list="false"
accept=".xls,.xlsx"
:auto-upload="false"
:on-change="handleImportFileChange"
>
<el-button type="primary" :loading="importing">选择 Excel 文件</el-button> <el-button type="primary" :loading="importing">选择 Excel 文件</el-button>
<span style="margin-left: 12px; color:#888; font-size:12px;">表头需包含question_title, question_type, score, question_analysis, option_a, option_b, ... , answer</span> <span style="margin-left: 12px; color:#888; font-size:12px;">表头需包含question_title, question_type, score,
question_analysis, option_a, option_b, ... , answer</span>
</el-upload> </el-upload>
<template #footer> <template #footer>
@ -109,12 +107,8 @@
<el-form label-width="80px"> <el-form label-width="80px">
<el-form-item label="题型"> <el-form-item label="题型">
<el-select v-model="smartType" placeholder="请选择题型" style="width: 260px;"> <el-select v-model="smartType" placeholder="请选择题型" style="width: 260px;">
<el-option <el-option v-for="opt in typeOptions" :key="opt.dict_value" :label="opt.dict_label"
v-for="opt in typeOptions" :value="String(opt.dict_value)" />
:key="opt.dict_value"
:label="opt.dict_label"
:value="String(opt.dict_value)"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -122,13 +116,8 @@
<p style="margin: 4px 0 8px; font-size: 13px; color: #666;"> <p style="margin: 4px 0 8px; font-size: 13px; color: #666;">
请粘贴完整的题目文本包含题干选项答案和解析例如 请粘贴完整的题目文本包含题干选项答案和解析例如
</p> </p>
<el-input <el-input v-model="smartRawText" type="textarea" :rows="10" placeholder="在这里粘贴题目文本..."
v-model="smartRawText" style="margin-top: 12px;" />
type="textarea"
:rows="10"
placeholder="在这里粘贴题目文本..."
style="margin-top: 12px;"
/>
<template #footer> <template #footer>
<div class="dlg-actions"> <div class="dlg-actions">
@ -152,9 +141,6 @@ import { getExamQuestions, getExamQuestionDetail, createExamQuestion, updateExam
import EditDialog from './edit.vue' import EditDialog from './edit.vue'
import DetailDialog from './detail.vue' import DetailDialog from './detail.vue'
const props = defineProps({
bankId: { type: [Number, String], default: null },
})
const emit = defineEmits(['back', 'saved']) const emit = defineEmits(['back', 'saved'])
const auth = useAuthStore() const auth = useAuthStore()
@ -173,6 +159,11 @@ const typeDictMap = computed(() => {
return m return m
}) })
const props = defineProps({
bankId: { type: [Number, String], default: null },
bankName: { type: String, default: '' },
})
// //
function handleDownloadTemplate() { function handleDownloadTemplate() {
// 使 // 使
@ -451,7 +442,7 @@ function handleImportFileChange(file) {
return return
} }
importing.value = true importing.value = true
const res = await batchImportExamQuestions({ items }) const res = await batchImportExamQuestions({ bank_id: props.bankId || undefined, items })
if (res && res.code === 0) { if (res && res.code === 0) {
const data = res.data || {} const data = res.data || {}
ElMessage.success(`导入完成:成功 ${data.success ?? 0} 条,失败 ${data.failed ?? 0}`) ElMessage.success(`导入完成:成功 ${data.success ?? 0} 条,失败 ${data.failed ?? 0}`)
@ -668,11 +659,16 @@ onMounted(async () => {
.topbar .title { .topbar .title {
font-weight: 600; font-weight: 600;
font-size: 16px; font-size: 20px;
} }
.search-bar { .search-bar {
/* margin-bottom: 12px; */ /* margin-bottom: 12px; */
.tools {
display: flex;
justify-content: space-between;
}
} }
.toolbar { .toolbar {

View File

@ -1,53 +1,27 @@
<template> <template>
<div class="examination-questions"> <div class="examination-questions">
<template v-if="!examListVisible"> <template v-if="!examListVisible">
<!-- 搜索栏 --> <!-- 搜索栏 -->
<div class="search-bar"> <div class="search-bar">
<el-form :inline="true" :model="filters"> <el-form :inline="true" :model="filters">
<div class="form-fields">
<el-form-item label="关键词">
<el-input v-model="filters.keyword" placeholder="请输入题目关键词" clearable
@keyup.enter="handleSearch" />
</el-form-item>
<el-form-item label="题型">
<el-select v-model="filters.type" placeholder="请选择题型" clearable>
<el-option v-for="opt in dictTypeOptions" :key="opt.dict_value" :label="opt.dict_label"
:value="opt.dict_value" />
</el-select>
</el-form-item>
</div>
<el-divider />
<div class="form-actions"> <div class="form-actions">
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button> <div class="left">
<el-button @click="handleReset">重置</el-button>
</div>
</el-form>
</div>
<!-- 操作栏 -->
<div class="toolbar">
<el-button type="primary" :icon="Plus" @click="handleCreate">新建题库</el-button> <el-button type="primary" :icon="Plus" @click="handleCreate">新建题库</el-button>
<div class="toolright"> </div>
<div class="right">
<el-button @click="handleBatchImport" :icon="Upload">批量导入</el-button> <el-button @click="handleBatchImport" :icon="Upload">批量导入</el-button>
<el-button @click="handleRefresh" :icon="Refresh">刷新</el-button> <el-button @click="handleRefresh" :icon="Refresh">刷新</el-button>
</div> </div>
</div> </div>
</el-form>
</div>
<!-- 题库列表 --> <!-- 题库列表 -->
<el-table v-loading="loading" :data="bankList" stripe border style="width: 100%"> <el-table v-loading="loading" :data="bankList" stripe border style="width: 100%">
<el-table-column prop="id" label="ID" width="80" align="center" /> <el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="bank_name" label="题库名称" min-width="200" show-overflow-tooltip /> <el-table-column prop="bank_name" label="题库名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="bank_desc" label="描述" min-width="200" show-overflow-tooltip /> <el-table-column prop="bank_desc" label="描述" min-width="200" show-overflow-tooltip />
<!-- <el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="String(row.status)==='0' ? 'success' : 'info'">{{ String(row.status)==='0' ? '启用' : '禁用' }}</el-tag>
</template>
</el-table-column> -->
<!-- <el-table-column prop="create_time" label="创建时间" width="180" align="center">
<template #default="{ row }">
{{ formatTime(row.create_time) }}
</template>
</el-table-column> -->
<el-table-column label="操作" width="200" fixed="right" align="center"> <el-table-column label="操作" width="200" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button> <el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
@ -65,11 +39,12 @@
</div> </div>
<EditDialog :visible="editVisible" :mode="editMode" :model="editModel" @close="editVisible = false" <EditDialog :visible="editVisible" :mode="editMode" :model="editModel" @close="editVisible = false"
@saved="handleSaved" /> @saved="handleSaved" />
<EditBanks :visible="bankVisible" :mode="bankMode" :model="bankModel" @close="bankVisible = false" @saved="handleBankSaved" /> <EditBanks :visible="bankVisible" :mode="bankMode" :model="bankModel" @close="bankVisible = false"
@saved="handleBankSaved" />
<DetailDialog :visible="viewVisible" :model="viewModel" @close="viewVisible = false" /> <DetailDialog :visible="viewVisible" :model="viewModel" @close="viewVisible = false" />
</template> </template>
<template v-else> <template v-else>
<ExamList :bank-id="examListBankId" @back="examListVisible = false" /> <ExamList :bank-id="examListBankId" :bank-name="examListBankName" @back="examListVisible = false" />
</template> </template>
</div> </div>
</template> </template>
@ -111,6 +86,7 @@ const bankModel = ref(null);
const examListVisible = ref(false); const examListVisible = ref(false);
const examListBankId = ref(null); const examListBankId = ref(null);
const examListBankName = ref('');
const auth = useAuthStore(); const auth = useAuthStore();
@ -156,7 +132,7 @@ const handleBatchImport = () => {
ElMessage.info('批量导入功能开发中'); ElMessage.info('批量导入功能开发中');
}; };
const handleView = async (row) => {}; const handleView = async (row) => { };
const handleRefresh = () => { const handleRefresh = () => {
fetchBankList(); fetchBankList();
@ -171,6 +147,7 @@ const handleBankSaved = async (payload) => {
} }
ElMessage.success('保存成功'); ElMessage.success('保存成功');
bankVisible.value = false; bankVisible.value = false;
await fetchBankList();
} catch (e) { } catch (e) {
ElMessage.error('保存失败'); ElMessage.error('保存失败');
} }
@ -184,6 +161,7 @@ const handleEdit = (row) => {
const handleAdd = (row) => { const handleAdd = (row) => {
examListBankId.value = row?.id || null; examListBankId.value = row?.id || null;
examListBankName.value = row?.bank_name || '';
examListVisible.value = true; examListVisible.value = true;
}; };
@ -301,6 +279,7 @@ onMounted(() => {
.form-actions { .form-actions {
display: flex; display: flex;
gap: 12px; gap: 12px;
justify-content: space-between;
} }
} }

View File

@ -0,0 +1,3 @@
<template>
<div class="p-4">Examinee</div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div class="p-4">Practice</div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div class="p-4">Training</div>
</template>

View File

@ -6,7 +6,7 @@ runmode = dev
# MySQL - 远程连接配置 # MySQL - 远程连接配置
mysqluser = gotest mysqluser = gotest
mysqlpass = 2nZhRdMPCNZrdzsd mysqlpass = 2nZhRdMPCNZrdzsd
mysqlurls = 192.168.31.10:3306 mysqlurls = 212.64.112.158:3388
mysqldb = gotest mysqldb = gotest
# ORM配置 # ORM配置

View File

@ -24,6 +24,8 @@ func (c *ExamQuestionController) GetList() {
tenantId, _ := c.Ctx.Input.GetData("tenantId").(int) tenantId, _ := c.Ctx.Input.GetData("tenantId").(int)
keyword := c.GetString("keyword") keyword := c.GetString("keyword")
typeStr := c.GetString("type") typeStr := c.GetString("type")
bankId, _ := c.GetInt64("bank_id", 0)
var qtype *int8 var qtype *int8
if typeStr != "" { if typeStr != "" {
if iv, err := strconv.Atoi(typeStr); err == nil { if iv, err := strconv.Atoi(typeStr); err == nil {
@ -31,12 +33,15 @@ func (c *ExamQuestionController) GetList() {
qtype = &v qtype = &v
} }
} }
page, _ := c.GetInt("page", 1) page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 10) pageSize, _ := c.GetInt("pageSize", 10)
list, total, err := services.GetExamQuestions(services.QuestionListParams{ list, total, err := services.GetExamQuestions(services.QuestionListParams{
TenantId: tenantId, TenantId: tenantId,
Keyword: keyword, Keyword: keyword,
QuestionType: qtype, QuestionType: qtype,
BankId: bankId,
Page: page, Page: page,
PageSize: pageSize, PageSize: pageSize,
}) })
@ -45,7 +50,7 @@ func (c *ExamQuestionController) GetList() {
c.ServeJSON() c.ServeJSON()
return return
} }
// build minimal DTO to limit fields in response
items := make([]map[string]interface{}, 0, len(list)) items := make([]map[string]interface{}, 0, len(list))
for _, q := range list { for _, q := range list {
if q == nil { if q == nil {
@ -59,6 +64,7 @@ func (c *ExamQuestionController) GetList() {
"score": q.Score, "score": q.Score,
}) })
} }
c.Data["json"] = map[string]interface{}{ c.Data["json"] = map[string]interface{}{
"code": 0, "code": 0,
"message": "ok", "message": "ok",
@ -101,6 +107,7 @@ func (c *ExamQuestionController) Create() {
QuestionAnalysis string `json:"question_analysis"` QuestionAnalysis string `json:"question_analysis"`
Options []map[string]string `json:"options"` Options []map[string]string `json:"options"`
Answer interface{} `json:"answer"` Answer interface{} `json:"answer"`
BankId int64 `json:"bank_id"`
} }
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil { if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数错误: " + err.Error(), "data": nil} c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数错误: " + err.Error(), "data": nil}
@ -115,6 +122,9 @@ func (c *ExamQuestionController) Create() {
Score: payload.Score, Score: payload.Score,
Status: 1, Status: 1,
} }
if payload.BankId > 0 {
q.BankId = payload.BankId
}
var opts []models.ExamQuestionOption var opts []models.ExamQuestionOption
for _, o := range payload.Options { for _, o := range payload.Options {
opts = append(opts, models.ExamQuestionOption{OptionLabel: o["label"], OptionContent: o["content"]}) opts = append(opts, models.ExamQuestionOption{OptionLabel: o["label"], OptionContent: o["content"]})
@ -149,6 +159,7 @@ func (c *ExamQuestionController) Create() {
func (c *ExamQuestionController) BatchCreate() { func (c *ExamQuestionController) BatchCreate() {
tenantId, _ := c.Ctx.Input.GetData("tenantId").(int) tenantId, _ := c.Ctx.Input.GetData("tenantId").(int)
var payload struct { var payload struct {
BankId int64 `json:"bank_id"`
Items []struct { Items []struct {
QuestionTitle string `json:"question_title"` QuestionTitle string `json:"question_title"`
QuestionType int8 `json:"question_type"` QuestionType int8 `json:"question_type"`
@ -182,6 +193,9 @@ func (c *ExamQuestionController) BatchCreate() {
Score: item.Score, Score: item.Score,
Status: 1, Status: 1,
} }
if payload.BankId > 0 {
q.BankId = payload.BankId
}
var opts []models.ExamQuestionOption var opts []models.ExamQuestionOption
for _, o := range item.Options { for _, o := range item.Options {
opts = append(opts, models.ExamQuestionOption{OptionLabel: o["label"], OptionContent: o["content"]}) opts = append(opts, models.ExamQuestionOption{OptionLabel: o["label"], OptionContent: o["content"]})
@ -251,6 +265,7 @@ func (c *ExamQuestionController) Update() {
QuestionAnalysis string `json:"question_analysis"` QuestionAnalysis string `json:"question_analysis"`
Options []map[string]string `json:"options"` Options []map[string]string `json:"options"`
Answer interface{} `json:"answer"` Answer interface{} `json:"answer"`
BankId int64 `json:"bank_id"`
} }
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil { if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil {
c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数错误: " + err.Error(), "data": nil} c.Data["json"] = map[string]interface{}{"code": 1, "message": "请求参数错误: " + err.Error(), "data": nil}
@ -258,6 +273,9 @@ func (c *ExamQuestionController) Update() {
return return
} }
q := &models.ExamQuestion{QuestionType: payload.QuestionType, QuestionTitle: payload.QuestionTitle, QuestionAnalysis: payload.QuestionAnalysis, Score: payload.Score} q := &models.ExamQuestion{QuestionType: payload.QuestionType, QuestionTitle: payload.QuestionTitle, QuestionAnalysis: payload.QuestionAnalysis, Score: payload.Score}
if payload.BankId > 0 {
q.BankId = payload.BankId
}
var opts []models.ExamQuestionOption var opts []models.ExamQuestionOption
for _, o := range payload.Options { for _, o := range payload.Options {
opts = append(opts, models.ExamQuestionOption{OptionLabel: o["label"], OptionContent: o["content"]}) opts = append(opts, models.ExamQuestionOption{OptionLabel: o["label"], OptionContent: o["content"]})

View File

@ -10,6 +10,7 @@ import (
type ExamQuestion struct { type ExamQuestion struct {
Id int64 `orm:"column(id);auto" json:"id"` Id int64 `orm:"column(id);auto" json:"id"`
TenantId int `orm:"column(tenant_id)" json:"tenant_id"` TenantId int `orm:"column(tenant_id)" json:"tenant_id"`
BankId int64 `orm:"column(bank_id);null" json:"bank_id"`
QuestionType int8 `orm:"column(question_type)" json:"question_type"` QuestionType int8 `orm:"column(question_type)" json:"question_type"`
QuestionTitle string `orm:"column(question_title);size(1000)" json:"question_title"` QuestionTitle string `orm:"column(question_title);size(1000)" json:"question_title"`
QuestionAnalysis string `orm:"column(question_analysis);size(2000);null" json:"question_analysis"` QuestionAnalysis string `orm:"column(question_analysis);size(2000);null" json:"question_analysis"`

View File

@ -13,6 +13,7 @@ type QuestionListParams struct {
TenantId int TenantId int
Keyword string Keyword string
QuestionType *int8 QuestionType *int8
BankId int64
Page int Page int
PageSize int PageSize int
} }
@ -42,6 +43,9 @@ func GetExamQuestions(params QuestionListParams) ([]*models.ExamQuestion, int64,
if params.QuestionType != nil { if params.QuestionType != nil {
qs = qs.Filter("question_type", *params.QuestionType) qs = qs.Filter("question_type", *params.QuestionType)
} }
if params.BankId > 0 {
qs = qs.Filter("bank_id", params.BankId)
}
total, err := qs.Count() total, err := qs.Count()
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@ -121,7 +125,13 @@ func UpdateExamQuestion(tenantId int, id int64, q *models.ExamQuestion, options
existing.QuestionTitle = q.QuestionTitle existing.QuestionTitle = q.QuestionTitle
existing.QuestionAnalysis = q.QuestionAnalysis existing.QuestionAnalysis = q.QuestionAnalysis
existing.Score = q.Score existing.Score = q.Score
if _, err := o.Update(&existing, "QuestionType", "QuestionTitle", "QuestionAnalysis", "Score"); err != nil { // 可选更新题库归属
fields := []string{"QuestionType", "QuestionTitle", "QuestionAnalysis", "Score"}
if q.BankId > 0 {
existing.BankId = q.BankId
fields = append(fields, "BankId")
}
if _, err := o.Update(&existing, fields...); err != nil {
return err return err
} }
// soft delete old options and answer // soft delete old options and answer