remove import teaching design feature

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 09:59:15 -06:00
parent 6e2a17f63f
commit fb0b8d11c9
3 changed files with 14 additions and 138 deletions

View File

@@ -8,7 +8,6 @@ const props = defineProps<{
}>()
defineEmits<{
upload: []
print: []
export: []
clear: []
@@ -29,7 +28,6 @@ const saveStatusLabel: Record<SaveStatus, string> = {
<template>
<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="batch-generate" @click="$emit('batchGenerate')">批量生成</button>
<button type="button" data-testid="print" :disabled="lessonCount === 0" @click="$emit('print')">打印整册</button>

View File

@@ -1,16 +1,14 @@
<script setup lang="ts">
import { ref } from 'vue'
import { type DuplicateStrategy, useTeachingBook } from '../composables/useTeachingBook'
import { useTeachingBook } from '../composables/useTeachingBook'
import type { TeachingDesign } from '../../shared/domain/teachingDesign'
import { createBookZip, downloadBlob } from '../services/zipExporter'
import A4Workspace from './A4Workspace.vue'
import BatchGenerateDialog from './BatchGenerateDialog.vue'
import FixBrokenDialog from './FixBrokenDialog.vue'
import GenerateLessonDialog from './GenerateLessonDialog.vue'
import ImportConflictDialog from './ImportConflictDialog.vue'
import LessonSidebar from './LessonSidebar.vue'
import PrintBook from './PrintBook.vue'
import UploadDropzone from './UploadDropzone.vue'
import WorkspaceToolbar from './WorkspaceToolbar.vue'
const BATCH_GENERATE_CONCURRENCY = 3
@@ -30,8 +28,6 @@ const {
selectedDesign,
hasDesigns,
warningCount,
importFiles,
detectDuplicates,
selectPage,
moveDesign,
removeDesign,
@@ -42,10 +38,7 @@ const {
regenerateLesson,
} = useTeachingBook(props.bookId)
const pendingFiles = ref<File[]>([])
const duplicateNames = ref<string[]>([])
const errorMessage = ref<string | null>(null)
const uploadRef = ref<InstanceType<typeof UploadDropzone> | null>(null)
const showGenerateDialog = ref(false)
const generateLoading = ref(false)
@@ -67,37 +60,6 @@ const fixCurrentTopic = ref('')
const fixError = ref<string | null>(null)
const fixCancelled = ref(false)
async function runImport(files: File[], strategy: DuplicateStrategy): Promise<void> {
const result = await importFiles(files, strategy)
if (result.failed.length > 0) {
errorMessage.value = `${result.failed.length} 个文件导入失败:${result.failed
.map((entry) => `${entry.filename}${entry.message}`)
.join('、')}`
}
}
async function handleFiles(files: File[]): Promise<void> {
const duplicates = detectDuplicates(files)
if (duplicates.length > 0) {
pendingFiles.value = files
duplicateNames.value = duplicates
return
}
await runImport(files, 'keep')
}
async function resolveConflict(strategy: DuplicateStrategy | 'cancel'): Promise<void> {
const files = pendingFiles.value
pendingFiles.value = []
duplicateNames.value = []
if (strategy === 'cancel') return
await runImport(files, strategy)
}
function triggerUpload(): void {
uploadRef.value?.openPicker()
}
function handlePrint(): void {
const prev = document.title
document.title = bookName.value || prev
@@ -239,13 +201,6 @@ function closeFixDialog(): void {
</div>
<template v-else>
<ImportConflictDialog
v-if="duplicateNames.length > 0"
:duplicates="duplicateNames"
@replace="resolveConflict('replace')"
@keep="resolveConflict('keep')"
@cancel="resolveConflict('cancel')"
/>
<GenerateLessonDialog
v-if="showGenerateDialog"
:loading="generateLoading"
@@ -290,7 +245,6 @@ function closeFixDialog(): void {
:warning-count="warningCount"
:save-status="saveStatus"
@back="$emit('back')"
@upload="triggerUpload"
@generate="openGenerateDialog"
@batch-generate="showBatchDialog = true"
@fix-broken="openFixDialog"
@@ -299,24 +253,19 @@ function closeFixDialog(): void {
@clear="handleClear"
/>
<UploadDropzone v-if="!hasDesigns" @files="handleFiles" />
<template v-else>
<div class="workspace-layout">
<LessonSidebar
:designs="book.designs"
:selected-id="book.selectedId"
@select="selectPage"
@remove="removeDesign"
@move="moveDesign"
/>
<A4Workspace
:selected-design="selectedDesign"
@update:design="handleDesignUpdate"
/>
</div>
<UploadDropzone ref="uploadRef" compact class="visually-hidden" @files="handleFiles" />
</template>
<div v-if="hasDesigns" class="workspace-layout">
<LessonSidebar
:designs="book.designs"
:selected-id="book.selectedId"
@select="selectPage"
@remove="removeDesign"
@move="moveDesign"
/>
<A4Workspace
:selected-design="selectedDesign"
@update:design="handleDesignUpdate"
/>
</div>
<PrintBook :designs="book.designs" />
</template>

View File

@@ -7,12 +7,9 @@ import {
} from '../../shared/domain/teachingDesign'
import * as booksApi from '../services/booksApi'
import { parseTeachingDesign } from '../services/markdownParser'
import { sortFilesNaturally } from '../services/naturalSort'
const AUTOSAVE_DELAY_MS = 300
export type DuplicateStrategy = 'replace' | 'keep'
export type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'
export type LoadStatus = 'loading' | 'loaded' | 'error'
@@ -30,12 +27,6 @@ export interface BatchGenerateLessonOptions {
onLessonComplete?: (count: number) => void
}
export interface ImportResult {
imported: number
failed: Array<{ filename: string; message: string }>
duplicates: string[]
}
export interface TeachingBookStore {
book: Ref<TeachingBook>
bookName: Ref<string>
@@ -46,8 +37,6 @@ export interface TeachingBookStore {
selectedDesign: Ref<TeachingDesign | null>
hasDesigns: Ref<boolean>
warningCount: Ref<number>
importFiles: (files: readonly File[], strategy: DuplicateStrategy) => Promise<ImportResult>
detectDuplicates: (files: readonly File[]) => string[]
selectPage: (id: DesignId) => void
moveDesign: (from: number, to: number) => void
removeDesign: (id: DesignId) => void
@@ -142,64 +131,6 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
void load()
function detectDuplicates(files: readonly File[]): string[] {
const existingNames = new Set(book.value.designs.map((design) => design.originalFilename))
return files.map((file) => file.name).filter((name) => existingNames.has(name))
}
async function importFiles(
files: readonly File[],
strategy: DuplicateStrategy,
): Promise<ImportResult> {
const markdownFiles = files.filter((file) => /\.md$/i.test(file.name))
const failed: ImportResult['failed'] = files
.filter((file) => !/\.md$/i.test(file.name))
.map((file) => ({ filename: file.name, message: '仅支持 .md 文件。' }))
const sortedFiles = sortFilesNaturally([...markdownFiles])
const duplicates: string[] = []
let imported = 0
for (const file of sortedFiles) {
try {
const text = await file.text()
const design = parseTeachingDesign(file.name, text)
const existingIndex = book.value.designs.findIndex(
(existing) => existing.originalFilename === file.name,
)
if (existingIndex !== -1) {
duplicates.push(file.name)
if (strategy === 'replace') {
book.value.designs.splice(existingIndex, 1, design)
} else {
book.value.designs.push(design)
}
} else {
book.value.designs.push(design)
}
imported++
} catch (error) {
failed.push({
filename: file.name,
message: error instanceof Error ? error.message : '解析失败。',
})
}
}
if (imported > 0 && book.value.selectedId === null && book.value.designs.length > 0) {
book.value.selectedId = book.value.designs[0]!.id
}
if (imported > 0) {
touch()
}
return { imported, failed, duplicates }
}
function selectPage(id: DesignId): void {
book.value.selectedId = id
}
@@ -359,8 +290,6 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
selectedDesign,
hasDesigns,
warningCount,
importFiles,
detectDuplicates,
selectPage,
moveDesign,
removeDesign,