Merge remote-tracking branch 'origin/master' into dev-QQq

fix/exam-question-access-material-excel-delete-draft
qinzhenpen 2 weeks ago
commit 9893e33a56

@ -1,5 +1,6 @@
<template>
<div class="five">
<TrainingTaskToolbar />
<el-scrollbar :height="`${height - 100}px`">
<TrainingIntro title="五维四率模型">
<template #background>
@ -177,6 +178,7 @@ import TrainingIntro from '@/views/components/TrainingIntro.vue';
import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
import * as analysisAPI from "@/api/market-demand-mining.js";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const { proxy } = getCurrentInstance();
const height = ref(800);
const liveRoomDataWithConversion = [

@ -1,5 +1,6 @@
<template>
<div class="kano">
<TrainingTaskToolbar />
<el-scrollbar :height="`${height - 100}px`">
<TrainingIntro title="KANO模型">
<template #background>
@ -320,6 +321,7 @@ import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
import * as analysisAPI from "@/api/market-demand-mining.js";
import { ref } from "vue";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const { proxy } = getCurrentInstance();
const height = ref(800);
const form = ref({

@ -8,6 +8,7 @@
-->
<template>
<div class="market">
<TrainingTaskToolbar />
<el-scrollbar :height="`${height - 100}px`">
<TrainingIntro title="市场机会矩阵">
<template #background>
@ -215,6 +216,7 @@ import TrainingIntro from '@/views/components/TrainingIntro.vue';
import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
import * as analysisAPI from "@/api/market-demand-mining.js";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const { proxy } = getCurrentInstance();
const height = ref(800);
const salesData = [

@ -8,6 +8,7 @@
-->
<template>
<div class="traffic">
<TrainingTaskToolbar />
<el-scrollbar :height="`${height - 100}px`">
<TrainingIntro title="流量问题诊断">
<template #background>
@ -107,6 +108,7 @@ import TrainingIntro from '@/views/components/TrainingIntro.vue';
import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
import * as analysisAPI from "@/api/market-demand-mining.js";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const { proxy } = getCurrentInstance();
const height = ref(800);
const from = ref({

@ -8,6 +8,7 @@
-->
<template>
<div class="purchase-page student-training-shell">
<TrainingTaskToolbar />
<main class="purchase-core">
<section class="task-header">
<div class="task-head-main">
@ -142,6 +143,7 @@ import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
import * as analysisAPI from "@/api/market-demand-mining.js";
import * as echarts from "echarts";
import useUserStore from "@/store/modules/user";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
const briefOpen = ref(false);

@ -10,6 +10,9 @@
:ai-manual-name="resolvedAiManual.name"
:ai-manual-url="resolvedAiManual.url"
/>
<el-button class="task-toolbar-button task-toolbar-button--save" :loading="savingProgress" @click="triggerExistingAction('save')">
<el-icon><CircleCheck /></el-icon>保存当前进度
</el-button>
<el-button class="task-toolbar-button task-toolbar-button--submit" @click="triggerExistingAction('submit')">
<el-icon><CircleCheck /></el-icon>提交任务
</el-button>
@ -60,6 +63,7 @@ const loadedMaterial = ref({
});
const loadedTask = ref(null);
const exporting = ref(false);
const savingProgress = ref(false);
const resolvedTaskKey = computed(() => resolveTrainingTaskKey(route, props.taskKey));
const resolvedMaterial = computed(() => ({
name: String(props.materialName || loadedMaterial.value.name || ""),
@ -139,6 +143,11 @@ function triggerExistingAction(action) {
reset: props.onReset,
};
if (action === "save") {
saveProgress();
return;
}
if (directActions[action]) {
directActions[action]();
return;
@ -201,6 +210,50 @@ function collectLegacyFields() {
}));
}
async function saveProgress() {
if (savingProgress.value) return;
// 优先触发页面自身的保存逻辑(保留各页面的校验与状态文案)
const saveLabelPriority = [[/保存当前进度/], [/保存进度/], [/保存(?!成功|失败|中)/]];
const buttons = getCurrentTaskActionButtons();
const target = saveLabelPriority
.map((matchers) => buttons.find((button) => matchers.some((matcher) => matcher.test(button.innerText || ""))))
.find(Boolean);
if (target) {
savingProgress.value = true;
try {
target.click();
await new Promise((resolve) => setTimeout(resolve, 600));
ElMessage.success("进度已保存");
} finally {
savingProgress.value = false;
}
return;
}
// 页面没有保存按钮时保存表单快照(SAVE 不等于提交)
if (!resolvedTaskKey.value) {
ElMessage.warning("当前任务暂未配置,无法保存。");
return;
}
savingProgress.value = true;
try {
await saveStudentTrainingAnswer(resolvedTaskKey.value, {
saveAction: "SAVE",
currentStep: 1,
step1Answer: JSON.stringify({
type: "legacy-training-snapshot-v3",
route: route.path,
savedAt: new Date().toISOString(),
fields: collectLegacyFields(),
}),
});
ElMessage.success("进度已保存");
} catch {
ElMessage.error("保存失败,请稍后重试。");
} finally {
savingProgress.value = false;
}
}
async function saveLegacyTraining() {
if (!resolvedTaskKey.value) {
ElMessage.warning("当前任务暂未配置,无法提交。");
@ -263,6 +316,8 @@ function exportLegacyTraining() {
.training-task-toolbar { display:flex; flex-wrap:wrap; align-items:center; justify-content:flex-end; gap:8px; }
.task-toolbar-button { min-height:34px; height:34px; padding:0 14px; border-color:rgba(37,99,235,.35); border-radius:8px; color:#2563eb; background:#fff; font-size:13px; font-weight:600; }
.task-toolbar-button:hover { border-color:#2563eb; color:#1d4ed8; background:#eff6ff; }
.task-toolbar-button--save { border-color:rgba(43,164,113,.45); color:#2ba471; }
.task-toolbar-button--save:hover { border-color:#2ba471; color:#1f8f62; background:#f0fbf6; }
.task-toolbar-button--submit { border-color:#2563eb; color:#fff; background:#2563eb; }
.task-toolbar-button--submit:hover { border-color:#1d4ed8; color:#fff; background:#1d4ed8; }
@media (max-width:768px) { .training-task-toolbar { justify-content:stretch; } .training-task-toolbar > * { flex:1 1 auto; } }

@ -25,6 +25,10 @@
<TrainingTaskToolbar :task-key="TASK_KEY" :on-submit="saveStepThree" :on-export="exportOutcome" />
</section>
<section class="task-action-strip">
<TrainingTaskToolbar :task-key="TASK_KEY" :on-submit="saveStepThree" :on-export="exportOutcome" />
</section>
<section class="workbench">
<nav class="step-indicator" aria-label="商业可行性论证步骤">
@ -372,8 +376,7 @@ import { checkBusinessFeasibilityStepOne, checkBusinessFeasibilityStepTwo, check
import { getTrainingTaskByKey } from "@/api/trainingTask"
import { parseTrainingArray } from "@/views/training/taskKeyMap"
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue"
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue"
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const TASK_KEY = "business-feasibility"
const DEFAULT_STEPS = ["商业可行性论证", "项目初步决策", "可行性论证报告", "决策结论"]
const overviewFields = [

@ -1,5 +1,6 @@
<template>
<div class="competitive-page student-training-shell">
<TrainingTaskToolbar />
<main class="training-core">
<section class="task-header">
<h1 class="task-title">{{ taskTitle }}</h1>
@ -149,6 +150,7 @@ import { getTrainingTaskByKey } from "@/api/trainingTask";
import { parseTrainingArray } from "@/views/training/taskKeyMap";
import { getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
import { ArrowLeft, ArrowRight, CircleCheck, Download } from "@element-plus/icons-vue";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const TASK_KEY = "competitive-environment-analysis";
const { proxy } = getCurrentInstance();

@ -1,5 +1,6 @@
<template>
<div class="industry-demand-page student-training-shell">
<TrainingTaskToolbar />
<main class="training-core">
<section class="task-header">
<h1 class="task-title">{{ taskTitle }}</h1>
@ -137,6 +138,7 @@ import { getTrainingTaskByKey } from "@/api/trainingTask";
import { parseTrainingArray } from "@/views/training/taskKeyMap";
import { getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
import { ArrowLeft, ArrowRight, CircleCheck, Download } from "@element-plus/icons-vue";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const TASK_KEY = "industry-demand-analysis";
const { proxy } = getCurrentInstance();

@ -8,6 +8,7 @@
-->
<template>
<div class="rgzn_top app-container2 student-training-shell legacy-training-page" v-loading="loading" element-loading-text="Loading..." :element-loading-spinner="svg" element-loading-svg-view-box="-10, -10, 50, 50" element-loading-background="rgba(122, 122, 122, 0.8)">
<TrainingTaskToolbar />
<div class="top_item">
<div>
<template v-for="(item, index) in tabsList" :key="item.id">
@ -59,6 +60,7 @@ import { getAssetsFile, isModuleUnique } from "@/utils/index.js";
import useAlgorithmStore from "@/store/modules/algorithm.js";
import { getUserInfo } from "@/utils/auth";
import { onMounted } from "vue";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const { proxy } = getCurrentInstance();
const algorithmStore = useAlgorithmStore();
const userInfo = JSON.parse(getUserInfo());

@ -1,5 +1,6 @@
<template>
<div class="market-selection-page student-training-shell">
<TrainingTaskToolbar />
<main class="training-core">
<section class="task-header">
<div class="task-head-main">
@ -228,6 +229,7 @@ import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vu
import { getTrainingTaskByKey } from "@/api/trainingTask";
import { parseTrainingArray } from "@/views/training/taskKeyMap";
import { getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const TASK_KEY = "market-opportunity-selection";
const { proxy } = getCurrentInstance();

@ -8,6 +8,7 @@
-->
<template>
<div class="marketing app-container student-training-shell legacy-training-page">
<TrainingTaskToolbar />
<TrainingIntro title="四象限法则">
<template #background>
<p>
@ -103,6 +104,7 @@ import popModel from "@/views/components/popModal.vue";
import * as analysisAPI from "@/api/market-demand-mining.js";
import * as echarts from "echarts";
import { onMounted } from "vue";
import TrainingTaskToolbar from "@/views/components/TrainingTaskToolbar.vue";
const textarea = ref("");
const dialogVisible = ref(false);
const { proxy } = getCurrentInstance();

Loading…
Cancel
Save