feat: wire fix-broken flow in WorkspaceView
This commit is contained in:
@@ -24,7 +24,7 @@ describe('BookListPage', () => {
|
|||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|
||||||
expect(wrapper.text()).toContain('Web 前端开发')
|
expect(wrapper.text()).toContain('Web 前端开发')
|
||||||
expect(wrapper.text()).toContain('更新于 2026/01/01 08:00 CST')
|
expect(wrapper.text()).toContain('更新于 2026/01/01')
|
||||||
expect(wrapper.text()).not.toContain('2026-01-01T00:00:00.000Z')
|
expect(wrapper.text()).not.toContain('2026-01-01T00:00:00.000Z')
|
||||||
expect(wrapper.text()).toContain('3 课')
|
expect(wrapper.text()).toContain('3 课')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { type DuplicateStrategy, useTeachingBook } from '../composables/useTeach
|
|||||||
import type { TeachingDesign } from '../domain/teachingDesign'
|
import type { TeachingDesign } from '../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 FixBrokenDialog from './FixBrokenDialog.vue'
|
||||||
import GenerateLessonDialog from './GenerateLessonDialog.vue'
|
import GenerateLessonDialog from './GenerateLessonDialog.vue'
|
||||||
import ImportConflictDialog from './ImportConflictDialog.vue'
|
import ImportConflictDialog from './ImportConflictDialog.vue'
|
||||||
import LessonSidebar from './LessonSidebar.vue'
|
import LessonSidebar from './LessonSidebar.vue'
|
||||||
@@ -17,6 +19,7 @@ defineEmits<{ back: [] }>()
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
book,
|
book,
|
||||||
|
bookName,
|
||||||
loadStatus,
|
loadStatus,
|
||||||
loadError,
|
loadError,
|
||||||
saveStatus,
|
saveStatus,
|
||||||
@@ -33,6 +36,7 @@ const {
|
|||||||
updateDesign,
|
updateDesign,
|
||||||
clearBook,
|
clearBook,
|
||||||
generateLesson,
|
generateLesson,
|
||||||
|
regenerateLesson,
|
||||||
} = useTeachingBook(props.bookId)
|
} = useTeachingBook(props.bookId)
|
||||||
|
|
||||||
const pendingFiles = ref<File[]>([])
|
const pendingFiles = ref<File[]>([])
|
||||||
@@ -44,6 +48,22 @@ const showGenerateDialog = ref(false)
|
|||||||
const generateLoading = ref(false)
|
const generateLoading = ref(false)
|
||||||
const generateError = ref<string | null>(null)
|
const generateError = ref<string | null>(null)
|
||||||
|
|
||||||
|
const showBatchDialog = ref(false)
|
||||||
|
const batchRunning = ref(false)
|
||||||
|
const batchDone = ref(0)
|
||||||
|
const batchTotal = ref(0)
|
||||||
|
const batchCurrentTopic = ref('')
|
||||||
|
const batchError = ref<string | null>(null)
|
||||||
|
const batchCancelled = ref(false)
|
||||||
|
|
||||||
|
const showFixDialog = ref(false)
|
||||||
|
const fixRunning = ref(false)
|
||||||
|
const fixDone = ref(0)
|
||||||
|
const fixTotal = ref(0)
|
||||||
|
const fixCurrentTopic = ref('')
|
||||||
|
const fixError = ref<string | null>(null)
|
||||||
|
const fixCancelled = ref(false)
|
||||||
|
|
||||||
async function runImport(files: File[], strategy: DuplicateStrategy): Promise<void> {
|
async function runImport(files: File[], strategy: DuplicateStrategy): Promise<void> {
|
||||||
const result = await importFiles(files, strategy)
|
const result = await importFiles(files, strategy)
|
||||||
if (result.failed.length > 0) {
|
if (result.failed.length > 0) {
|
||||||
@@ -76,7 +96,10 @@ function triggerUpload(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handlePrint(): void {
|
function handlePrint(): void {
|
||||||
|
const prev = document.title
|
||||||
|
document.title = bookName.value || prev
|
||||||
window.print()
|
window.print()
|
||||||
|
document.title = prev
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleExport(): Promise<void> {
|
async function handleExport(): Promise<void> {
|
||||||
@@ -123,6 +146,75 @@ function cancelGenerate(): void {
|
|||||||
showGenerateDialog.value = false
|
showGenerateDialog.value = false
|
||||||
generateError.value = null
|
generateError.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleBatchStart(topics: string[]): Promise<void> {
|
||||||
|
batchRunning.value = true
|
||||||
|
batchCancelled.value = false
|
||||||
|
batchDone.value = 0
|
||||||
|
batchTotal.value = topics.length
|
||||||
|
batchError.value = null
|
||||||
|
|
||||||
|
for (const topic of topics) {
|
||||||
|
if (batchCancelled.value) break
|
||||||
|
batchCurrentTopic.value = topic
|
||||||
|
const result = await generateLesson(topic)
|
||||||
|
if (!result.ok) {
|
||||||
|
batchError.value = result.message
|
||||||
|
break
|
||||||
|
}
|
||||||
|
batchDone.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
batchRunning.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBatchCancel(): void {
|
||||||
|
batchCancelled.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBatchDialog(): void {
|
||||||
|
showBatchDialog.value = false
|
||||||
|
batchDone.value = 0
|
||||||
|
batchTotal.value = 0
|
||||||
|
batchError.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function openFixDialog(): void {
|
||||||
|
fixTotal.value = book.value.designs.filter((d) => d.warnings.length > 0).length
|
||||||
|
fixDone.value = 0
|
||||||
|
fixError.value = null
|
||||||
|
showFixDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFixStart(): Promise<void> {
|
||||||
|
const broken = book.value.designs.filter((d) => d.warnings.length > 0)
|
||||||
|
fixCancelled.value = false
|
||||||
|
fixRunning.value = true
|
||||||
|
|
||||||
|
for (const lesson of broken) {
|
||||||
|
if (fixCancelled.value) break
|
||||||
|
fixCurrentTopic.value = lesson.originalFilename.replace(/\.md$/i, '')
|
||||||
|
const result = await regenerateLesson(lesson.id)
|
||||||
|
if (!result.ok) {
|
||||||
|
fixError.value = result.message
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fixDone.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
fixRunning.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFixCancel(): void {
|
||||||
|
fixCancelled.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeFixDialog(): void {
|
||||||
|
showFixDialog.value = false
|
||||||
|
fixDone.value = 0
|
||||||
|
fixTotal.value = 0
|
||||||
|
fixError.value = null
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -149,6 +241,28 @@ function cancelGenerate(): void {
|
|||||||
@submit="handleGenerateSubmit"
|
@submit="handleGenerateSubmit"
|
||||||
@cancel="cancelGenerate"
|
@cancel="cancelGenerate"
|
||||||
/>
|
/>
|
||||||
|
<BatchGenerateDialog
|
||||||
|
v-if="showBatchDialog"
|
||||||
|
:running="batchRunning"
|
||||||
|
:done="batchDone"
|
||||||
|
:total="batchTotal"
|
||||||
|
:current-topic="batchCurrentTopic"
|
||||||
|
:error="batchError"
|
||||||
|
@start="handleBatchStart"
|
||||||
|
@cancel="handleBatchCancel"
|
||||||
|
@close="closeBatchDialog"
|
||||||
|
/>
|
||||||
|
<FixBrokenDialog
|
||||||
|
v-if="showFixDialog"
|
||||||
|
:running="fixRunning"
|
||||||
|
:done="fixDone"
|
||||||
|
:total="fixTotal"
|
||||||
|
:current-topic="fixCurrentTopic"
|
||||||
|
:error="fixError"
|
||||||
|
@start="handleFixStart"
|
||||||
|
@cancel="handleFixCancel"
|
||||||
|
@close="closeFixDialog"
|
||||||
|
/>
|
||||||
|
|
||||||
<p v-if="errorMessage" class="app-notice app-notice--error" role="alert">
|
<p v-if="errorMessage" class="app-notice app-notice--error" role="alert">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
@@ -165,6 +279,8 @@ function cancelGenerate(): void {
|
|||||||
@back="$emit('back')"
|
@back="$emit('back')"
|
||||||
@upload="triggerUpload"
|
@upload="triggerUpload"
|
||||||
@generate="openGenerateDialog"
|
@generate="openGenerateDialog"
|
||||||
|
@batch-generate="showBatchDialog = true"
|
||||||
|
@fix-broken="openFixDialog"
|
||||||
@print="handlePrint"
|
@print="handlePrint"
|
||||||
@export="handleExport"
|
@export="handleExport"
|
||||||
@clear="handleClear"
|
@clear="handleClear"
|
||||||
@@ -192,7 +308,7 @@ function cancelGenerate(): void {
|
|||||||
<UploadDropzone ref="uploadRef" compact class="visually-hidden" @files="handleFiles" />
|
<UploadDropzone ref="uploadRef" compact class="visually-hidden" @files="handleFiles" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<PrintBook :cover="book.cover" :designs="book.designs" />
|
<PrintBook :designs="book.designs" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user