fix
This commit is contained in:
@@ -98,7 +98,7 @@ function handleClose(): void {
|
|||||||
<!-- 第二步:确认/编辑大纲 -->
|
<!-- 第二步:确认/编辑大纲 -->
|
||||||
<template v-else-if="phase === 'outline'">
|
<template v-else-if="phase === 'outline'">
|
||||||
<p>AI 已生成以下大纲,可直接编辑后开始生成:</p>
|
<p>AI 已生成以下大纲,可直接编辑后开始生成:</p>
|
||||||
<textarea v-model="outlineText" class="batch-topics-input" rows="12" />
|
<textarea v-model="outlineText" class="batch-topics-input" rows="24" />
|
||||||
<p class="batch-topics-count">共 {{ parsedTopics.length }} 个课题</p>
|
<p class="batch-topics-count">共 {{ parsedTopics.length }} 个课题</p>
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button type="button" :disabled="parsedTopics.length === 0" @click="handleStart">开始生成</button>
|
<button type="button" :disabled="parsedTopics.length === 0" @click="handleStart">开始生成</button>
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ import { createEmptyTeachingDesign, type TeachingDesign } from '../domain/teachi
|
|||||||
import TeachingDesignPage from './TeachingDesignPage.vue'
|
import TeachingDesignPage from './TeachingDesignPage.vue'
|
||||||
|
|
||||||
describe('TeachingDesignPage', () => {
|
describe('TeachingDesignPage', () => {
|
||||||
|
it('does not show an empty additional content section while editing', () => {
|
||||||
|
const design = createEmptyTeachingDesign('1.md')
|
||||||
|
|
||||||
|
const wrapper = mount(TeachingDesignPage, {
|
||||||
|
props: { design, editable: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.text()).not.toContain('附加内容')
|
||||||
|
})
|
||||||
|
|
||||||
it('adds and removes teaching process rows', async () => {
|
it('adds and removes teaching process rows', async () => {
|
||||||
const design = createEmptyTeachingDesign('1.md')
|
const design = createEmptyTeachingDesign('1.md')
|
||||||
const wrapper = mount(TeachingDesignPage, {
|
const wrapper = mount(TeachingDesignPage, {
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ function removeStep(index: number): void {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<template v-if="design.additionalContent || editable">
|
<template v-if="design.additionalContent.trim()">
|
||||||
<h2 class="section-heading">附加内容</h2>
|
<h2 class="section-heading">附加内容</h2>
|
||||||
<EditableMarkdown
|
<EditableMarkdown
|
||||||
:model-value="design.additionalContent"
|
:model-value="design.additionalContent"
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ const saveStatusLabel: Record<SaveStatus, string> = {
|
|||||||
<header class="workspace-toolbar">
|
<header class="workspace-toolbar">
|
||||||
<button type="button" data-testid="back" @click="$emit('back')">返回列表</button>
|
<button type="button" data-testid="back" @click="$emit('back')">返回列表</button>
|
||||||
<button type="button" data-testid="upload" @click="$emit('upload')">导入教案</button>
|
<button type="button" data-testid="upload" @click="$emit('upload')">导入教案</button>
|
||||||
<button type="button" data-testid="generate" @click="$emit('generate')">生成教案</button>
|
<button type="button" data-testid="generate" @click="$emit('generate')">生成一篇</button>
|
||||||
<button type="button" data-testid="batch-generate" @click="$emit('batchGenerate')">批量生成</button>
|
<button type="button" data-testid="batch-generate" @click="$emit('batchGenerate')">批量生成</button>
|
||||||
<button type="button" data-testid="print" :disabled="lessonCount === 0" @click="$emit('print')">打印整册</button>
|
<button type="button" data-testid="print" :disabled="lessonCount === 0" @click="$emit('print')">打印整册</button>
|
||||||
<button type="button" data-testid="export" :disabled="lessonCount === 0" @click="$emit('export')">导出 Markdown</button>
|
<button type="button" data-testid="export" :disabled="lessonCount === 0" @click="$emit('export')">导出 MD</button>
|
||||||
<button type="button" data-testid="clear" :disabled="lessonCount === 0" @click="$emit('clear')">清空</button>
|
<button type="button" data-testid="clear" :disabled="lessonCount === 0" @click="$emit('clear')">清空</button>
|
||||||
|
|
||||||
<span class="workspace-toolbar-count">共 {{ lessonCount }} 课</span>
|
<span class="workspace-toolbar-count">共 {{ lessonCount }} 课</span>
|
||||||
|
|||||||
@@ -18,6 +18,42 @@ function createBookWithDesign(filename = '1.md'): { data: TeachingBook; design:
|
|||||||
return { data, design }
|
return { data, design }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generatedMarkdownWithAdditionalSection(topic: string): string {
|
||||||
|
return [
|
||||||
|
`# ${topic} 教学设计`,
|
||||||
|
'| | |',
|
||||||
|
'|:---|:---|',
|
||||||
|
`| **课题** | **${topic}** |`,
|
||||||
|
'| **课时** | 1课时(40分钟) |',
|
||||||
|
'| **教学目标** | **知识目标**:理解概念。<br>**技能目标**:完成任务。<br>**素养目标**:规范表达。 |',
|
||||||
|
'| **教学重难点** | **重点**:任务流程。<br>**难点**:问题定位。 |',
|
||||||
|
'| **教学资源准备** | 机房、示例文件。 |',
|
||||||
|
'',
|
||||||
|
'## 教学过程',
|
||||||
|
'',
|
||||||
|
'| 教学环节 | 教学内容 | 教师活动 | 学生活动 | 设计意图 |',
|
||||||
|
'|:---|:---|:---|:---|:---|',
|
||||||
|
'| **1. 导入**<br>(5分钟) | 引出任务。 | **情境导入**<br>展示案例。 | **观察思考**<br>回答问题。 | 明确目标。 |',
|
||||||
|
'',
|
||||||
|
'## 板书设计',
|
||||||
|
'',
|
||||||
|
'```text',
|
||||||
|
`${topic}`,
|
||||||
|
'```',
|
||||||
|
'',
|
||||||
|
'## 教学成效与反思',
|
||||||
|
'',
|
||||||
|
'| | |',
|
||||||
|
'|:---|:---|',
|
||||||
|
'| **教学成效** | 学生完成任务。 |',
|
||||||
|
'| **教学反思** | 后续加强练习。 |',
|
||||||
|
'',
|
||||||
|
'## 附加说明',
|
||||||
|
'',
|
||||||
|
'这是模型额外生成的内容。',
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
describe('useTeachingBook', () => {
|
describe('useTeachingBook', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
@@ -140,6 +176,25 @@ describe('useTeachingBook', () => {
|
|||||||
expect(store.book.value.selectedId).toBe(store.book.value.designs[0]?.id)
|
expect(store.book.value.selectedId).toBe(store.book.value.designs[0]?.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('generateLesson discards unclassified additional content from AI output', async () => {
|
||||||
|
mockGetBook(createEmptyBook())
|
||||||
|
vi.mocked(booksApi.generateLesson).mockResolvedValue({
|
||||||
|
filename: 'css-flex.md',
|
||||||
|
markdown: generatedMarkdownWithAdditionalSection('CSS 弹性布局'),
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = useTeachingBook('b1')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const result = await store.generateLesson('CSS 弹性布局')
|
||||||
|
|
||||||
|
expect(result).toEqual({ ok: true })
|
||||||
|
expect(store.book.value.designs[0]?.additionalContent).toBe('')
|
||||||
|
expect(store.book.value.designs[0]?.warnings).not.toContainEqual(
|
||||||
|
expect.objectContaining({ code: 'unclassified-content' }),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it('generateLesson returns an error when the API call fails', async () => {
|
it('generateLesson returns an error when the API call fails', async () => {
|
||||||
mockGetBook(createEmptyBook())
|
mockGetBook(createEmptyBook())
|
||||||
vi.mocked(booksApi.generateLesson).mockRejectedValue(new Error('Deepseek 请求失败。'))
|
vi.mocked(booksApi.generateLesson).mockRejectedValue(new Error('Deepseek 请求失败。'))
|
||||||
|
|||||||
@@ -244,10 +244,18 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
|||||||
touch()
|
touch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeGeneratedAdditionalContent(design: TeachingDesign): TeachingDesign {
|
||||||
|
design.additionalContent = ''
|
||||||
|
design.warnings = design.warnings.filter((warning) => warning.code !== 'unclassified-content')
|
||||||
|
return design
|
||||||
|
}
|
||||||
|
|
||||||
async function generateLesson(topic: string): Promise<GenerateLessonResult> {
|
async function generateLesson(topic: string): Promise<GenerateLessonResult> {
|
||||||
try {
|
try {
|
||||||
const result = await booksApi.generateLesson(topic)
|
const result = await booksApi.generateLesson(topic)
|
||||||
const design = parseTeachingDesign(result.filename, result.markdown)
|
const design = removeGeneratedAdditionalContent(
|
||||||
|
parseTeachingDesign(result.filename, result.markdown),
|
||||||
|
)
|
||||||
book.value.designs.push(design)
|
book.value.designs.push(design)
|
||||||
book.value.selectedId = design.id
|
book.value.selectedId = design.id
|
||||||
touch()
|
touch()
|
||||||
@@ -299,7 +307,9 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await booksApi.generateLesson(topic)
|
const result = await booksApi.generateLesson(topic)
|
||||||
results[index] = parseTeachingDesign(result.filename, result.markdown)
|
results[index] = removeGeneratedAdditionalContent(
|
||||||
|
parseTeachingDesign(result.filename, result.markdown),
|
||||||
|
)
|
||||||
appendReadyLessons()
|
appendReadyLessons()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
firstError = error instanceof Error ? error.message : '生成失败。'
|
firstError = error instanceof Error ? error.message : '生成失败。'
|
||||||
@@ -322,7 +332,9 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
|||||||
const topic = existing.originalFilename.replace(/\.md$/i, '')
|
const topic = existing.originalFilename.replace(/\.md$/i, '')
|
||||||
try {
|
try {
|
||||||
const result = await booksApi.generateLesson(topic)
|
const result = await booksApi.generateLesson(topic)
|
||||||
const newDesign = parseTeachingDesign(result.filename, result.markdown)
|
const newDesign = removeGeneratedAdditionalContent(
|
||||||
|
parseTeachingDesign(result.filename, result.markdown),
|
||||||
|
)
|
||||||
const index = book.value.designs.findIndex((d) => d.id === id)
|
const index = book.value.designs.findIndex((d) => d.id === id)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
book.value.designs.splice(index, 1, newDesign)
|
book.value.designs.splice(index, 1, newDesign)
|
||||||
|
|||||||
@@ -229,8 +229,8 @@ input {
|
|||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
.lesson-sidebar {
|
.lesson-sidebar {
|
||||||
width: 260px;
|
width: 360px;
|
||||||
flex: 0 0 260px;
|
flex: 0 0 360px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-right: 1px solid var(--line);
|
border-right: 1px solid var(--line);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -300,7 +300,7 @@ input {
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
font-size: 16px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lesson-sidebar-remove:hover {
|
.lesson-sidebar-remove:hover {
|
||||||
|
|||||||
Reference in New Issue
Block a user