You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/views/components/TrainingLegacyActions.vue

187 lines
5.6 KiB
Vue

<template>
<div class="training-legacy-actions training-actions">
<el-button class="training-action training-action--save" :loading="saving" @click="saveProgress">保存当前进度</el-button>
<el-button class="training-action training-action--submit" :loading="saving" @click="submitAnswer">提交任务</el-button>
<el-button class="training-action training-action--reset" :loading="saving" @click="resetAnswer">重新实训</el-button>
<el-button class="training-action training-action--export" @click="exportOutcome">导出实训成果</el-button>
</div>
</template>
<script setup>
import { deleteStudentTrainingAnswer, getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
import { resolveTrainingTaskKey } from "@/views/training/taskKeyMap";
const props = defineProps({
taskKey: {
type: String,
default: "",
},
});
const route = useRoute();
const { proxy } = getCurrentInstance();
const saving = ref(false);
const resolvedTaskKey = computed(() => resolveTrainingTaskKey(route, props.taskKey));
onMounted(() => {
loadSavedAnswer();
});
watch(
() => resolvedTaskKey.value,
() => loadSavedAnswer()
);
async function loadSavedAnswer() {
if (!resolvedTaskKey.value) return;
try {
const res = await getStudentTrainingAnswer(resolvedTaskKey.value);
const saved = parseSavedPayload(res?.data?.step1Answer);
if (saved?.fields?.length) {
await nextTick();
applyFields(saved.fields);
}
} catch (error) {
// Old pages remain usable when no saved answer exists.
}
}
async function saveProgress() {
await persist("SAVE", "保存成功");
}
async function submitAnswer() {
await persist("SUBMIT", "提交成功");
}
async function resetAnswer() {
if (!resolvedTaskKey.value || saving.value) return;
saving.value = true;
try {
await deleteStudentTrainingAnswer(resolvedTaskKey.value);
clearFields();
proxy?.$modal?.msgSuccess?.("已清空");
} catch (error) {
await saveStudentTrainingAnswer(resolvedTaskKey.value, {
saveAction: "RESET",
currentStep: 1,
});
clearFields();
proxy?.$modal?.msgSuccess?.("已清空");
} finally {
saving.value = false;
}
}
async function persist(saveAction, message) {
if (!resolvedTaskKey.value || saving.value) return;
saving.value = true;
try {
const fields = collectFields();
await saveStudentTrainingAnswer(resolvedTaskKey.value, {
saveAction,
currentStep: 1,
step1Answer: JSON.stringify({
type: "legacy-dom-form",
route: route.path,
savedAt: new Date().toISOString(),
fields,
}),
});
proxy?.$modal?.msgSuccess?.(message);
} finally {
saving.value = false;
}
}
function getScope() {
return document.querySelector(".legacy-training-page") || document.querySelector(".training-step-area") || document.body;
}
function collectFields() {
const scope = getScope();
const controls = Array.from(scope.querySelectorAll("input, textarea, select"))
.filter((el) => !el.closest(".training-ai-sidebar") && !el.closest(".training-legacy-actions"));
return controls.map((el) => ({
selector: buildStableSelector(el),
type: el.type || el.tagName.toLowerCase(),
value: getControlValue(el),
checked: Boolean(el.checked),
}));
}
function applyFields(fields) {
fields.forEach((field) => {
const el = findControl(field.selector);
if (!el) return;
if (el.type === "checkbox" || el.type === "radio") {
el.checked = Boolean(field.checked);
el.dispatchEvent(new Event("change", { bubbles: true }));
return;
}
el.value = field.value ?? "";
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
});
}
function clearFields() {
collectFields().forEach((field) => {
const el = findControl(field.selector);
if (!el) return;
if (el.type === "checkbox" || el.type === "radio") {
el.checked = false;
el.dispatchEvent(new Event("change", { bubbles: true }));
return;
}
el.value = "";
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
});
}
function buildStableSelector(el) {
const scope = getScope();
const controls = Array.from(scope.querySelectorAll("input, textarea, select"))
.filter((item) => !item.closest(".training-ai-sidebar") && !item.closest(".training-legacy-actions"));
return `${el.tagName.toLowerCase()}:${controls.indexOf(el)}`;
}
function findControl(selector) {
const [tag, indexText] = String(selector || "").split(":");
const index = Number(indexText);
if (!tag || Number.isNaN(index)) return null;
const scope = getScope();
const controls = Array.from(scope.querySelectorAll("input, textarea, select"))
.filter((el) => !el.closest(".training-ai-sidebar") && !el.closest(".training-legacy-actions"));
return controls[index] && controls[index].tagName.toLowerCase() === tag ? controls[index] : null;
}
function getControlValue(el) {
if (el.type === "checkbox" || el.type === "radio") {
return el.value;
}
return el.value;
}
function parseSavedPayload(value) {
if (!value) return null;
try {
return JSON.parse(value);
} catch (error) {
return null;
}
}
function exportOutcome() {
const blob = new Blob([JSON.stringify(collectFields(), null, 2)], { type: "application/json;charset=utf-8" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = `${route.meta?.title || "实训"}-实训成果.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
}
</script>