|
|
|
@ -104,7 +104,7 @@
|
|
|
|
<div class="drawer-heading">
|
|
|
|
<div class="drawer-heading">
|
|
|
|
<span class="drawer-kicker">{{ dialogMode === "view" ? "Training Task Preview" : "Training Task Editor" }}</span>
|
|
|
|
<span class="drawer-kicker">{{ dialogMode === "view" ? "Training Task Preview" : "Training Task Editor" }}</span>
|
|
|
|
<h3>{{ dialogMode === "view" ? "查看实训任务" : "编辑实训任务" }}</h3>
|
|
|
|
<h3>{{ dialogMode === "view" ? "查看实训任务" : "编辑实训任务" }}</h3>
|
|
|
|
<p>任务数量与步骤数量由系统固定,老师可维护任务显示名称、实训说明、目标、要求和步骤名称。</p>
|
|
|
|
<p>老师可维护任务显示名称、实训说明、目标、要求和步骤,并可新增或删除步骤。</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="drawer-header-actions">
|
|
|
|
<div class="drawer-header-actions">
|
|
|
|
<button type="button" class="drawer-close-button" aria-label="关闭抽屉" @click="dialogVisible = false">x</button>
|
|
|
|
<button type="button" class="drawer-close-button" aria-label="关闭抽屉" @click="dialogVisible = false">x</button>
|
|
|
|
@ -190,10 +190,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="步骤名称:">
|
|
|
|
<el-form-item label="步骤名称:">
|
|
|
|
<div class="step-row">
|
|
|
|
<div class="step-row">
|
|
|
|
<template v-for="(step, index) in taskForm.steps" :key="index">
|
|
|
|
<template v-for="(step, index) in taskForm.steps" :key="step.id">
|
|
|
|
<el-input v-model="taskForm.steps[index]" placeholder="步骤名称" />
|
|
|
|
<div class="step-item">
|
|
|
|
|
|
|
|
<el-input v-model="step.name" placeholder="步骤名称" />
|
|
|
|
|
|
|
|
<el-button v-if="!isReadonly" text type="danger" @click="removeStep(index)">删除</el-button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<span v-if="index < taskForm.steps.length - 1" class="step-arrow">>>></span>
|
|
|
|
<span v-if="index < taskForm.steps.length - 1" class="step-arrow">>>></span>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<el-button v-if="!isReadonly" plain class="add-step-button" @click="addStep">新增步骤</el-button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</el-form-item>
|
|
|
|
</el-form-item>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
@ -388,7 +392,7 @@ function createEmptyTask() {
|
|
|
|
requirements: "",
|
|
|
|
requirements: "",
|
|
|
|
materialName: "",
|
|
|
|
materialName: "",
|
|
|
|
materialUrl: "",
|
|
|
|
materialUrl: "",
|
|
|
|
steps: ["", "", "", ""],
|
|
|
|
steps: createBuiltinSteps(),
|
|
|
|
sort: 0,
|
|
|
|
sort: 0,
|
|
|
|
enabled: true,
|
|
|
|
enabled: true,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
@ -406,7 +410,7 @@ function normalizeTask(task) {
|
|
|
|
requirements: task.requirements || "",
|
|
|
|
requirements: task.requirements || "",
|
|
|
|
materialName: material.name,
|
|
|
|
materialName: material.name,
|
|
|
|
materialUrl: material.url,
|
|
|
|
materialUrl: material.url,
|
|
|
|
steps: normalizeSteps(parseArray(task.steps, ["", "", "", ""])),
|
|
|
|
steps: normalizeSteps(parseArray(task.steps, createBuiltinSteps())),
|
|
|
|
sort: task.sort || 0,
|
|
|
|
sort: task.sort || 0,
|
|
|
|
enabled: task.enabled !== false,
|
|
|
|
enabled: task.enabled !== false,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
@ -446,11 +450,28 @@ function parseArray(value, fallback) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeSteps(steps) {
|
|
|
|
function normalizeSteps(steps) {
|
|
|
|
const result = steps.filter((item) => String(item || "").trim()).slice(0, 4);
|
|
|
|
const source = Array.isArray(steps) ? steps : [];
|
|
|
|
while (result.length < 4) {
|
|
|
|
const result = source
|
|
|
|
result.push("");
|
|
|
|
.map((item, index) => {
|
|
|
|
|
|
|
|
const raw = item && typeof item === "object" ? item : {};
|
|
|
|
|
|
|
|
const name = typeof item === "object" ? String(raw.name || "").trim() : String(item || "").trim();
|
|
|
|
|
|
|
|
const kind = raw.kind === "custom" ? "custom" : index < 4 ? "builtin" : "custom";
|
|
|
|
|
|
|
|
return createStep(name, kind, raw.id);
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.filter((item) => item.name);
|
|
|
|
|
|
|
|
return result.length ? result : [createStep()];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createBuiltinSteps() {
|
|
|
|
|
|
|
|
return [1, 2, 3, 4].map((index) => createStep("", "builtin", `builtin-${index}`));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
|
|
function createStep(name = "", kind = "custom", id = "") {
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
id: id || `custom-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
|
|
|
|
|
|
name,
|
|
|
|
|
|
|
|
kind,
|
|
|
|
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openTaskDialog(task, mode) {
|
|
|
|
function openTaskDialog(task, mode) {
|
|
|
|
@ -463,7 +484,7 @@ function cloneTask(task) {
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
...task,
|
|
|
|
...task,
|
|
|
|
objectives: [...(task.objectives?.length ? task.objectives : [""])],
|
|
|
|
objectives: [...(task.objectives?.length ? task.objectives : [""])],
|
|
|
|
steps: normalizeSteps(task.steps?.length ? task.steps : ["", "", "", ""]),
|
|
|
|
steps: normalizeSteps(task.steps?.length ? task.steps : createBuiltinSteps()),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -476,6 +497,15 @@ function removeObjective(index) {
|
|
|
|
taskForm.objectives.splice(index, 1);
|
|
|
|
taskForm.objectives.splice(index, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function addStep() {
|
|
|
|
|
|
|
|
taskForm.steps.push(createStep());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function removeStep(index) {
|
|
|
|
|
|
|
|
if (taskForm.steps.length <= 1) return;
|
|
|
|
|
|
|
|
taskForm.steps.splice(index, 1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleMaterialChange(file) {
|
|
|
|
async function handleMaterialChange(file) {
|
|
|
|
const rawFile = file?.raw;
|
|
|
|
const rawFile = file?.raw;
|
|
|
|
if (!rawFile) return;
|
|
|
|
if (!rawFile) return;
|
|
|
|
@ -579,7 +609,7 @@ function buildPayload() {
|
|
|
|
background: taskForm.background,
|
|
|
|
background: taskForm.background,
|
|
|
|
objectives: JSON.stringify(taskForm.objectives.map((item) => item.trim()).filter(Boolean)),
|
|
|
|
objectives: JSON.stringify(taskForm.objectives.map((item) => item.trim()).filter(Boolean)),
|
|
|
|
requirements: taskForm.requirements,
|
|
|
|
requirements: taskForm.requirements,
|
|
|
|
steps: JSON.stringify(taskForm.steps.map((item) => item.trim()).filter(Boolean)),
|
|
|
|
steps: JSON.stringify(taskForm.steps.map((item) => ({ ...item, name: item.name.trim() })).filter((item) => item.name)),
|
|
|
|
materialName: taskForm.materialName,
|
|
|
|
materialName: taskForm.materialName,
|
|
|
|
materialUrl: taskForm.materialUrl,
|
|
|
|
materialUrl: taskForm.materialUrl,
|
|
|
|
sort: taskForm.sort,
|
|
|
|
sort: taskForm.sort,
|
|
|
|
@ -1283,13 +1313,25 @@ function buildPayload() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-row {
|
|
|
|
.step-row {
|
|
|
|
display: grid;
|
|
|
|
display: flex;
|
|
|
|
grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
gap: 12px;
|
|
|
|
gap: 12px;
|
|
|
|
align-items: center;
|
|
|
|
align-items: center;
|
|
|
|
width: 100%;
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.step-item {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
|
|
min-width: 230px;
|
|
|
|
|
|
|
|
flex: 1 1 260px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.add-step-button {
|
|
|
|
|
|
|
|
border-style: dashed;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-arrow {
|
|
|
|
.step-arrow {
|
|
|
|
color: #7ddaff;
|
|
|
|
color: #7ddaff;
|
|
|
|
font-weight: 800;
|
|
|
|
font-weight: 800;
|
|
|
|
@ -1342,7 +1384,7 @@ function buildPayload() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-row {
|
|
|
|
.step-row {
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
align-items: stretch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-arrow {
|
|
|
|
.step-arrow {
|
|
|
|
|