From f2b31bf553daf28f6de6d3e809ca23a7f4bacabe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B2=85?= <907037276@qq.com> Date: Thu, 3 Sep 2026 15:01:32 +0800 Subject: [PATCH] feat: export AI teaching plans as Word --- src/views/analysis-of-students/dialogue.vue | 43 ++++++++++++++++----- tests/ai-lesson-plan-export.static.test.cjs | 18 +++++++++ 2 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 tests/ai-lesson-plan-export.static.test.cjs diff --git a/src/views/analysis-of-students/dialogue.vue b/src/views/analysis-of-students/dialogue.vue index 7013815..a524d61 100644 --- a/src/views/analysis-of-students/dialogue.vue +++ b/src/views/analysis-of-students/dialogue.vue @@ -17,9 +17,9 @@ -
- - +
+ +
@@ -47,6 +47,7 @@ import { getAssetsFile } from "@/utils/index.js"; import * as AI from "@/api/AI.js"; import "md-editor-v3/lib/style.css"; import * as XLSX from "xlsx"; +import { marked } from "marked"; import useUserStore from "@/store/modules/user"; const { proxy } = getCurrentInstance(); const userStore = useUserStore(); @@ -146,9 +147,31 @@ const scrollToBottom = () => { scrollEl.scrollTop = scrollEl.scrollHeight; } }; -const copyTextSuccess = () => { - proxy.$modal.msgSuccess("复制成功"); -}; +function exportTeachingPlan(item) { + if (!item?.content?.trim()) { + proxy.$message.warning("暂无可导出的教案内容"); + return; + } + const documentHtml = `

AI 实训教案

${marked.parse(item.content, { gfm: true, breaks: true })}`; + const blob = new Blob(["\ufeff", documentHtml], { type: "application/msword;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = `AI实训教案-${new Date().toISOString().slice(0, 10)}.doc`; + link.click(); + URL.revokeObjectURL(url); + proxy.$modal.msgSuccess("教案已导出"); +} // 中断对话 const gInterrupt = () => { if (abortController) { @@ -373,7 +396,7 @@ onUnmounted(() => { gap: 1rem; position: relative; padding-right: 10px; - .copy { + .answer-export { position: absolute; top: 5px; right: 23px; @@ -385,7 +408,7 @@ onUnmounted(() => { } } &:hover { - .copy { + .answer-export { display: block; cursor: pointer; } @@ -408,7 +431,7 @@ onUnmounted(() => { .qustionCon { position: relative; padding-left: 10px; - .copy { + .answer-export { position: absolute; top: 5px; left: 8px; @@ -420,7 +443,7 @@ onUnmounted(() => { } } &:hover { - .copy { + .answer-export { display: block; cursor: pointer; } diff --git a/tests/ai-lesson-plan-export.static.test.cjs b/tests/ai-lesson-plan-export.static.test.cjs new file mode 100644 index 0000000..0152d31 --- /dev/null +++ b/tests/ai-lesson-plan-export.static.test.cjs @@ -0,0 +1,18 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); + +const page = fs.readFileSync( + path.resolve(__dirname, "../src/views/analysis-of-students/dialogue.vue"), + "utf8", +); + +assert.match(page, /content="导出教案"/); +assert.match(page, /icon-class="导出"/); +assert.doesNotMatch(page, /v-copyText="item\.content"/); +assert.match(page, /function exportTeachingPlan\(item\)/); +assert.match(page, /marked\.parse\(item\.content/); +assert.match(page, /table-layout:\s*fixed/); +assert.match(page, /th, td\s*\{[^}]*word-break:\s*break-word/); + +console.log("AI lesson plan export static checks passed");