459 lines
16 KiB
Markdown
459 lines
16 KiB
Markdown
|
|
# Data Process Create Wizard Implementation Plan
|
|||
|
|
|
|||
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|||
|
|
|
|||
|
|
**Goal:** 将 `/data-process/create` 实现为固定四步向导,并在第二步提供“右侧选择切片、左侧自动定位并高亮原文”的可编辑对照体验。
|
|||
|
|
|
|||
|
|
**Architecture:** `DataProcessCreateView.vue` 只负责向导状态、步骤切换和跨步骤数据;每个步骤拆成独立 Vue 组件。源文定位和切片生成由纯 TypeScript 模块负责,第二步组件只消费偏移范围并同步滚动、高亮和编辑状态。现有 Vue 3、Element Plus、SCSS 和 Font Awesome 继续使用,不引入新依赖。
|
|||
|
|
|
|||
|
|
**Tech Stack:** Vue 3.5、TypeScript 5.7、Vite 6、Element Plus 2.9、SCSS、Node.js 回归脚本、`vue-tsc`。
|
|||
|
|
|
|||
|
|
## Global Constraints
|
|||
|
|
|
|||
|
|
- 顶部固定四步:`创建任务`、`数据预览`、`开始生成`、`结果编辑与保存`。
|
|||
|
|
- 结构化和非结构化类型不得改变步骤数量。
|
|||
|
|
- 第二步桌面端左侧约 58% 为只读源文件,右侧约 42% 为切片或记录列表及编辑器。
|
|||
|
|
- 点击右侧条目时,左侧必须定位并高亮 `sourceStart` 到 `sourceEnd` 的原始范围。
|
|||
|
|
- 编辑切片不得改写源文件;来源映射始终指向初始原文。
|
|||
|
|
- 每一步只能有一个主操作,不得同时出现“下一步”和“开始生成”等竞争动作。
|
|||
|
|
- 页面继续使用现有全局白色页面画布,不新增整页嵌套白卡。
|
|||
|
|
- 不增加第三方依赖。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 1: 建立四步向导回归测试
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `frontend/scripts/regression-data-process-wizard.mjs`
|
|||
|
|
- Modify: `frontend/package.json`
|
|||
|
|
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: Vue SFC 源码、`@vue/compiler-sfc`、`@vue/compiler-dom`。
|
|||
|
|
- Produces: `npm run test:data-process-wizard`,验证固定步骤、组件边界、对照定位标记和底部唯一主操作。
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 写入当前实现必然失败的结构回归检查**
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
import assert from 'node:assert/strict'
|
|||
|
|
import { readFile } from 'node:fs/promises'
|
|||
|
|
import path from 'node:path'
|
|||
|
|
import { fileURLToPath } from 'node:url'
|
|||
|
|
import { parse as parseSfc } from '@vue/compiler-sfc'
|
|||
|
|
|
|||
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|||
|
|
const viewSource = await readFile(
|
|||
|
|
path.resolve(scriptDir, '../src/views/data-process/DataProcessCreateView.vue'),
|
|||
|
|
'utf8',
|
|||
|
|
)
|
|||
|
|
const previewSource = await readFile(
|
|||
|
|
path.resolve(scriptDir, '../src/views/data-process/create/PreviewCompareStep.vue'),
|
|||
|
|
'utf8',
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert.match(viewSource, /const WIZARD_STEPS = \[/)
|
|||
|
|
for (const title of ['创建任务', '数据预览', '开始生成', '结果编辑与保存']) {
|
|||
|
|
assert.ok(viewSource.includes(`title: '${title}'`), `缺少固定步骤:${title}`)
|
|||
|
|
}
|
|||
|
|
assert.doesNotMatch(viewSource, /all\.filter|steps\s*=\s*computed/)
|
|||
|
|
assert.match(previewSource, /class="source-viewer"/)
|
|||
|
|
assert.match(previewSource, /class="preview-workspace"/)
|
|||
|
|
assert.match(previewSource, /scrollIntoView/)
|
|||
|
|
assert.match(previewSource, /sourceStart/)
|
|||
|
|
assert.match(previewSource, /sourceEnd/)
|
|||
|
|
|
|||
|
|
const { descriptor } = parseSfc(viewSource)
|
|||
|
|
assert.ok(descriptor.template?.content.includes('TaskSetupStep'))
|
|||
|
|
assert.ok(descriptor.template?.content.includes('PreviewCompareStep'))
|
|||
|
|
assert.ok(descriptor.template?.content.includes('GenerationStep'))
|
|||
|
|
assert.ok(descriptor.template?.content.includes('ResultEditorStep'))
|
|||
|
|
|
|||
|
|
console.log('数据处理四步向导回归检查通过')
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 在 `package.json` 注册命令**
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"scripts": {
|
|||
|
|
"test:data-process-wizard": "node scripts/regression-data-process-wizard.mjs"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 运行测试并确认失败原因正确**
|
|||
|
|
|
|||
|
|
Run: `cd frontend && npm run test:data-process-wizard`
|
|||
|
|
|
|||
|
|
Expected: FAIL,首先因 `PreviewCompareStep.vue` 不存在或固定步骤断言不成立而失败。
|
|||
|
|
|
|||
|
|
### Task 2: 建立向导类型、草稿状态和来源映射模型
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `frontend/src/views/data-process/create/types.ts`
|
|||
|
|
- Create: `frontend/src/views/data-process/create/previewModel.ts`
|
|||
|
|
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: 上传文件解析出的字符串。
|
|||
|
|
- Produces: `ProcessType`、`StepId`、`PreviewItem`、`ResultItem`、`DataProcessDraft`;`buildPreviewItems(sourceText, processType)` 和 `sourceLines(sourceText)`。
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 在回归脚本增加模型文件和关键字段断言**
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
const typesSource = await readFile(
|
|||
|
|
path.resolve(scriptDir, '../src/views/data-process/create/types.ts'),
|
|||
|
|
'utf8',
|
|||
|
|
)
|
|||
|
|
const modelSource = await readFile(
|
|||
|
|
path.resolve(scriptDir, '../src/views/data-process/create/previewModel.ts'),
|
|||
|
|
'utf8',
|
|||
|
|
)
|
|||
|
|
for (const field of ['sourceStart', 'sourceEnd', 'originalContent', 'editedContent']) {
|
|||
|
|
assert.ok(typesSource.includes(field), `PreviewItem 缺少字段:${field}`)
|
|||
|
|
}
|
|||
|
|
assert.match(modelSource, /export function buildPreviewItems/)
|
|||
|
|
assert.match(modelSource, /export function sourceLines/)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 定义稳定类型**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export type ProcessType = 'structured' | 'unstructured'
|
|||
|
|
export type StepId = 'create' | 'preview' | 'generate' | 'results'
|
|||
|
|
|
|||
|
|
export interface PreviewItem {
|
|||
|
|
id: string
|
|||
|
|
originalContent: string
|
|||
|
|
editedContent: string
|
|||
|
|
sourceStart: number | null
|
|||
|
|
sourceEnd: number | null
|
|||
|
|
sourceStartLine: number | null
|
|||
|
|
sourceEndLine: number | null
|
|||
|
|
tokenCount: number
|
|||
|
|
status: 'original' | 'modified' | 'manual' | 'invalid'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface ResultItem {
|
|||
|
|
id: string
|
|||
|
|
instruction: string
|
|||
|
|
input: string
|
|||
|
|
output: string
|
|||
|
|
status: 'valid' | 'modified' | 'invalid'
|
|||
|
|
error?: string
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 实现可重复的来源偏移生成**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export function buildPreviewItems(sourceText: string, processType: ProcessType): PreviewItem[] {
|
|||
|
|
const lines = sourceText.split('\n')
|
|||
|
|
const groupSize = processType === 'structured' ? 1 : 3
|
|||
|
|
let cursor = 0
|
|||
|
|
const ranges = lines.map((line, index) => {
|
|||
|
|
const start = cursor
|
|||
|
|
cursor += line.length + (index < lines.length - 1 ? 1 : 0)
|
|||
|
|
return { line, lineNumber: index + 1, start, end: start + line.length }
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const items: PreviewItem[] = []
|
|||
|
|
for (let index = 0; index < ranges.length; index += groupSize) {
|
|||
|
|
const group = ranges.slice(index, index + groupSize)
|
|||
|
|
if (!group.length || group.every((item) => !item.line.trim())) continue
|
|||
|
|
const content = group.map((item) => item.line).join('\n')
|
|||
|
|
items.push({
|
|||
|
|
id: `preview-${items.length + 1}`,
|
|||
|
|
originalContent: content,
|
|||
|
|
editedContent: content,
|
|||
|
|
sourceStart: group[0].start,
|
|||
|
|
sourceEnd: group[group.length - 1].end,
|
|||
|
|
sourceStartLine: group[0].lineNumber,
|
|||
|
|
sourceEndLine: group[group.length - 1].lineNumber,
|
|||
|
|
tokenCount: Math.max(1, Math.ceil(content.length / 2)),
|
|||
|
|
status: 'original',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
return items
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 运行回归检查和类型检查**
|
|||
|
|
|
|||
|
|
Run: `cd frontend && npm run test:data-process-wizard && npm run type-check`
|
|||
|
|
|
|||
|
|
Expected: 回归测试继续因组件未完成而失败;`previewModel.ts` 和 `types.ts` 不产生 TypeScript 错误。
|
|||
|
|
|
|||
|
|
### Task 3: 实现向导壳层和第一步创建任务
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `frontend/src/views/data-process/create/TaskSetupStep.vue`
|
|||
|
|
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
|
|||
|
|
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: `ProcessType`、Element Plus 表单与上传组件。
|
|||
|
|
- Produces: `TaskSetupStep` 的 `v-model:name`、`v-model:description`、`v-model:processType`、`file-change`、`remove-file` 事件;父页面提供固定 `WIZARD_STEPS` 和统一底部操作。
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 将父页面步骤定义改为不可变四步**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const WIZARD_STEPS = [
|
|||
|
|
{ id: 'create', title: '创建任务', desc: '填写任务信息与上传源数据' },
|
|||
|
|
{ id: 'preview', title: '数据预览', desc: '核对源文件与预览内容' },
|
|||
|
|
{ id: 'generate', title: '开始生成', desc: '确认摘要并启动处理' },
|
|||
|
|
{ id: 'results', title: '结果编辑与保存', desc: '检查、修改并保存结果' },
|
|||
|
|
] as const
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 创建第一步组件,保留现有校验并改为视觉选择块**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const props = defineProps<{
|
|||
|
|
name: string
|
|||
|
|
description: string
|
|||
|
|
processType: ProcessType
|
|||
|
|
file: File | null
|
|||
|
|
fileCount: number
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const emit = defineEmits<{
|
|||
|
|
'update:name': [value: string]
|
|||
|
|
'update:description': [value: string]
|
|||
|
|
'update:processType': [value: ProcessType]
|
|||
|
|
'file-change': [file: UploadFile]
|
|||
|
|
'remove-file': []
|
|||
|
|
}>()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 在父页面统一步骤导航和底部动作文案**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const primaryActionLabel = computed(() => ({
|
|||
|
|
create: '继续:数据预览',
|
|||
|
|
preview: '确认预览并继续',
|
|||
|
|
generate: generation.progress === 100 ? '查看生成结果' : '开始生成',
|
|||
|
|
results: '保存任务',
|
|||
|
|
}[currentStepId.value]))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 运行回归检查和类型检查**
|
|||
|
|
|
|||
|
|
Run: `cd frontend && npm run test:data-process-wizard && npm run type-check`
|
|||
|
|
|
|||
|
|
Expected: 回归测试因后续三个组件缺失而失败;第一步相关代码通过类型检查。
|
|||
|
|
|
|||
|
|
### Task 4: 实现左右源文件与切片同步预览
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `frontend/src/views/data-process/create/PreviewCompareStep.vue`
|
|||
|
|
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
|
|||
|
|
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: `sourceText: string`、`items: PreviewItem[]`、`selectedId: string | null`、`processType: ProcessType`。
|
|||
|
|
- Produces: `update:selectedId`、`update:item-content`、`restore:item`;选中条目变化时调用 `scrollIntoView({ block: 'center' })`。
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 增加源文范围与选中态的结构断言**
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
for (const marker of [
|
|||
|
|
'source-viewer',
|
|||
|
|
'source-line',
|
|||
|
|
'is-highlighted',
|
|||
|
|
'preview-item',
|
|||
|
|
'preview-editor',
|
|||
|
|
'scrollIntoView',
|
|||
|
|
]) {
|
|||
|
|
assert.ok(previewSource.includes(marker), `第二步缺少结构:${marker}`)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 通过行偏移判断高亮范围**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
function isLineHighlighted(lineStart: number, lineEnd: number) {
|
|||
|
|
if (!selectedItem.value || selectedItem.value.sourceStart == null || selectedItem.value.sourceEnd == null) {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
return lineEnd >= selectedItem.value.sourceStart
|
|||
|
|
&& lineStart <= selectedItem.value.sourceEnd
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 选中切片后定位首个高亮行**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
watch(selectedItem, async (item) => {
|
|||
|
|
if (!item || item.sourceStart == null) return
|
|||
|
|
await nextTick()
|
|||
|
|
sourceViewerRef.value
|
|||
|
|
?.querySelector<HTMLElement>(`[data-offset="${item.sourceStart}"]`)
|
|||
|
|
?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 编辑时只更新 `editedContent` 和状态**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
function updateContent(item: PreviewItem, value: string) {
|
|||
|
|
emit('update:item-content', item.id, value)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
父组件处理事件时不得修改 `sourceText`、`sourceStart` 或 `sourceEnd`:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
function updatePreviewContent(id: string, value: string) {
|
|||
|
|
const item = draft.previewItems.find((entry) => entry.id === id)
|
|||
|
|
if (!item) return
|
|||
|
|
item.editedContent = value
|
|||
|
|
item.status = value === item.originalContent ? 'original' : 'modified'
|
|||
|
|
draft.dirty = true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: 完成搜索、仅看已修改、上一片、下一片和恢复原文**
|
|||
|
|
|
|||
|
|
Run: `cd frontend && npm run test:data-process-wizard && npm run type-check`
|
|||
|
|
|
|||
|
|
Expected: 第二步结构断言通过,类型检查通过;回归测试只因第三、四步组件缺失而失败。
|
|||
|
|
|
|||
|
|
### Task 5: 实现生成与结果编辑两个独立步骤
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `frontend/src/views/data-process/create/GenerationStep.vue`
|
|||
|
|
- Create: `frontend/src/views/data-process/create/ResultEditorStep.vue`
|
|||
|
|
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
|
|||
|
|
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: 任务摘要、预览条目、生成状态和结果条目。
|
|||
|
|
- Produces: `start`、`stop`、`retry`、`update:result`、`restore:result`、`save` 事件。
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 生成步骤只保留摘要、进度与状态**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const emit = defineEmits<{
|
|||
|
|
start: []
|
|||
|
|
stop: []
|
|||
|
|
retry: []
|
|||
|
|
}>()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
生成完成前底部唯一主操作为 `开始生成`;生成进行中为禁用的 `正在生成`;完成后变为 `查看生成结果`。
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 清理并托管模拟生成计时器**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
let generationTimer: ReturnType<typeof setInterval> | null = null
|
|||
|
|
|
|||
|
|
function stopGenerationTimer() {
|
|||
|
|
if (generationTimer) clearInterval(generationTimer)
|
|||
|
|
generationTimer = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onBeforeUnmount(stopGenerationTimer)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 将预览条目转换为结构化结果字段**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
function createResults(items: PreviewItem[]): ResultItem[] {
|
|||
|
|
return items.slice(0, 12).map((item, index) => ({
|
|||
|
|
id: `result-${index + 1}`,
|
|||
|
|
instruction: item.editedContent.split('\n')[0] || `数据条目 ${index + 1}`,
|
|||
|
|
input: '',
|
|||
|
|
output: item.editedContent.split('\n').slice(1).join('\n') || item.editedContent,
|
|||
|
|
status: 'valid',
|
|||
|
|
}))
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 使用左侧结果列表和右侧字段编辑器替代原始 JSON 文本框**
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
function validateResult(item: ResultItem) {
|
|||
|
|
item.error = item.instruction.trim() && item.output.trim() ? undefined : '指令和输出不能为空'
|
|||
|
|
item.status = item.error ? 'invalid' : 'modified'
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: 运行回归、页面表面和类型检查**
|
|||
|
|
|
|||
|
|
Run: `cd frontend && npm run test:data-process-wizard && npm run test:page-surface && npm run type-check`
|
|||
|
|
|
|||
|
|
Expected: 三项检查全部 PASS。
|
|||
|
|
|
|||
|
|
### Task 6: 完成视觉实现、响应式和浏览器验收
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
|
|||
|
|
- Modify: `frontend/src/views/data-process/create/TaskSetupStep.vue`
|
|||
|
|
- Modify: `frontend/src/views/data-process/create/PreviewCompareStep.vue`
|
|||
|
|
- Modify: `frontend/src/views/data-process/create/GenerationStep.vue`
|
|||
|
|
- Modify: `frontend/src/views/data-process/create/ResultEditorStep.vue`
|
|||
|
|
- Test: `frontend/scripts/regression-data-process-wizard.mjs`
|
|||
|
|
|
|||
|
|
**Interfaces:**
|
|||
|
|
- Consumes: 已完成的四步组件和现有全局页面画布。
|
|||
|
|
- Produces: 与确认修订稿一致的桌面布局,以及 900px 以下的上下布局。
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 落实单层页面、固定步骤和底部操作栏样式**
|
|||
|
|
|
|||
|
|
```scss
|
|||
|
|
.wizard-footer {
|
|||
|
|
position: sticky;
|
|||
|
|
bottom: 0;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
min-height: 64px;
|
|||
|
|
background: rgba(255, 255, 255, 0.98);
|
|||
|
|
border-top: 1px solid #eef0f5;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 落实桌面左右对照和 900px 响应式**
|
|||
|
|
|
|||
|
|
```scss
|
|||
|
|
.preview-workspace {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: minmax(0, 58fr) minmax(380px, 42fr);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 900px) {
|
|||
|
|
.preview-workspace {
|
|||
|
|
grid-template-columns: minmax(0, 1fr);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 启动页面并逐步验证四步交互**
|
|||
|
|
|
|||
|
|
Run: `cd frontend && npm run dev -- --host 0.0.0.0 --port 16801`
|
|||
|
|
|
|||
|
|
Browser checks at `http://localhost:16801/data-process/create`:
|
|||
|
|
|
|||
|
|
1. 第一步上传文本并选择非结构化数据。
|
|||
|
|
2. 第二步点击至少三个右侧切片,确认左侧滚动目标和高亮范围变化。
|
|||
|
|
3. 修改一个切片并切换前后条目,确认修改状态和内容保留。
|
|||
|
|
4. 完成生成并进入第四步,修改结果字段并保存。
|
|||
|
|
5. 返回前一步,确认草稿和选中项未丢失。
|
|||
|
|
6. 以 1440×1024 和 900px 窄屏分别截图,确认无横向溢出和底部遮挡。
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 执行完整验证**
|
|||
|
|
|
|||
|
|
Run: `cd frontend && npm run test:data-process-wizard && npm run test:page-surface && npm run type-check && npm run build`
|
|||
|
|
|
|||
|
|
Expected: 所有回归脚本、类型检查和生产构建全部 PASS。
|
|||
|
|
|
|||
|
|
## Self-Review Result
|
|||
|
|
|
|||
|
|
- 规格中的四步稳定语义由 Tasks 1、3、5 覆盖。
|
|||
|
|
- 左右对照、来源映射、滚动高亮、编辑不改源文件由 Tasks 2、4 覆盖。
|
|||
|
|
- 生成状态、计时器清理、结果字段校验由 Task 5 覆盖。
|
|||
|
|
- 单层白底、响应式、路由转场连续性和最终验收由 Task 6 覆盖。
|
|||
|
|
- 未引入新依赖;计划中所有类型和事件名在前置任务中已有定义。
|
|||
|
|
|