fix: guard AI assessment until saved

dev-QQq
chenyuan 3 weeks ago
parent 0d2cc2aac0
commit 19e1310ca8

@ -38,7 +38,8 @@
</div>
</div>
<el-button class="assessment-button" :loading="assessmentLoading" :disabled="!currentTaskKey" @click="requestEvaluation">AI</el-button>
<p v-if="currentTaskKey && !hasSavedAnswer" class="assessment-hint"> AI </p>
<el-button class="assessment-button" :loading="assessmentLoading" :disabled="!currentTaskKey || !hasSavedAnswer || assessmentLoading" @click="requestEvaluation">AI</el-button>
</section>
</aside>
</template>
@ -84,6 +85,7 @@ const props = defineProps({
default: () => [],
},
taskKey: { type: String, default: "" },
hasSavedAnswer: { type: Boolean, default: true },
});
const emit = defineEmits(["request-assist", "request-evaluation"]);
@ -135,7 +137,10 @@ async function requestAssist() {
async function requestEvaluation() {
emit("request-evaluation", reviewQuestion.value);
if (!currentTaskKey.value) return;
if (!currentTaskKey.value || !props.hasSavedAnswer) {
internalEvalMessage.value = "请先至少填写并保存一项实训内容,再请求 AI 助评。";
return;
}
assessmentLoading.value = true;
try {
await requestAiTrainingAssessment(currentTaskKey.value);
@ -143,6 +148,8 @@ async function requestEvaluation() {
internalScoreVisible.value = reports.value.score !== null;
internalScore.value = reports.value.score ?? "--";
internalEvalMessage.value = reports.value.assessmentReport || props.evalMessage;
} catch (error) {
internalEvalMessage.value = "AI 助评暂时不可用,请稍后重试。";
} finally { assessmentLoading.value = false; }
}
</script>
@ -228,6 +235,7 @@ async function requestEvaluation() {
.reference-block { width: 100%; border: 0; background: transparent; padding: 14px 0 0; text-align: left; cursor: pointer; }
.reference-block:hover strong { color: #77eaff; }
.assessment-button { width: 100%; margin-top: 14px; min-height: 44px; font-weight: 800; }
.assessment-hint { margin: 12px 0 -2px; color: #9fcbe2; font-size: 13px; line-height: 1.6; }
.output-glow {
position: absolute;

@ -157,6 +157,7 @@
<div class="training-ai-tools">
<TrainingAiSidebar
:task-key="pageKey"
:has-saved-answer="hasSavedAnswer"
:study-tip="`当前任务建议:围绕“${page.title}”先明确产品对象,再按任务流程逐步补全分析内容。`"
:progress-items="progressItems"
/>
@ -201,6 +202,7 @@ const buildInitialProcessRows = () =>
const form = ref(buildInitialForm());
const workRows = ref(buildInitialRows());
const processRows = ref(buildInitialProcessRows());
const hasSavedAnswer = ref(false);
const hasAnyAnswer = computed(() =>
Object.values(form.value).some(hasMeaningfulAnswer) ||
workRows.value.some((row) => hasMeaningfulAnswer(row.answer)) ||
@ -225,6 +227,7 @@ watch(
form.value = buildInitialForm();
workRows.value = buildInitialRows();
processRows.value = buildInitialProcessRows();
hasSavedAnswer.value = false;
await loadSavedAnswer();
},
{ immediate: true }
@ -352,6 +355,9 @@ async function loadSavedAnswer() {
try {
const res = await getStudentTrainingAnswer(pageKey.value);
const answer = res?.data;
hasSavedAnswer.value = Boolean(
answer && [answer.step1Answer, answer.step2Answer, answer.step3Answer, answer.step4Answer, answer.dynamicStepAnswers].some(hasMeaningfulAnswer)
);
if (answer?.currentStep && Number(answer.currentStep) > 0) {
currentStep.value = Number(answer.currentStep);
}
@ -377,6 +383,7 @@ async function saveAnswer(saveAction = "SAVE") {
saving.value = true;
try {
await saveStudentTrainingAnswer(pageKey.value, buildAnswerPayload(saveAction));
hasSavedAnswer.value = hasAnyAnswer.value;
proxy?.$modal?.msgSuccess(saveAction === "SUBMIT" ? "提交成功" : "已保存");
} catch (error) {
proxy?.$modal?.msgError?.(saveAction === "SUBMIT" ? "提交失败" : "保存失败");

@ -5,14 +5,14 @@ const path = require("path");
const root = path.resolve(__dirname, "..");
const api = fs.readFileSync(path.join(root, "src/api/aiTrainingEvaluation.js"), "utf8");
const component = fs.readFileSync(path.join(root, "src/views/components/TrainingAiEvaluation.vue"), "utf8");
const sidebar = fs.readFileSync(path.join(root, "src/views/components/TrainingAiSidebar.vue"), "utf8");
const page = fs.readFileSync(path.join(root, "src/views/training/GenericTrainingPage.vue"), "utf8");
const { normalizeEvaluationReports } = require(path.join(root, "src/views/components/trainingAiEvaluationReport.cjs"));
assert(/`\/api\/student\/training-tasks\/\$\{taskKey\}\/ai-evaluation`/.test(api), "evaluation API should use the student task endpoint");
assert(/\/help/.test(api) && /\/assessment/.test(api), "evaluation API should expose help and assessment operations");
assert(/TrainingAiEvaluation/.test(page) && /:has-any-answer="hasAnyAnswer"/.test(page), "generic training page should pass meaningful answer state to AI evaluation");
assert(/TrainingAiSidebar/.test(page) && /:has-saved-answer="hasSavedAnswer"/.test(page), "generic training page should pass persisted answer state to the AI sidebar");
assert(/form\.value/.test(page) && /workRows\.value/.test(page) && /processRows\.value/.test(page), "hasAnyAnswer should inspect all supported answer shapes");
assert(/<TrainingAiEvaluation[\s\S]*<TrainingAiSidebar/.test(page), "AI evaluation should render before the existing AI sidebar");
assert(/onMounted\(/.test(component) && /watch\(\s*\(\)\s*=>\s*props\.taskKey/.test(component), "AI evaluation should load on mount and task changes");
assert(/AI助学/.test(component) && /AI助评/.test(component), "AI evaluation should offer separate help and assessment controls");
@ -56,3 +56,7 @@ assert.strictEqual(malformedPayload.assessmentReport, "{also not valid JSON", "m
assert.strictEqual(malformedPayload.score, null, "malformed assessment JSON should not produce a score");
assert.deepStrictEqual(malformedPayload.scoreCriteria, [], "malformed assessment JSON should not produce criteria");
assert(/retry/.test(component) || /重试/.test(component), "failed AI operations should expose a retry action");
assert(/hasSavedAnswer/.test(page) && /:has-saved-answer="hasSavedAnswer"/.test(page), "generic training page should distinguish persisted answers before enabling AI assessment");
assert(/hasSavedAnswer/.test(sidebar), "AI sidebar should accept persisted-answer state");
assert(/!props\.hasSavedAnswer/.test(sidebar), "AI assessment should be disabled until a saved answer exists");
assert(/catch \(error\)/.test(sidebar), "AI sidebar should handle assessment failures without an unhandled rejection");

Loading…
Cancel
Save