fix
This commit is contained in:
@@ -98,7 +98,7 @@ function handleClose(): void {
|
||||
<!-- 第二步:确认/编辑大纲 -->
|
||||
<template v-else-if="phase === 'outline'">
|
||||
<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>
|
||||
<div class="dialog-actions">
|
||||
<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'
|
||||
|
||||
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 () => {
|
||||
const design = createEmptyTeachingDesign('1.md')
|
||||
const wrapper = mount(TeachingDesignPage, {
|
||||
|
||||
@@ -269,7 +269,7 @@ function removeStep(index: number): void {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<template v-if="design.additionalContent || editable">
|
||||
<template v-if="design.additionalContent.trim()">
|
||||
<h2 class="section-heading">附加内容</h2>
|
||||
<EditableMarkdown
|
||||
:model-value="design.additionalContent"
|
||||
|
||||
@@ -30,10 +30,10 @@ const saveStatusLabel: Record<SaveStatus, string> = {
|
||||
<header class="workspace-toolbar">
|
||||
<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="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="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>
|
||||
|
||||
<span class="workspace-toolbar-count">共 {{ lessonCount }} 课</span>
|
||||
|
||||
@@ -18,6 +18,42 @@ function createBookWithDesign(filename = '1.md'): { data: TeachingBook; 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', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@@ -140,6 +176,25 @@ describe('useTeachingBook', () => {
|
||||
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 () => {
|
||||
mockGetBook(createEmptyBook())
|
||||
vi.mocked(booksApi.generateLesson).mockRejectedValue(new Error('Deepseek 请求失败。'))
|
||||
|
||||
@@ -244,10 +244,18 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
||||
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> {
|
||||
try {
|
||||
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.selectedId = design.id
|
||||
touch()
|
||||
@@ -299,7 +307,9 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
||||
|
||||
try {
|
||||
const result = await booksApi.generateLesson(topic)
|
||||
results[index] = parseTeachingDesign(result.filename, result.markdown)
|
||||
results[index] = removeGeneratedAdditionalContent(
|
||||
parseTeachingDesign(result.filename, result.markdown),
|
||||
)
|
||||
appendReadyLessons()
|
||||
} catch (error) {
|
||||
firstError = error instanceof Error ? error.message : '生成失败。'
|
||||
@@ -322,7 +332,9 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
||||
const topic = existing.originalFilename.replace(/\.md$/i, '')
|
||||
try {
|
||||
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)
|
||||
if (index !== -1) {
|
||||
book.value.designs.splice(index, 1, newDesign)
|
||||
|
||||
@@ -229,8 +229,8 @@ input {
|
||||
|
||||
/* Sidebar */
|
||||
.lesson-sidebar {
|
||||
width: 260px;
|
||||
flex: 0 0 260px;
|
||||
width: 360px;
|
||||
flex: 0 0 360px;
|
||||
background: #fff;
|
||||
border-right: 1px solid var(--line);
|
||||
overflow-y: auto;
|
||||
@@ -300,7 +300,7 @@ input {
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
padding: 0 12px;
|
||||
font-size: 16px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.lesson-sidebar-remove:hover {
|
||||
|
||||
Reference in New Issue
Block a user