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<{ defineEmits<{
upload: []
print: [] print: []
export: [] export: []
clear: [] clear: []
@@ -29,7 +28,6 @@ const saveStatusLabel: Record<SaveStatus, string> = {
<template> <template>
<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="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>

View File

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

View File

@@ -7,12 +7,9 @@ import {
} from '../../shared/domain/teachingDesign' } from '../../shared/domain/teachingDesign'
import * as booksApi from '../services/booksApi' import * as booksApi from '../services/booksApi'
import { parseTeachingDesign } from '../services/markdownParser' import { parseTeachingDesign } from '../services/markdownParser'
import { sortFilesNaturally } from '../services/naturalSort'
const AUTOSAVE_DELAY_MS = 300 const AUTOSAVE_DELAY_MS = 300
export type DuplicateStrategy = 'replace' | 'keep'
export type SaveStatus = 'idle' | 'saving' | 'saved' | 'error' export type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'
export type LoadStatus = 'loading' | 'loaded' | 'error' export type LoadStatus = 'loading' | 'loaded' | 'error'
@@ -30,12 +27,6 @@ export interface BatchGenerateLessonOptions {
onLessonComplete?: (count: number) => void onLessonComplete?: (count: number) => void
} }
export interface ImportResult {
imported: number
failed: Array<{ filename: string; message: string }>
duplicates: string[]
}
export interface TeachingBookStore { export interface TeachingBookStore {
book: Ref<TeachingBook> book: Ref<TeachingBook>
bookName: Ref<string> bookName: Ref<string>
@@ -46,8 +37,6 @@ export interface TeachingBookStore {
selectedDesign: Ref<TeachingDesign | null> selectedDesign: Ref<TeachingDesign | null>
hasDesigns: Ref<boolean> hasDesigns: Ref<boolean>
warningCount: Ref<number> warningCount: Ref<number>
importFiles: (files: readonly File[], strategy: DuplicateStrategy) => Promise<ImportResult>
detectDuplicates: (files: readonly File[]) => string[]
selectPage: (id: DesignId) => void selectPage: (id: DesignId) => void
moveDesign: (from: number, to: number) => void moveDesign: (from: number, to: number) => void
removeDesign: (id: DesignId) => void removeDesign: (id: DesignId) => void
@@ -142,64 +131,6 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
void load() 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 { function selectPage(id: DesignId): void {
book.value.selectedId = id book.value.selectedId = id
} }
@@ -359,8 +290,6 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
selectedDesign, selectedDesign,
hasDesigns, hasDesigns,
warningCount, warningCount,
importFiles,
detectDuplicates,
selectPage, selectPage,
moveDesign, moveDesign,
removeDesign, removeDesign,