|
|
|
@ -344,6 +344,10 @@
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 第一步选择题:编辑入口固定在第一个步骤行旁,仅三个白名单任务显示 -->
|
|
|
|
|
|
|
|
<StepOneQuestionBankPicker v-model:visible="bankPickerVisible" :load-questions="loadBankQuestions" :existing="taskForm.stepOneQuestions" @confirm="handleBankConfirm" />
|
|
|
|
|
|
|
|
<StepOneChoiceQuestionEditor v-model:visible="choiceEditorVisible" v-model="taskForm.stepOneQuestions" :task-name="taskForm.title" :load-questions="loadBankQuestions" />
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 步骤数量与顺序固定,仅允许修改名称 -->
|
|
|
|
<!-- 步骤数量与顺序固定,仅允许修改名称 -->
|
|
|
|
<el-form-item label="步骤名称:">
|
|
|
|
<el-form-item label="步骤名称:">
|
|
|
|
<div class="step-list">
|
|
|
|
<div class="step-list">
|
|
|
|
@ -357,6 +361,13 @@
|
|
|
|
<div class="step-content">
|
|
|
|
<div class="step-content">
|
|
|
|
<span class="step-number">{{ index + 1 }}</span>
|
|
|
|
<span class="step-number">{{ index + 1 }}</span>
|
|
|
|
<el-input v-model="step.name" placeholder="步骤名称" />
|
|
|
|
<el-input v-model="step.name" placeholder="步骤名称" />
|
|
|
|
|
|
|
|
<template v-if="index === 0 && supportsStepOneChoice(taskForm.taskKey)">
|
|
|
|
|
|
|
|
<el-button v-if="!isReadonly" type="primary" class="step-question-btn"
|
|
|
|
|
|
|
|
@click="choiceEditorVisible = true">
|
|
|
|
|
|
|
|
编辑题目{{ stepOneQuestionCount ? `(${stepOneQuestionCount}题)` : "" }}
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
<span v-else class="step-question-tag">已配置 {{ stepOneQuestionCount }} 道题</span>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@ -397,6 +408,10 @@ import { listTrainingTasks, listClassTrainingTasks, saveClassTrainingTask, resto
|
|
|
|
import { createStudentDemoSession } from "@/api/studentDemo"
|
|
|
|
import { createStudentDemoSession } from "@/api/studentDemo"
|
|
|
|
import * as indexApi from "@/api/teacher"
|
|
|
|
import * as indexApi from "@/api/teacher"
|
|
|
|
import useUserStore from "@/store/modules/user"
|
|
|
|
import useUserStore from "@/store/modules/user"
|
|
|
|
|
|
|
|
import StepOneChoiceQuestionEditor from "@/views/components/StepOneChoiceQuestionEditor.vue"
|
|
|
|
|
|
|
|
import StepOneQuestionBankPicker from "@/views/components/StepOneQuestionBankPicker.vue"
|
|
|
|
|
|
|
|
import { parseStepOneChoiceQuestions, supportsStepOneChoiceQuestions } from "@/utils/stepOneChoiceQuestions"
|
|
|
|
|
|
|
|
import { getQuestions } from "@/api/exam"
|
|
|
|
|
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
|
const userStore = useUserStore()
|
|
|
|
const userStore = useUserStore()
|
|
|
|
@ -408,6 +423,8 @@ const classesLoading = ref(false)
|
|
|
|
const saving = ref(false)
|
|
|
|
const saving = ref(false)
|
|
|
|
const restoring = ref(false)
|
|
|
|
const restoring = ref(false)
|
|
|
|
const uploadingField = ref("")
|
|
|
|
const uploadingField = ref("")
|
|
|
|
|
|
|
|
const choiceEditorVisible = ref(false)
|
|
|
|
|
|
|
|
const bankPickerVisible = ref(false)
|
|
|
|
// 四类资料槽位:key 对应 taskForm 的 `${key}Name` / `${key}Url` 字段
|
|
|
|
// 四类资料槽位:key 对应 taskForm 的 `${key}Name` / `${key}Url` 字段
|
|
|
|
const materialSlots = [
|
|
|
|
const materialSlots = [
|
|
|
|
{ key: "material", label: "案例资料" },
|
|
|
|
{ key: "material", label: "案例资料" },
|
|
|
|
@ -423,6 +440,7 @@ const dialogMode = ref("view")
|
|
|
|
const taskForm = reactive(createEmptyTask())
|
|
|
|
const taskForm = reactive(createEmptyTask())
|
|
|
|
const currentUserId = computed(() => userStore.userInfo.userId)
|
|
|
|
const currentUserId = computed(() => userStore.userInfo.userId)
|
|
|
|
const isReadonly = computed(() => dialogMode.value === "view")
|
|
|
|
const isReadonly = computed(() => dialogMode.value === "view")
|
|
|
|
|
|
|
|
const stepOneQuestionCount = computed(() => taskForm.stepOneQuestions?.length || 0)
|
|
|
|
const projects = computed(() => [...new Set(tasks.value.map((task) => task.project).filter(Boolean))])
|
|
|
|
const projects = computed(() => [...new Set(tasks.value.map((task) => task.project).filter(Boolean))])
|
|
|
|
const projectOptions = computed(() => {
|
|
|
|
const projectOptions = computed(() => {
|
|
|
|
return projects.value.map((project) => ({
|
|
|
|
return projects.value.map((project) => ({
|
|
|
|
@ -566,12 +584,28 @@ function createEmptyTask() {
|
|
|
|
guideManualUrl: "",
|
|
|
|
guideManualUrl: "",
|
|
|
|
aiManualName: "",
|
|
|
|
aiManualName: "",
|
|
|
|
aiManualUrl: "",
|
|
|
|
aiManualUrl: "",
|
|
|
|
|
|
|
|
stepOneQuestions: [],
|
|
|
|
steps: createBuiltinSteps(),
|
|
|
|
steps: createBuiltinSteps(),
|
|
|
|
sort: 0,
|
|
|
|
sort: 0,
|
|
|
|
enabled: true,
|
|
|
|
enabled: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function supportsStepOneChoice(taskKey) {
|
|
|
|
|
|
|
|
return supportsStepOneChoiceQuestions(taskKey)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 本校题库单选题(平台题 + 本校教师题,按学校过滤)
|
|
|
|
|
|
|
|
async function loadBankQuestions() {
|
|
|
|
|
|
|
|
const res = await getQuestions({ scope: "school", type: "SINGLE" })
|
|
|
|
|
|
|
|
return res?.data || []
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleBankConfirm(questions) {
|
|
|
|
|
|
|
|
taskForm.stepOneQuestions = [...(taskForm.stepOneQuestions || []), ...questions]
|
|
|
|
|
|
|
|
proxy?.$modal?.msgSuccess(`已从题库添加 ${questions.length} 道题,点击“保存配置”后生效`)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function hasMaterial(slotKey) {
|
|
|
|
function hasMaterial(slotKey) {
|
|
|
|
return isRealMaterial(taskForm[`${slotKey}Name`], taskForm[`${slotKey}Url`])
|
|
|
|
return isRealMaterial(taskForm[`${slotKey}Name`], taskForm[`${slotKey}Url`])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -601,6 +635,7 @@ function normalizeTask(task) {
|
|
|
|
guideManualUrl: guideManual.url,
|
|
|
|
guideManualUrl: guideManual.url,
|
|
|
|
aiManualName: aiManual.name,
|
|
|
|
aiManualName: aiManual.name,
|
|
|
|
aiManualUrl: aiManual.url,
|
|
|
|
aiManualUrl: aiManual.url,
|
|
|
|
|
|
|
|
stepOneQuestions: parseStepOneChoiceQuestions(task.stepOneQuestions),
|
|
|
|
steps: normalizeSteps(parseArray(task.steps, createBuiltinSteps())),
|
|
|
|
steps: normalizeSteps(parseArray(task.steps, createBuiltinSteps())),
|
|
|
|
sort: task.sort || 0,
|
|
|
|
sort: task.sort || 0,
|
|
|
|
enabled: task.enabled !== false,
|
|
|
|
enabled: task.enabled !== false,
|
|
|
|
@ -833,6 +868,7 @@ function buildPayload() {
|
|
|
|
guideManualUrl: taskForm.guideManualUrl,
|
|
|
|
guideManualUrl: taskForm.guideManualUrl,
|
|
|
|
aiManualName: taskForm.aiManualName,
|
|
|
|
aiManualName: taskForm.aiManualName,
|
|
|
|
aiManualUrl: taskForm.aiManualUrl,
|
|
|
|
aiManualUrl: taskForm.aiManualUrl,
|
|
|
|
|
|
|
|
stepOneQuestions: JSON.stringify(taskForm.stepOneQuestions || []),
|
|
|
|
sort: taskForm.sort,
|
|
|
|
sort: taskForm.sort,
|
|
|
|
enabled: taskForm.enabled,
|
|
|
|
enabled: taskForm.enabled,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1581,6 +1617,20 @@ function buildPayload() {
|
|
|
|
.el-input {
|
|
|
|
.el-input {
|
|
|
|
flex: 1;
|
|
|
|
flex: 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.step-question-btn {
|
|
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
|
|
font-size: var(--fs-body);
|
|
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.step-question-tag {
|
|
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
|
|
color: var(--text-muted);
|
|
|
|
|
|
|
|
font-size: var(--fs-tag);
|
|
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.drawer-footer {
|
|
|
|
.drawer-footer {
|
|
|
|
|