第一次提交
This commit is contained in:
458
docs/superpowers/plans/2026-07-10-data-process-create-wizard.md
Normal file
458
docs/superpowers/plans/2026-07-10-data-process-create-wizard.md
Normal file
@@ -0,0 +1,458 @@
|
||||
# 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 覆盖。
|
||||
- 未引入新依赖;计划中所有类型和事件名在前置任务中已有定义。
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# 数据处理任务状态切换移除 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:** 移除数据处理任务列表左上角的三个状态切换按钮,并让表格始终展示全部任务。
|
||||
|
||||
**Architecture:** 保持现有 `DataTablePage` 结构不变,仅删除 `DataProcessListView.vue` 内部的页签状态、派生筛选数据、标题插槽和专用样式。新增一个轻量源码回归脚本,锁定“无状态切换组件且表格直接使用完整数据源”的行为。
|
||||
|
||||
**Tech Stack:** Vue 3、TypeScript、Element Plus、Node.js `assert`、Vue SFC parser
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 增加状态切换移除回归检查
|
||||
|
||||
**Files:**
|
||||
- Create: `frontend/scripts/regression-data-process-list.mjs`
|
||||
- Modify: `frontend/package.json`
|
||||
- Test: `frontend/scripts/regression-data-process-list.mjs`
|
||||
|
||||
- [ ] **Step 1: 编写失败的回归检查**
|
||||
|
||||
```js
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'node:path'
|
||||
import { parse as parseSfc } from '@vue/compiler-sfc'
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const viewPath = path.resolve(scriptDir, '../src/views/data-process/DataProcessListView.vue')
|
||||
const source = await readFile(viewPath, 'utf8')
|
||||
const { descriptor, errors } = parseSfc(source, { filename: viewPath })
|
||||
|
||||
assert.equal(errors.length, 0, `数据处理任务列表模板无法解析:${errors[0]}`)
|
||||
assert.ok(descriptor.template?.content.trim(), '数据处理任务列表缺少可渲染模板')
|
||||
assert.match(source, /:data="dataList"/, '任务表格必须直接展示完整任务数据')
|
||||
assert.doesNotMatch(source, /activeTab|filteredDataList/, '不应保留状态切换筛选逻辑')
|
||||
assert.doesNotMatch(source, /全部任务|处理中|已完成/, '不应保留状态切换按钮文案')
|
||||
assert.doesNotMatch(source, /capsule-tabs|capsule-tab-item/, '不应保留状态切换专用样式')
|
||||
|
||||
console.log('数据处理任务列表状态切换移除回归检查通过')
|
||||
```
|
||||
|
||||
在 `frontend/package.json` 的 `scripts` 中增加:
|
||||
|
||||
```json
|
||||
"test:data-process-list": "node scripts/regression-data-process-list.mjs"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行检查并确认先失败**
|
||||
|
||||
Run: `npm run test:data-process-list`
|
||||
|
||||
Expected: FAIL,错误指出任务表格尚未直接使用 `dataList`,或仍存在状态切换逻辑。
|
||||
|
||||
- [ ] **Step 3: 提交回归检查**
|
||||
|
||||
```bash
|
||||
git add frontend/package.json frontend/scripts/regression-data-process-list.mjs
|
||||
git commit -m "test: 覆盖数据处理任务列表布局"
|
||||
```
|
||||
|
||||
### Task 2: 移除状态切换组件和筛选逻辑
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/views/data-process/DataProcessListView.vue`
|
||||
- Test: `frontend/scripts/regression-data-process-list.mjs`
|
||||
|
||||
- [ ] **Step 1: 实现最小改动**
|
||||
|
||||
将脚本导入改为仅保留 `ref`:
|
||||
|
||||
```ts
|
||||
import { ref } from 'vue'
|
||||
```
|
||||
|
||||
删除 `activeTab` 和 `filteredDataList`,并将表格数据源改为:
|
||||
|
||||
```vue
|
||||
<DataTablePage
|
||||
title=""
|
||||
:data="dataList"
|
||||
searchable
|
||||
:search-fields="['name']"
|
||||
create-text="新建数据处理任务"
|
||||
create-to="/data-process/create"
|
||||
row-key="id"
|
||||
:page-size="10"
|
||||
>
|
||||
```
|
||||
|
||||
同时删除整个 `#title` 插槽以及 `.capsule-tabs`、`.capsule-tab-item` 样式,仅保留操作按钮样式。
|
||||
|
||||
- [ ] **Step 2: 运行目标回归检查**
|
||||
|
||||
Run: `npm run test:data-process-list`
|
||||
|
||||
Expected: PASS,输出 `数据处理任务列表状态切换移除回归检查通过`。
|
||||
|
||||
- [ ] **Step 3: 运行前端类型检查**
|
||||
|
||||
Run: `npm run type-check`
|
||||
|
||||
Expected: PASS,退出码为 `0`。
|
||||
|
||||
- [ ] **Step 4: 检查差异和格式**
|
||||
|
||||
Run: `git diff --check && git diff -- frontend/src/views/data-process/DataProcessListView.vue frontend/package.json frontend/scripts/regression-data-process-list.mjs`
|
||||
|
||||
Expected: `git diff --check` 无输出,差异仅包含状态切换移除及对应测试。
|
||||
|
||||
- [ ] **Step 5: 提交实现**
|
||||
|
||||
```bash
|
||||
git add frontend/src/views/data-process/DataProcessListView.vue
|
||||
git commit -m "refactor: 移除数据处理状态切换"
|
||||
```
|
||||
182
docs/superpowers/plans/2026-07-10-dataset-task-mock-data.md
Normal file
182
docs/superpowers/plans/2026-07-10-dataset-task-mock-data.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Dataset Task Mock Data 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:** 在数据集管理页的“数据任务”页签展示 4 条任务产出 Mock 数据,并让“本地上传”与“数据任务”按来源稳定分流。
|
||||
|
||||
**Architecture:** 保持 `mockDatasets` 为唯一数据源,在 `DatasetItem` 上增加可选来源字段,并由列表页计算属性按来源过滤。使用一个无新增依赖的 Node 回归脚本锁定类型、Mock 数量、数据名称和页签过滤规则。
|
||||
|
||||
**Tech Stack:** Vue 3、TypeScript 5.7、Element Plus、Node.js 回归脚本、Vite 6
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- `source` 只允许 `upload` 或 `task`,并保持可选以兼容暂未返回该字段的接口数据。
|
||||
- 未携带 `source` 的数据归入“本地上传”。
|
||||
- 现有 6 条 Mock 数据标记为 `upload`,新增 4 条 Mock 数据标记为 `task`。
|
||||
- 不新增依赖,不修改后端接口,不实现真实的数据任务关联。
|
||||
- 搜索、分页、预览、下载和删除按钮保持现有行为。
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- `frontend/scripts/regression-dataset-task-tab.mjs`:静态回归检查,验证来源类型、Mock 数据和页签过滤规则。
|
||||
- `frontend/package.json`:注册 `test:dataset-task-tab` 命令。
|
||||
- `frontend/src/types/index.ts`:定义 `DatasetSource` 并扩展 `DatasetItem`。
|
||||
- `frontend/src/mock/data.ts`:标记 6 条上传数据并新增 4 条任务数据。
|
||||
- `frontend/src/views/dataset/DatasetListView.vue`:按 `source` 过滤两个页签。
|
||||
|
||||
### Task 1: 数据任务 Mock 数据与页签分流
|
||||
|
||||
**Files:**
|
||||
- Create: `frontend/scripts/regression-dataset-task-tab.mjs`
|
||||
- Modify: `frontend/package.json`
|
||||
- Modify: `frontend/src/types/index.ts:58-78`
|
||||
- Modify: `frontend/src/mock/data.ts:100-108`
|
||||
- Modify: `frontend/src/views/dataset/DatasetListView.vue:16-23`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `getDatasetList(): Promise<DatasetItem[]>` 与现有 `DataTablePage` 的 `data` 属性。
|
||||
- Produces: `DatasetSource = 'upload' | 'task'`、`DatasetItem.source?: DatasetSource`,以及按来源过滤后的 `filteredDataList`。
|
||||
|
||||
- [ ] **Step 1: 写入会失败的回归检查并注册命令**
|
||||
|
||||
创建 `frontend/scripts/regression-dataset-task-tab.mjs`:
|
||||
|
||||
```js
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'node:path'
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const typesPath = path.resolve(scriptDir, '../src/types/index.ts')
|
||||
const dataPath = path.resolve(scriptDir, '../src/mock/data.ts')
|
||||
const viewPath = path.resolve(scriptDir, '../src/views/dataset/DatasetListView.vue')
|
||||
|
||||
const [typesSource, dataSource, viewSource] = await Promise.all([
|
||||
readFile(typesPath, 'utf8'),
|
||||
readFile(dataPath, 'utf8'),
|
||||
readFile(viewPath, 'utf8'),
|
||||
])
|
||||
|
||||
assert.match(typesSource, /export type DatasetSource = 'upload' \| 'task'/)
|
||||
assert.match(typesSource, /source\?: DatasetSource/)
|
||||
|
||||
assert.equal((dataSource.match(/source: 'upload'/g) || []).length, 6)
|
||||
assert.equal((dataSource.match(/source: 'task'/g) || []).length, 4)
|
||||
|
||||
for (const name of [
|
||||
'客服对话清洗集',
|
||||
'通用指令构造集',
|
||||
'用户反馈脱敏集',
|
||||
'多轮对话增强集',
|
||||
]) {
|
||||
assert.ok(dataSource.includes(`name: '${name}'`), `缺少数据任务 Mock:${name}`)
|
||||
}
|
||||
|
||||
assert.match(viewSource, /item\.source === 'task'/)
|
||||
assert.match(viewSource, /item\.source !== 'task'/)
|
||||
assert.doesNotMatch(viewSource, /数据任务产生的数据集[\s\S]*?return \[\]/)
|
||||
|
||||
console.log('数据任务 Mock 数据与页签分流回归检查通过')
|
||||
```
|
||||
|
||||
在 `frontend/package.json` 的 `scripts` 中加入:
|
||||
|
||||
```json
|
||||
"test:dataset-task-tab": "node scripts/regression-dataset-task-tab.mjs"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行回归检查并确认红灯**
|
||||
|
||||
Run: `cd frontend && npm run test:dataset-task-tab`
|
||||
|
||||
Expected: FAIL,首个断言提示缺少 `DatasetSource`。
|
||||
|
||||
- [ ] **Step 3: 增加来源类型**
|
||||
|
||||
在 `frontend/src/types/index.ts` 的数据集类型区加入并使用:
|
||||
|
||||
```ts
|
||||
export type DatasetType = 'train' | 'test' | 'eval' | 'val' | 'other'
|
||||
export type DatasetStorage = 'local' | 'cloud' | 'minio'
|
||||
export type DatasetSource = 'upload' | 'task'
|
||||
|
||||
export interface DatasetItem {
|
||||
id: number | string
|
||||
name: string
|
||||
type: DatasetType | string
|
||||
storage_type: DatasetStorage | string
|
||||
source?: DatasetSource
|
||||
size?: string | number
|
||||
count?: number
|
||||
description?: string
|
||||
create_time?: string
|
||||
files?: DatasetFile[]
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 标记现有数据并添加 4 条任务数据**
|
||||
|
||||
将 `frontend/src/mock/data.ts` 的 `mockDatasets` 更新为:
|
||||
|
||||
```ts
|
||||
export const mockDatasets: DatasetItem[] = [
|
||||
{ id: 1, name: '金融问答-训练集', type: 'train', storage_type: 'local', source: 'upload', size: '128 MB', count: 8560, description: '金融领域问答对', create_time: '2025-12-20T08:00:00Z' },
|
||||
{ id: 2, name: '法律文书-训练集', type: 'train', storage_type: 'local', source: 'upload', size: '256 MB', count: 15230, description: '法律文书数据集', create_time: '2025-12-25T10:30:00Z' },
|
||||
{ id: 3, name: '客服对话-训练集', type: 'train', storage_type: 'minio', source: 'upload', size: '512 MB', count: 24500, description: '客服对话记录', create_time: '2026-01-05T14:20:00Z' },
|
||||
{ id: 4, name: '金融评测集', type: 'eval', storage_type: 'local', source: 'upload', size: '32 MB', count: 1200, description: '金融领域评测', create_time: '2026-01-10T09:15:00Z' },
|
||||
{ id: 5, name: '通用能力评测', type: 'eval', storage_type: 'local', source: 'upload', size: '64 MB', count: 3500, description: '通用能力评测数据集', create_time: '2026-01-12T11:30:00Z' },
|
||||
{ id: 6, name: '医疗问答-训练集', type: 'train', storage_type: 'local', source: 'upload', size: '180 MB', count: 9800, description: '医疗问答对', create_time: '2026-02-01T15:00:00Z' },
|
||||
{ id: 7, name: '客服对话清洗集', type: 'train', storage_type: 'minio', source: 'task', size: '96 MB', count: 18240, description: '由客服问答数据清洗任务生成', create_time: '2026-07-08T06:28:00Z' },
|
||||
{ id: 8, name: '通用指令构造集', type: 'train', storage_type: 'local', source: 'task', size: '148 MB', count: 12600, description: '由指令微调数据构造任务生成', create_time: '2026-07-09T01:42:00Z' },
|
||||
{ id: 9, name: '用户反馈脱敏集', type: 'test', storage_type: 'minio', source: 'task', size: '72 MB', count: 9340, description: '由敏感信息脱敏任务生成', create_time: '2026-07-09T09:18:00Z' },
|
||||
{ id: 10, name: '多轮对话增强集', type: 'eval', storage_type: 'local', source: 'task', size: '41 MB', count: 2780, description: '由多轮对话拼接任务生成', create_time: '2026-07-10T02:06:00Z' },
|
||||
]
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 实现两个页签的来源过滤**
|
||||
|
||||
将 `frontend/src/views/dataset/DatasetListView.vue` 的 `filteredDataList` 更新为:
|
||||
|
||||
```ts
|
||||
const filteredDataList = computed(() => {
|
||||
if (activeTab.value === 'task') {
|
||||
return dataList.value.filter((item) => item.source === 'task')
|
||||
}
|
||||
|
||||
return dataList.value.filter((item) => item.source !== 'task')
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 运行针对性回归检查并确认绿灯**
|
||||
|
||||
Run: `cd frontend && npm run test:dataset-task-tab`
|
||||
|
||||
Expected: PASS,输出 `数据任务 Mock 数据与页签分流回归检查通过`。
|
||||
|
||||
- [ ] **Step 7: 运行类型检查和生产构建**
|
||||
|
||||
Run: `cd frontend && npm run type-check`
|
||||
|
||||
Expected: PASS;若仓库原有错误仍存在,保存完整输出并确认本任务修改文件不在错误列表中。
|
||||
|
||||
Run: `cd frontend && npx vite build`
|
||||
|
||||
Expected: PASS,并生成 `dist` 产物。
|
||||
|
||||
- [ ] **Step 8: 页面烟雾验证**
|
||||
|
||||
启动开发服务器后打开数据集管理页,验证“本地上传”总数为 6,切换“数据任务”后总数为 4,搜索“脱敏”只显示“用户反馈脱敏集”,且预览、下载、删除按钮可见。
|
||||
|
||||
- [ ] **Step 9: 提交实现**
|
||||
|
||||
```bash
|
||||
git add frontend/package.json \
|
||||
frontend/scripts/regression-dataset-task-tab.mjs \
|
||||
frontend/src/types/index.ts \
|
||||
frontend/src/mock/data.ts \
|
||||
frontend/src/views/dataset/DatasetListView.vue
|
||||
git commit -m "feat: 添加数据任务 mock 数据"
|
||||
```
|
||||
126
docs/superpowers/plans/2026-07-10-multi-file-preview-selector.md
Normal file
126
docs/superpowers/plans/2026-07-10-multi-file-preview-selector.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# 多文件预览下拉选择器 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:** 在数据处理向导第二步中支持按文件切换原文和切片,同时保留现有双栏阅读空间。
|
||||
|
||||
**Architecture:** 为每个预览条目记录来源文件 ID;父页面按当前文件筛选原文与条目。预览组件只负责可搜索下拉选择器和当前文件双栏对照,不拼接不同文件的原文。
|
||||
|
||||
**Tech Stack:** Vue 3、TypeScript、Element Plus、SCSS、Node 回归脚本。
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 预览主区保持原文与切片的双栏比例,不新增常驻第三栏。
|
||||
- 下拉选择器必须支持 100 个文件的名称筛选。
|
||||
- 切换文件不得丢失其他文件已编辑的切片内容。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 锁定多文件来源映射
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/scripts/regression-data-process-wizard.mjs`
|
||||
- Modify: `frontend/src/views/data-process/create/types.ts`
|
||||
- Modify: `frontend/src/views/data-process/create/previewModel.ts`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `PreviewItem.sourceFileId: string`。
|
||||
- Produces: `buildPreviewItems(sourceText, processType, sourceFileId)` 为同一文件生成带文件归属的唯一条目。
|
||||
|
||||
- [ ] **Step 1: 写入失败断言**
|
||||
|
||||
```js
|
||||
assert.match(typesSource, /sourceFileId/, 'PreviewItem 缺少来源文件标识')
|
||||
assert.match(modelSource, /sourceFileId/, '切片生成没有写入来源文件标识')
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行失败断言**
|
||||
|
||||
Run: `npm run test:data-process-wizard`
|
||||
Expected: FAIL,提示缺少 `sourceFileId`。
|
||||
|
||||
- [ ] **Step 3: 实现文件归属**
|
||||
|
||||
```ts
|
||||
export interface PreviewItem {
|
||||
sourceFileId: string
|
||||
}
|
||||
|
||||
export function buildPreviewItems(sourceText: string, processType: ProcessType, sourceFileId: string) {
|
||||
// 每个 item 写入 sourceFileId,并以它构造稳定 ID。
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 再次运行回归脚本**
|
||||
|
||||
Run: `npm run test:data-process-wizard`
|
||||
Expected: 新断言通过;仅保留已知的布局失败(如存在)。
|
||||
|
||||
### Task 2: 按文件驱动双栏预览
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `PreviewItem.sourceFileId`。
|
||||
- Produces: `activePreviewFile`、`activePreviewItems` 与当前文件选择状态。
|
||||
|
||||
- [ ] **Step 1: 为多文件下拉接线添加失败断言**
|
||||
|
||||
```js
|
||||
assert.match(viewSource, /selectedPreviewFileId/, '父页面缺少当前预览文件状态')
|
||||
assert.match(viewSource, /buildPreviewItems\(file\.content, processType\.value, String\(file\.uid\)\)/, '预览没有按文件分别生成')
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行失败断言**
|
||||
|
||||
Run: `npm run test:data-process-wizard`
|
||||
Expected: FAIL,提示缺少当前预览文件状态。
|
||||
|
||||
- [ ] **Step 3: 最小实现**
|
||||
|
||||
```ts
|
||||
const activePreviewFile = computed(() => uploadedFiles.value.find((file) => String(file.uid) === selectedPreviewFileId.value))
|
||||
const activePreviewItems = computed(() => previewItems.value.filter((item) => item.sourceFileId === selectedPreviewFileId.value))
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 运行回归脚本**
|
||||
|
||||
Run: `npm run test:data-process-wizard`
|
||||
Expected: 父页面多文件断言通过。
|
||||
|
||||
### Task 3: 加入可搜索文件下拉框与布局修复
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/views/data-process/create/PreviewCompareStep.vue`
|
||||
- Modify: `frontend/src/views/data-process/DataProcessCreateView.vue`
|
||||
- Modify: `frontend/scripts/regression-data-process-wizard.mjs`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `files`、`selectedFileId`、`items`、`sourceText`。
|
||||
- Produces: `update:selectedFileId` 事件。
|
||||
|
||||
- [ ] **Step 1: 添加失败断言**
|
||||
|
||||
```js
|
||||
assert.match(previewSource, /filterable/, '文件选择器必须可搜索')
|
||||
assert.match(previewSource, /当前文件/, '预览缺少当前文件切换器')
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行失败断言**
|
||||
|
||||
Run: `npm run test:data-process-wizard`
|
||||
Expected: FAIL,提示缺少可搜索的文件选择器。
|
||||
|
||||
- [ ] **Step 3: 实现下拉框与响应式样式**
|
||||
|
||||
```vue
|
||||
<el-select filterable :model-value="selectedFileId" @update:model-value="emit('update:selectedFileId', $event)">
|
||||
<el-option v-for="file in files" :key="file.id" :label="file.name" :value="file.id" />
|
||||
</el-select>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 将 `.wizard-content` 设为 `min-height: 0` 并运行验证**
|
||||
|
||||
Run: `npm run test:data-process-wizard && npm run type-check && npm run build`
|
||||
Expected: 三个命令退出码均为 0。
|
||||
@@ -0,0 +1,94 @@
|
||||
# Page Surface Classification 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:** 列表页直接使用自身白色卡片,表单和详情页继续使用主布局提供的白色圆角画布。
|
||||
|
||||
**Architecture:** 使用 Vue Router `meta.pageSurface` 做显式页面表面分类。主布局默认渲染白色画布,仅在 `pageSurface === 'self'` 时切换为透明、无内边距的承载容器。
|
||||
|
||||
**Tech Stack:** Vue 3、Vue Router 4、TypeScript、SCSS、Node.js 回归脚本
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 只给六个自带白色列表卡片的路由声明 `pageSurface: 'self'`。
|
||||
- 其他路由默认继续使用白色页面画布。
|
||||
- 不新增依赖,不修改业务逻辑。
|
||||
- 先写失败测试,再实现最小修复。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 路由级页面表面分类
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/scripts/regression-page-surface.mjs`
|
||||
- Modify: `frontend/src/router/index.ts`
|
||||
- Modify: `frontend/src/layouts/MainLayout.vue`
|
||||
- Test: `frontend/scripts/regression-page-surface.mjs`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Vue Router 当前路由对象的 `route.meta.pageSurface`。
|
||||
- Produces: `pageSurface: 'self'` 路由元数据和 `.page-canvas.is-self-surface` 布局状态。
|
||||
|
||||
- [ ] **Step 1: 写入失败回归测试**
|
||||
|
||||
在 `regression-page-surface.mjs` 中读取 `src/router/index.ts`,断言六个列表路由包含
|
||||
`pageSurface: 'self'`;断言 `MainLayout` 使用 `useRoute()` 和动态类;断言状态样式为:
|
||||
|
||||
```scss
|
||||
.page-canvas.is-self-surface {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试并确认 RED**
|
||||
|
||||
Run: `npm run test:page-surface`
|
||||
|
||||
Expected: FAIL,提示列表路由缺少 `pageSurface: 'self'` 或主布局缺少自表面状态。
|
||||
|
||||
- [ ] **Step 3: 写入最小实现**
|
||||
|
||||
在六个列表路由中加入:
|
||||
|
||||
```ts
|
||||
meta: { title: '页面标题', pageSurface: 'self' },
|
||||
```
|
||||
|
||||
在 `MainLayout.vue` 中使用:
|
||||
|
||||
```ts
|
||||
const route = useRoute()
|
||||
```
|
||||
|
||||
```vue
|
||||
<div
|
||||
class="page-canvas"
|
||||
:class="{ 'is-self-surface': route.meta.pageSurface === 'self' }"
|
||||
>
|
||||
```
|
||||
|
||||
并加入透明承载容器样式。
|
||||
|
||||
- [ ] **Step 4: 运行专项测试并确认 GREEN**
|
||||
|
||||
Run: `npm run test:page-surface`
|
||||
|
||||
Expected: PASS,输出“全局页面背景与内容表面回归检查通过”。
|
||||
|
||||
- [ ] **Step 5: 运行相关回归与生产构建**
|
||||
|
||||
Run: `npm run test:training-log-layout`
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
Run: `npx vite build`
|
||||
|
||||
Expected: build exit code 0;允许保留项目既有字体解析和 chunk size 警告。
|
||||
|
||||
- [ ] **Step 6: 浏览器视觉验证**
|
||||
|
||||
打开 `/fine-tune`,确认灰色背景上仅有列表自身白色卡片;打开
|
||||
`/training-log/1`,确认白色圆角页面画布仍存在。两个页面均不得水平溢出,控制台不得新增错误。
|
||||
77
docs/superpowers/plans/2026-07-10-preview-slice-edit-mode.md
Normal file
77
docs/superpowers/plans/2026-07-10-preview-slice-edit-mode.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 切片单面板编辑模式 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:** 将数据预览右侧改为列表和编辑器互斥的单面板,并通过保存或取消控制切片内容写回。
|
||||
|
||||
**Architecture:** `PreviewCompareStep.vue` 保留切片筛选、分页和源文件定位,新增组件内编辑模式与临时草稿。父页面仅在收到保存事件时更新 `PreviewItem`,删除仍复用现有确认与删除事件。
|
||||
|
||||
**Tech Stack:** Vue 3 Composition API、TypeScript、Element Plus、Node 回归脚本。
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 列表行只展示编号、来源行号及编辑、删除图标操作。
|
||||
- 编辑草稿未保存时不得更新 `PreviewItem.editedContent`。
|
||||
- 搜索、分页或切换文件时退出编辑模式并丢弃草稿。
|
||||
- 不新增依赖。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 切片列表与编辑模式切换
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/views/data-process/create/PreviewCompareStep.vue`
|
||||
- Modify: `frontend/scripts/regression-data-process-wizard.mjs`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `PreviewItem`、`update:selectedId`、`update:item-content`、`remove:item`。
|
||||
- Produces: `openEditor(item)`、`closeEditor()`、`saveEditor()` 与列表/编辑互斥渲染。
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
在 `regression-data-process-wizard.mjs` 断言组件存在 `editingItemId` 和 `editorDraft`,列表以图标按钮触发 `openEditor` 与 `remove:item`,编辑模式拥有 `保存修改`、`取消` 与 `返回列表`,并且列表不再含 `item-token`、`item-status`、`modifiedOnly`。
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `npm run test:data-process-wizard`
|
||||
|
||||
Expected: FAIL,提示缺少单面板编辑模式结构。
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
在组件中增加以下状态和行为:
|
||||
|
||||
```ts
|
||||
const editingItemId = ref<string | null>(null)
|
||||
const editorDraft = ref('')
|
||||
|
||||
function openEditor(item: PreviewItem) {
|
||||
editingItemId.value = item.id
|
||||
editorDraft.value = item.editedContent
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
editingItemId.value = null
|
||||
editorDraft.value = ''
|
||||
}
|
||||
|
||||
function saveEditor() {
|
||||
if (!editingItem.value) return
|
||||
emit('update:item-content', editingItem.value.id, editorDraft.value)
|
||||
closeEditor()
|
||||
}
|
||||
```
|
||||
|
||||
列表模式只渲染编号、来源和两个无文字图标按钮;编辑模式在同一位置渲染正文输入框以及返回、取消、保存操作。搜索、翻页、文件切换调用 `closeEditor()`。
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `npm run test:data-process-wizard`
|
||||
|
||||
Expected: `数据处理四步向导回归检查通过`。
|
||||
|
||||
- [ ] **Step 5: Build and visually verify**
|
||||
|
||||
Run: `npx vite build`
|
||||
|
||||
Expected: Vite completes successfully. Open the second wizard step, verify the list has only the two icon operations and that cancel does not change the selected slice content while save returns to the list.
|
||||
78
docs/superpowers/plans/2026-07-10-route-transition.md
Normal file
78
docs/superpowers/plans/2026-07-10-route-transition.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Route Transition Removal 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:** 移除主布局的页面级透明转场,消除列表与二级页面切换时的闪烁中间帧。
|
||||
|
||||
**Architecture:** `router-view` 直接渲染当前路由组件,不再包裹 Vue `transition`。页面表面仍由 `route.meta.pageSurface` 控制,因此内容和表面在同一轮渲染中同步更新。
|
||||
|
||||
**Tech Stack:** Vue 3、Vue Router 4、SCSS、Node.js 回归脚本
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 删除页面级透明度转场。
|
||||
- 保留组件内部动画。
|
||||
- 不改变路由表面分类、业务逻辑或数据加载流程。
|
||||
- 先写失败测试,再实现最小修复。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 移除主布局页面级透明转场
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/scripts/regression-page-surface.mjs`
|
||||
- Modify: `frontend/src/layouts/MainLayout.vue`
|
||||
- Test: `frontend/scripts/regression-page-surface.mjs`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `router-view` 提供的当前路由组件。
|
||||
- Produces: 不带页面级透明度动画的同步路由内容渲染。
|
||||
|
||||
- [ ] **Step 1: 写入失败回归测试**
|
||||
|
||||
在 `regression-page-surface.mjs` 中断言主布局模板不包含页面级 `transition`,并断言主布局样式不包含 `.fade-enter-*` 或 `.fade-leave-*`。
|
||||
|
||||
- [ ] **Step 2: 运行测试并确认 RED**
|
||||
|
||||
Run: `npm run test:page-surface`
|
||||
|
||||
Expected: FAIL,提示主布局仍包含页面级透明转场。
|
||||
|
||||
- [ ] **Step 3: 写入最小实现**
|
||||
|
||||
将:
|
||||
|
||||
```vue
|
||||
<transition name="fade" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
```
|
||||
|
||||
改为:
|
||||
|
||||
```vue
|
||||
<component :is="Component" />
|
||||
```
|
||||
|
||||
并删除主布局中的 `.fade-enter-active`、`.fade-leave-active`、`.fade-enter-from` 和 `.fade-leave-to` 样式。
|
||||
|
||||
- [ ] **Step 4: 运行专项回归并确认 GREEN**
|
||||
|
||||
Run: `npm run test:page-surface`
|
||||
|
||||
Expected: PASS,输出“全局页面背景与内容表面回归检查通过”。
|
||||
|
||||
- [ ] **Step 5: 运行相关回归与构建**
|
||||
|
||||
Run: `npm run test:training-log-layout`
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
Run: `npx vite build`
|
||||
|
||||
Expected: exit code 0;允许项目既有字体解析和 chunk size 警告。
|
||||
|
||||
- [ ] **Step 6: 浏览器往返验证**
|
||||
|
||||
验证 `/fine-tune` → `/fine-tune/create`、`/fine-tune` → `/training-log/1` 以及二级页返回列表;页面内容与表面同步切换,无半透明旧页面、无水平溢出、无新增控制台错误。
|
||||
|
||||
202
docs/superpowers/plans/2026-07-10-source-upload-file-list.md
Normal file
202
docs/superpowers/plans/2026-07-10-source-upload-file-list.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# 源数据上传紧凑文件列表 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:** 将数据处理创建页的已上传文件从大卡片改为固定高度、可滚动且可逐项操作的紧凑列表。
|
||||
|
||||
**Architecture:** 只修改 `TaskSetupStep` 的模板和局部样式,继续消费现有的
|
||||
`uploadedFiles` 属性并派发既有 `remove-file` 事件。使用同一组件内的标题栏和
|
||||
滚动容器管理信息密度,不改变上传、格式限制或父组件数据流。
|
||||
|
||||
**Tech Stack:** Vue 3 `<script setup>`、TypeScript、Element Plus、SCSS、Node.js 回归脚本。
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 不新增依赖、组件、数据字段或父子组件事件。
|
||||
- 上传区、格式限制、校验语义与逐项删除行为必须保持不变。
|
||||
- 空状态保留大尺寸拖拽上传区与格式提示;存在至少一个文件时,改为列表标题右侧
|
||||
的小型“继续上传”入口,且复用相同上传属性与文件变更事件。
|
||||
- 列表默认显示最多 5 行;额外文件只能在列表内部纵向滚动。
|
||||
- 文件名允许省略,但必须通过原生 `title` 保留完整文本。
|
||||
- 校验状态必须同时显示图标和文字;删除按钮必须可见且可键盘操作。
|
||||
- 当前工作区不是 Git 仓库,本计划不包含提交操作。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 紧凑上传文件列表
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/scripts/regression-data-process-wizard.mjs`
|
||||
- Modify: `frontend/src/views/data-process/create/TaskSetupStep.vue:157-164,314-351`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `uploadedFiles: { uid: string | number; name: string; size: number; count: number }[]`
|
||||
和 `remove-file(uid)` 事件。
|
||||
- Produces: `.uploaded-file-list-header`、`.uploaded-file-items` 和每条
|
||||
`.uploaded-file` 紧凑行;不新增对外 TypeScript 接口。
|
||||
|
||||
- [ ] **Step 1: 添加会失败的页面结构回归断言**
|
||||
|
||||
在 `frontend/scripts/regression-data-process-wizard.mjs` 的第二步组件检查之后读取
|
||||
`TaskSetupStep.vue`,并追加以下断言:
|
||||
|
||||
```js
|
||||
const taskSetupPath = path.join(createDir, 'TaskSetupStep.vue')
|
||||
const taskSetupSource = await readFile(taskSetupPath, 'utf8')
|
||||
|
||||
for (const marker of [
|
||||
'uploaded-file-list-header',
|
||||
'uploaded-file-items',
|
||||
'已添加 {{ uploadedFiles.length }} 个文件',
|
||||
':title="file.name"',
|
||||
'aria-label="已上传文件列表"',
|
||||
'继续上传',
|
||||
'v-if="uploadedFiles.length === 0"',
|
||||
]) {
|
||||
assert.ok(taskSetupSource.includes(marker), `源数据文件列表缺少:${marker}`)
|
||||
}
|
||||
assert.match(taskSetupSource, /\.uploaded-file-items\s*\{[\s\S]*max-height:\s*240px/, '文件列表没有固定可见高度')
|
||||
assert.match(taskSetupSource, /\.uploaded-file-items\s*\{[\s\S]*overflow-y:\s*auto/, '超出文件没有在列表内滚动')
|
||||
assert.match(taskSetupSource, /compact-upload[\s\S]*:accept="uploadAccept"/, '继续上传没有复用格式限制')
|
||||
assert.match(taskSetupSource, /compact-upload[\s\S]*on-change/, '继续上传没有复用文件变更事件')
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行回归脚本,确认新增断言失败**
|
||||
|
||||
Run: `npm --prefix frontend run test:data-process-wizard`
|
||||
|
||||
Expected: FAIL,错误信息包含新增文件列表、列表可访问性或继续上传状态切换的缺失标记。
|
||||
|
||||
- [ ] **Step 3: 更新模板为带标题与滚动区域的紧凑列表**
|
||||
|
||||
在 `TaskSetupStep.vue` 中,将当前上传区改为两种互斥状态:空状态保留大尺寸拖拽
|
||||
区和格式提示;存在文件时显示含继续上传入口的紧凑列表。两个上传入口均保留
|
||||
`multiple`、`:accept="uploadAccept"`、`:auto-upload="false"` 和相同 `on-change` 事件。
|
||||
文件图标、元信息、成功状态和原有删除事件必须保留:
|
||||
|
||||
```vue
|
||||
<el-upload
|
||||
v-if="uploadedFiles.length === 0"
|
||||
class="upload-empty-state"
|
||||
drag
|
||||
multiple
|
||||
:accept="uploadAccept"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:on-change="(file: UploadFile) => emit('file-change', file)"
|
||||
>
|
||||
<!-- 保留现有大尺寸上传引导和格式提示 -->
|
||||
</el-upload>
|
||||
|
||||
<div v-else class="uploaded-file-list" aria-label="已上传文件列表">
|
||||
<div class="uploaded-file-list-header">
|
||||
<span>已添加 {{ uploadedFiles.length }} 个文件</span>
|
||||
<el-upload
|
||||
class="compact-upload"
|
||||
multiple
|
||||
:accept="uploadAccept"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:on-change="(file: UploadFile) => emit('file-change', file)"
|
||||
>
|
||||
<el-button size="small" type="primary">继续上传</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div class="uploaded-file-items">
|
||||
<div v-for="file in uploadedFiles" :key="file.uid" class="uploaded-file">
|
||||
<span class="file-icon"><i class="fa fa-file-text-o" /></span>
|
||||
<div class="file-main">
|
||||
<strong :title="file.name">{{ file.name }}</strong>
|
||||
<span>{{ formatSize(file.size) }}<template v-if="file.count"> · {{ file.count.toLocaleString() }} 条</template></span>
|
||||
</div>
|
||||
<span class="file-status"><i class="fa fa-check-circle" /> 校验通过</span>
|
||||
<el-button link type="danger" @click="emit('remove-file', file.uid)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 将现有大卡片样式改为紧凑行和内部滚动**
|
||||
|
||||
在 `TaskSetupStep.vue` 中替换现有 `.uploaded-file-list` 与 `.uploaded-file` 相关样式,
|
||||
使容器、标题、滚动区和行高满足以下实现:
|
||||
|
||||
```scss
|
||||
.uploaded-file-list {
|
||||
margin-top: 20px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border: 1px solid #dfe3ea;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.uploaded-file-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
color: #5f6878;
|
||||
font-size: 12px;
|
||||
background: #fbfcfe;
|
||||
border-bottom: 1px solid #edf0f5;
|
||||
}
|
||||
|
||||
.uploaded-file-items {
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.compact-upload {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.uploaded-file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 48px;
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px solid #edf0f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 14px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.file-main strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
```
|
||||
|
||||
保留 `.file-main` 的弹性填充与 `.file-status` 的成功色;为窄屏媒体查询加入更小的
|
||||
行间距和可换行的状态文本,保证删除按钮不被文件名挤出。
|
||||
|
||||
- [ ] **Step 5: 运行回归脚本并执行类型检查**
|
||||
|
||||
Run: `npm --prefix frontend run test:data-process-wizard && npm --prefix frontend run type-check`
|
||||
|
||||
Expected: 两个命令均以退出码 `0` 完成,前者输出 `数据处理四步向导回归检查通过`。
|
||||
|
||||
- [ ] **Step 6: 构建生产包,检查样式与模板编译**
|
||||
|
||||
Run: `npm --prefix frontend run build`
|
||||
|
||||
Expected: 退出码 `0`,Vite 输出生产构建产物信息,无 Vue 模板或 SCSS 编译错误。
|
||||
|
||||
- [ ] **Step 7: 检查变更范围**
|
||||
|
||||
Run: `git diff -- frontend/src/views/data-process/create/TaskSetupStep.vue frontend/scripts/regression-data-process-wizard.mjs`
|
||||
|
||||
Expected: 当前目录不是 Git 仓库时,该命令会报告仓库缺失;改用
|
||||
`diff -u <(git show HEAD:...) ...` 不可用,因此使用 `sed` 复查两个文件的目标区段,
|
||||
确认没有修改上传、格式限制或父组件事件。
|
||||
@@ -0,0 +1,131 @@
|
||||
# 源数据上传文件分页 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:** 将紧凑源文件列表限制为每页最多 10 条,并在文件总数超过 10 时使用分页器切换文件。
|
||||
|
||||
**Architecture:** 分页状态只在 `TaskSetupStep` 内维护;父组件仍只提供完整的
|
||||
`uploadedFiles` 数组和既有上传、删除事件。组件通过计算属性得到当前页文件,并在
|
||||
文件数变化后跳转到新增文件所在的末页或将删除后的空页回退到有效页。
|
||||
|
||||
**Tech Stack:** Vue 3 `<script setup>`、TypeScript、Element Plus、SCSS、Node.js 回归脚本。
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 不新增依赖、组件、数据字段或父子组件事件。
|
||||
- 每页固定显示最多 10 个文件;只有第 11 个文件出现分页器。
|
||||
- 不再使用文件列表内部滚动承载额外文件;分页器承担跨页浏览。
|
||||
- 新增文件自动切换到最后一页;删除后当前页不存在时回退到最后一个有效页。
|
||||
- 空状态的大尺寸上传区、有文件时的小型继续上传入口、格式限制、校验状态和逐项
|
||||
删除行为必须保持不变。
|
||||
- 当前工作区不是 Git 仓库,本计划不包含提交操作。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 十条一页的文件列表
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/views/data-process/create/TaskSetupStep.vue:1-45,158-184,350-420`
|
||||
- Modify: `frontend/scripts/regression-data-process-wizard.mjs:65-140`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `uploadedFiles` 完整数组和既有 `file-change`、`remove-file` 事件。
|
||||
- Produces: 组件内部的 `currentFilePage`、`pagedUploadedFiles` 与只在超出 10 条时显示的
|
||||
Element Plus 分页器;不新增对外接口。
|
||||
|
||||
- [ ] **Step 1: 先添加分页回归断言**
|
||||
|
||||
在 `frontend/scripts/regression-data-process-wizard.mjs` 的 `TaskSetupStep.vue` 检查中追加:
|
||||
|
||||
```js
|
||||
for (const marker of [
|
||||
'const FILE_PAGE_SIZE = 10',
|
||||
'const currentFilePage = ref(1)',
|
||||
'const pagedUploadedFiles = computed',
|
||||
'v-for="file in pagedUploadedFiles"',
|
||||
'class="uploaded-file-pagination"',
|
||||
':page-size="FILE_PAGE_SIZE"',
|
||||
'uploadedFiles.length > FILE_PAGE_SIZE',
|
||||
]) {
|
||||
assert.ok(taskSetupSource.includes(marker), `文件分页缺少:${marker}`)
|
||||
}
|
||||
assert.match(taskSetupSource, /newLength > oldLength[\s\S]*currentFilePage\.value = totalPages/, '新增文件后没有跳到最后一页')
|
||||
assert.match(taskSetupSource, /Math\.min\(currentFilePage\.value, totalPages\)/, '删除文件后没有回退到有效页')
|
||||
assert.doesNotMatch(taskSetupSource, /\.uploaded-file-items\s*\{[\s\S]*overflow-y:\s*auto/, '文件列表仍依赖内部滚动')
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行回归脚本,确认新增断言失败**
|
||||
|
||||
Run: `npm --prefix frontend run test:data-process-wizard`
|
||||
|
||||
Expected: FAIL,错误信息包含 `文件分页缺少:const FILE_PAGE_SIZE = 10`。
|
||||
|
||||
- [ ] **Step 3: 增加组件内分页状态与页码校正**
|
||||
|
||||
在 `TaskSetupStep.vue` 中将 Vue 导入扩展为 `computed, ref, watch`,并在
|
||||
`uploadAccept` 之后添加:
|
||||
|
||||
```ts
|
||||
const FILE_PAGE_SIZE = 10
|
||||
const currentFilePage = ref(1)
|
||||
const totalFilePages = computed(() => Math.max(1, Math.ceil(props.uploadedFiles.length / FILE_PAGE_SIZE)))
|
||||
const pagedUploadedFiles = computed(() => {
|
||||
const start = (currentFilePage.value - 1) * FILE_PAGE_SIZE
|
||||
return props.uploadedFiles.slice(start, start + FILE_PAGE_SIZE)
|
||||
})
|
||||
|
||||
watch(() => props.uploadedFiles.length, (newLength, oldLength) => {
|
||||
const totalPages = Math.max(1, Math.ceil(newLength / FILE_PAGE_SIZE))
|
||||
currentFilePage.value = newLength > oldLength
|
||||
? totalPages
|
||||
: Math.min(currentFilePage.value, totalPages)
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 使用当前页文件并添加紧凑分页器**
|
||||
|
||||
将文件行循环改为 `v-for="file in pagedUploadedFiles"`。在 `.uploaded-file-items`
|
||||
之后、`</section>` 之前插入:
|
||||
|
||||
```vue
|
||||
<el-pagination
|
||||
v-if="uploadedFiles.length > FILE_PAGE_SIZE"
|
||||
v-model:current-page="currentFilePage"
|
||||
:page-size="FILE_PAGE_SIZE"
|
||||
:total="uploadedFiles.length"
|
||||
:pager-count="5"
|
||||
small
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
class="uploaded-file-pagination"
|
||||
/>
|
||||
```
|
||||
|
||||
保留现有列表语义、继续上传入口、状态、删除按钮和 `aria-label`。
|
||||
|
||||
- [ ] **Step 5: 移除列表滚动样式并添加分页器间距**
|
||||
|
||||
将 `.uploaded-file-items` 中的 `max-height` 与 `overflow-y` 删除,确保 10 条以内
|
||||
由页面自然高度承载;追加:
|
||||
|
||||
```scss
|
||||
.uploaded-file-pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 10px 14px;
|
||||
border-top: 1px solid #edf0f5;
|
||||
}
|
||||
```
|
||||
|
||||
在窄屏媒体查询中将分页器改为水平居中,避免与文件操作区争夺宽度。
|
||||
|
||||
- [ ] **Step 6: 验证回归与编译**
|
||||
|
||||
Run: `npm --prefix frontend run test:data-process-wizard`
|
||||
|
||||
Expected: 文件分页新增断言全部通过;脚本若非零,只能在既有父页面的
|
||||
`.wizard-content` / `min-height: 0` 断言处终止。
|
||||
|
||||
Run: `cd frontend && npm exec vite build`
|
||||
|
||||
Expected: 退出码 `0`,无 Vue 模板或 SCSS 编译错误。
|
||||
@@ -0,0 +1,128 @@
|
||||
# 训练日志详情页双栏改版 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:** 将训练日志详情首屏改造成用户选定的双栏任务档案布局,同时保持原有数据与日志行为。
|
||||
|
||||
**Architecture:** 继续由 `TrainingLogView.vue` 负责数据加载与页面编排,仅重写首屏模板和 scoped SCSS。使用原生语义元素与 CSS Grid,不新增依赖或全局组件,避免影响其他详情页。
|
||||
|
||||
**Tech Stack:** Vue 3、TypeScript、Element Plus、SCSS、Node.js 原生断言脚本
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 保留所有 API 请求、5 秒轮询、指标解析、ECharts 和日志输出逻辑。
|
||||
- 使用现有 Indigo/Slate 视觉 token,不改全局主题。
|
||||
- 灰色 `#f3f5f8` 只作为应用外壳留白;所有业务路由必须渲染在统一的白色圆角页面画布内。
|
||||
- 缺失参数显示“未配置”,缺失输出模型显示“暂未生成”。
|
||||
- `<= 1100px` 主体改单栏,折叠按钮具备 `aria-expanded`。
|
||||
- 不新增依赖。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 锁定布局与可访问性回归
|
||||
|
||||
**Files:**
|
||||
- Create: `frontend/scripts/regression-training-log-layout.mjs`
|
||||
- Modify: `frontend/package.json`
|
||||
- Test: `frontend/scripts/regression-training-log-layout.mjs`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `frontend/src/views/system/TrainingLogView.vue` 源文件。
|
||||
- Produces: `npm run test:training-log-layout` 专项回归命令。
|
||||
|
||||
- [ ] **Step 1: 写入失败测试**
|
||||
|
||||
测试必须断言:双栏容器、任务/数据集/运行概况分组、语义化参数折叠按钮、`aria-expanded`、缺失值文案和 `1100px` 响应式断点存在;首屏旧 `el-descriptions` 结构已移除。
|
||||
|
||||
- [ ] **Step 2: 运行并确认 RED**
|
||||
|
||||
Run: `cd frontend && npm run test:training-log-layout`
|
||||
Expected: FAIL,提示缺少 `.overview-layout` 等新结构。
|
||||
|
||||
- [ ] **Step 3: 在 package scripts 暴露测试命令**
|
||||
|
||||
```json
|
||||
"test:training-log-layout": "node scripts/regression-training-log-layout.mjs"
|
||||
```
|
||||
|
||||
### Task 2: 实现双栏任务档案首屏
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/views/system/TrainingLogView.vue`
|
||||
- Test: `frontend/scripts/regression-training-log-layout.mjs`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: 现有 `task`、`dataset`、`modelsStore`、枚举映射与 `paramsExpanded`。
|
||||
- Produces: `.overview-layout`、`.profile-section`、`.runtime-panel`、`.parameter-groups` 和语义化 `.params-toggle-button`。
|
||||
|
||||
- [ ] **Step 1: 重写任务与数据集模板**
|
||||
|
||||
使用一个顶层 `PageCard` 承载任务头和双栏内容;左栏用定义列表表达任务信息,数据集使用名称/类型/描述加指标带;右栏使用纵向运行概况项。
|
||||
|
||||
- [ ] **Step 2: 重写参数分组模板**
|
||||
|
||||
训练参数和 LoRA 参数改为双列轻分隔行,所有缺失值用 `未配置`,折叠按钮绑定 `:aria-expanded="paramsExpanded"`。
|
||||
|
||||
- [ ] **Step 3: 实现视觉与响应式 SCSS**
|
||||
|
||||
添加双栏、信息行、指标带、焦点态和 `@media (max-width: 1100px)` / `700px` 规则;不修改全局样式。
|
||||
|
||||
- [ ] **Step 4: 运行并确认 GREEN**
|
||||
|
||||
Run: `cd frontend && npm run test:training-log-layout`
|
||||
Expected: PASS,输出“训练日志详情布局回归检查通过”。
|
||||
|
||||
### Task 2.5: 统一页面背景与内容表面
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/styles/index.scss`
|
||||
- Modify: `frontend/src/layouts/MainLayout.vue`
|
||||
- Modify: `frontend/src/views/system/TrainingLogView.vue`
|
||||
- Create: `frontend/scripts/regression-page-surface.mjs`
|
||||
|
||||
- [ ] **Step 1: 先写并运行背景层回归检查**
|
||||
|
||||
Run: `cd frontend && npm run test:page-surface`
|
||||
Expected: 首次 FAIL,提示缺少 `--app-shell-bg` 和全局 `.page-canvas`。
|
||||
|
||||
- [ ] **Step 2: 添加全局背景与表面 token**
|
||||
|
||||
定义灰色外壳 `--app-shell-bg: #f3f5f8`、白色页面画布 `--app-page-bg: #ffffff` 和白色内容表面 `--app-surface-bg: #ffffff`。在 `MainLayout` 中用 `.page-canvas` 包裹所有业务路由,并消除直接根 `PageCard` 的重复阴影。
|
||||
|
||||
- [ ] **Step 3: 运行并确认 GREEN**
|
||||
|
||||
Run: `cd frontend && npm run test:page-surface`
|
||||
Expected: PASS,输出“全局页面背景与内容表面回归检查通过”。
|
||||
|
||||
### Task 3: 类型、构建与视觉 QA
|
||||
|
||||
**Files:**
|
||||
- Create: `design-qa.md`
|
||||
- Modify: `frontend/src/views/system/TrainingLogView.vue`(仅在 QA 发现 P0/P1/P2 时)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: 选定视觉稿、浏览器渲染截图。
|
||||
- Produces: 通过的类型检查、生产构建和 `design-qa.md`。
|
||||
|
||||
- [ ] **Step 1: 静态验证**
|
||||
|
||||
Run: `cd frontend && npm run type-check`
|
||||
Expected: 项目引用模式能检查真实源码;若仓库原有错误仍存在,输出中不得包含本次修改的训练日志页、布局或全局样式。
|
||||
|
||||
Run: `cd frontend && npx vite build`
|
||||
Expected: exit 0。
|
||||
|
||||
Run: `cd frontend && npm run build`
|
||||
Expected: 在类型检查修复前仍会被仓库原有错误阻断,必须记录实际错误文件。
|
||||
|
||||
- [ ] **Step 2: 启动并检查页面**
|
||||
|
||||
启动 Vite,打开 `/login`,使用 mock 账号进入 `/training-log/1`,在 1440px 宽视口检查双栏、折叠交互和控制台错误。
|
||||
|
||||
- [ ] **Step 3: 执行 Design QA**
|
||||
|
||||
将选定视觉稿与实现截图放在同一比较输入中,检查字体、间距、颜色、图标、文案和交互;修复所有 P0/P1/P2 后更新 `design-qa.md` 为 `final result: passed`。
|
||||
|
||||
- [ ] **Step 4: 最终验证**
|
||||
|
||||
重新运行专项回归、类型检查和构建,并记录实际结果。
|
||||
Reference in New Issue
Block a user