docs: 添加设计文档与视觉走查记录
视觉走查记录、设计方案规格(specs)、实施计划(plans)及配套截图资产,覆盖路由过渡、页面表层级、数据处理向导、训练日志重设计等改进项。
160
design-qa.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# Training Log Detail Design QA
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Source visual truth: `docs/superpowers/specs/assets/training-log-detail-option-2.png`
|
||||||
|
- Implementation screenshot: `docs/superpowers/specs/assets/training-log-detail-final-expanded-1440.png`
|
||||||
|
- Collapsed implementation screenshot with global surface: `docs/superpowers/specs/assets/training-log-detail-global-surface-1440-v2.png`
|
||||||
|
- Normalized full-view comparison: `docs/superpowers/specs/assets/training-log-detail-final-comparison-normalized.png`
|
||||||
|
- Focused parameter comparison: `docs/superpowers/specs/assets/training-log-detail-final-comparison-params.png`
|
||||||
|
- White-canvas reference: `docs/superpowers/specs/assets/page-white-canvas-reference.png`
|
||||||
|
- White-canvas implementation: `docs/superpowers/specs/assets/model-edit-white-page-canvas-final-1440.png`
|
||||||
|
- White-canvas normalized comparison: `docs/superpowers/specs/assets/page-white-canvas-comparison.png`
|
||||||
|
- Training-log white-canvas screenshot: `docs/superpowers/specs/assets/training-log-detail-white-page-canvas-1440.png`
|
||||||
|
- Self-surface list screenshot: `docs/superpowers/specs/assets/fine-tune-list-self-surface-final-1440.png`
|
||||||
|
- Default-canvas detail screenshot: `docs/superpowers/specs/assets/training-log-detail-default-canvas-final-1440.png`
|
||||||
|
- Reference/detail comparison: `docs/superpowers/specs/assets/page-surface-reference-detail-comparison.png`
|
||||||
|
- Create-page duplicate-surface evidence: `docs/superpowers/specs/assets/fine-tune-create-double-surface-before-1440.png`
|
||||||
|
- Create-page single-surface evidence: `docs/superpowers/specs/assets/fine-tune-create-single-surface-final-1440.png`
|
||||||
|
- Route-transition flash reference: `/var/folders/nk/yks07zp14wb4rv3jqq0pt_4h0000gn/T/codex-clipboard-ea775434-828f-4bca-9714-72887faa9af9.png`
|
||||||
|
- Route-transition final detail frame: `docs/superpowers/specs/assets/route-transition-detail-final.png`
|
||||||
|
- Viewport: 1440 × 1024; comparison content normalized to 1200 × 800 after removing the existing 240px sidebar and 60px header from the implementation capture.
|
||||||
|
- State: `finance-sft-001`, completed, mock data loaded, training parameters expanded.
|
||||||
|
|
||||||
|
## Full-view comparison
|
||||||
|
|
||||||
|
The implementation preserves the selected two-column hierarchy: task and dataset information occupy the wide left track, runtime facts use the narrow right rail, and training parameters continue as a full-width disclosure section. The selected mock omitted the product shell, so the comparison intentionally crops the existing sidebar and header rather than treating them as design drift.
|
||||||
|
|
||||||
|
The global product mode now uses two intentional surface modes. Form and detail routes render inside one `#ffffff` page canvas with 16px radius and 24px content padding. List routes that already own a white table/card surface render that surface directly on the `#f3f5f8` application background, avoiding a redundant white layer.
|
||||||
|
|
||||||
|
## Required fidelity surfaces
|
||||||
|
|
||||||
|
- Fonts and typography: Existing system font stack is preserved. Heading, label, value, and muted-copy hierarchy match the selected direction; output model uses body-level contrast after iteration 1, and all “未配置” values use `#64748b` on white after iteration 2.
|
||||||
|
- Spacing and layout rhythm: 24px main gap, 16px section gap, 12px surface radius, and light row separators match the selected composition. The existing application shell reduces usable content width, but normalized proportions remain aligned.
|
||||||
|
- Colors and tokens: Indigo accent, Slate text, success status, `#f3f5f8` page background, and white content surfaces are consistent with the current product.
|
||||||
|
- Image and icon fidelity: The screen contains no raster imagery. Existing Font Awesome icons are retained to match the repository's icon system; no placeholder, emoji, CSS drawing, or handcrafted SVG was introduced.
|
||||||
|
- Copy and content: Task name, status, model, date, duration, dataset metadata, storage, SFT, LoRA, and missing-value copy match the selected design and actual mock data.
|
||||||
|
|
||||||
|
## Interaction and responsive checks
|
||||||
|
|
||||||
|
- Parameter disclosure changed from `aria-expanded="false"` to `true` after activation, and the expanded content became visible.
|
||||||
|
- At 1000px viewport width, the overview changed to one column and the document had no horizontal overflow.
|
||||||
|
- At 700px viewport width, dataset metrics and parameter rows changed to one column and the document had no horizontal overflow.
|
||||||
|
- Browser console: no errors. One existing Element Plus `el-link` underline deprecation warning was emitted by the login flow and is unrelated to this page.
|
||||||
|
|
||||||
|
## Comparison history
|
||||||
|
|
||||||
|
### Iteration 1 — blocked
|
||||||
|
|
||||||
|
- [P2] The implementation added a visible “基础训练参数” heading that did not exist in the selected mock, creating extra vertical space.
|
||||||
|
- [P2] “暂未生成” was styled too faintly compared with the selected design.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
- Removed the redundant visible base-parameter heading while retaining an accessible region label.
|
||||||
|
- Restored body-level contrast for “暂未生成”.
|
||||||
|
|
||||||
|
Post-fix evidence:
|
||||||
|
|
||||||
|
- `docs/superpowers/specs/assets/training-log-detail-final-comparison-normalized.png`
|
||||||
|
- `docs/superpowers/specs/assets/training-log-detail-final-comparison-params.png`
|
||||||
|
|
||||||
|
### Iteration 2 — blocked
|
||||||
|
|
||||||
|
- [P2] “未配置” values used `#94a3b8` on white, below WCAG AA contrast for 14px text.
|
||||||
|
|
||||||
|
Fix:
|
||||||
|
|
||||||
|
- Updated muted values to `#64748b`; the regression check now calculates the contrast ratio and requires at least 4.5:1.
|
||||||
|
|
||||||
|
Post-fix evidence:
|
||||||
|
|
||||||
|
- Browser computed color: `rgb(100, 116, 139)`.
|
||||||
|
- Browser console: no errors.
|
||||||
|
|
||||||
|
### Iteration 3 — passed
|
||||||
|
|
||||||
|
No actionable P0/P1/P2 differences remain. The retained P3 difference is that the generated mock does not include the real product sidebar/header; this is an intentional constraint because the existing shell is shared by every page.
|
||||||
|
|
||||||
|
### Iteration 4 — clarified global page canvas, passed
|
||||||
|
|
||||||
|
- [P1] The earlier interpretation left the route content directly on the gray layout background and only made individual cards white. The clarified reference requires a single white page canvas behind every route.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
- Split the shell and page tokens into `--app-shell-bg: #f3f5f8` and `--app-page-bg: #ffffff`.
|
||||||
|
- Added one global `.page-canvas` around every route in `MainLayout.vue`.
|
||||||
|
- Added 16px outer gutter, 16px canvas radius, 24px canvas padding, and a subtle canvas shadow.
|
||||||
|
- Flattened a route-root `PageCard` to prevent a duplicate large card layer.
|
||||||
|
|
||||||
|
Post-fix evidence:
|
||||||
|
|
||||||
|
- `docs/superpowers/specs/assets/page-white-canvas-comparison.png`
|
||||||
|
- Browser computed canvas: white background, 16px radius, 24px padding; outer shell: `rgb(243, 245, 248)`.
|
||||||
|
- Both the model-edit page and training-log page render inside the same global white canvas without horizontal overflow.
|
||||||
|
|
||||||
|
### Iteration 5 — corrected list-page surface ownership, passed
|
||||||
|
|
||||||
|
- [P1] Applying the white page canvas to every route created a redundant layer on list pages because `DataTablePage`, model evaluation, and model management already provide their own white root card.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
- Added explicit `pageSurface: 'self'` metadata to each self-surfaced list route: model tuning, model evaluation, model inference, model management, data processing, and dataset management.
|
||||||
|
- Added `.page-canvas.is-self-surface` to remove the outer canvas padding, radius, background, and shadow only for those routes.
|
||||||
|
- Preserved the default white canvas for training-log, create, edit, preview, chat, and result routes.
|
||||||
|
|
||||||
|
Post-fix evidence:
|
||||||
|
|
||||||
|
- `docs/superpowers/specs/assets/fine-tune-list-self-surface-final-1440.png`
|
||||||
|
- `docs/superpowers/specs/assets/training-log-detail-default-canvas-final-1440.png`
|
||||||
|
- `docs/superpowers/specs/assets/page-surface-reference-detail-comparison.png`
|
||||||
|
- Browser computed list state: transparent outer canvas, 0px padding/radius, no shadow; white 12px-radius list card on `rgb(243, 245, 248)` shell.
|
||||||
|
- Browser computed detail state: white outer canvas, 24px padding, 16px radius, subtle shadow.
|
||||||
|
- Both states have no horizontal overflow and no console errors at 1440 × 900.
|
||||||
|
|
||||||
|
### Iteration 6 — flattened wrapped root PageCard, passed
|
||||||
|
|
||||||
|
- [P1] The training-task creation route wraps its root `PageCard` in `.fine-tune-create`. The earlier selector only matched a `PageCard` directly under `.page-canvas`, so this page retained a second white background, 12px radius, and card shadow.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
- Added an explicit `.page-card-host` marker to the training-task creation route root; the layout flattens only a directly rendered root `PageCard` or a `PageCard` inside that explicit host.
|
||||||
|
- Root `PageCard` now uses a transparent background, 0px radius, no shadow, and no bottom margin while preserving its header/body layout.
|
||||||
|
- Kept the selector excluded from `.is-self-surface`, so list cards retain their own white background, 12px radius, and shadow.
|
||||||
|
- Rejected a generic one-level descendant selector because it would also match the training-log parameter card.
|
||||||
|
|
||||||
|
Post-fix evidence:
|
||||||
|
|
||||||
|
- `docs/superpowers/specs/assets/fine-tune-create-double-surface-before-1440.png`
|
||||||
|
- `docs/superpowers/specs/assets/fine-tune-create-single-surface-final-1440.png`
|
||||||
|
- Browser computed create-page root card: transparent background, 0px radius, no shadow; outer canvas remains white with 24px padding.
|
||||||
|
- Browser computed list-page card remains white with 12px radius and subtle shadow on a transparent outer canvas.
|
||||||
|
- Browser computed training-log parameter card remains white with 12px radius and subtle shadow, confirming that internal business cards are not flattened.
|
||||||
|
- Both pages have no horizontal overflow; create-page console has no errors.
|
||||||
|
|
||||||
|
### Iteration 7 — removed page-level opacity transition, passed
|
||||||
|
|
||||||
|
- [P1] When navigating from a self-surface list to a default-canvas secondary page, `route.meta.pageSurface` changed immediately while the old list remained for the 150ms `out-in` leave animation. The result was a semi-transparent old list rendered inside the new white canvas.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
- Removed the page-level Vue `transition` wrapper from `MainLayout.vue`.
|
||||||
|
- Removed the `.fade-enter-*` and `.fade-leave-*` opacity rules.
|
||||||
|
- Preserved local component animations such as dialogs, disclosures, and the selected-row batch bar.
|
||||||
|
|
||||||
|
Post-fix evidence:
|
||||||
|
|
||||||
|
- Source flash frame: `/var/folders/nk/yks07zp14wb4rv3jqq0pt_4h0000gn/T/codex-clipboard-ea775434-828f-4bca-9714-72887faa9af9.png`.
|
||||||
|
- Final detail frame: `docs/superpowers/specs/assets/route-transition-detail-final.png`.
|
||||||
|
- Immediate state after list → create: old list absent, route-root opacity `1`, zero `.fade-*` transition elements, correct default canvas.
|
||||||
|
- Immediate state after create → list: old create page absent, route-root opacity `1`, zero `.fade-*` transition elements, correct self-surface canvas.
|
||||||
|
- Immediate state after list → training log: old list absent, route-root opacity `1`, zero `.fade-*` transition elements, correct default canvas.
|
||||||
|
- All three paths had no horizontal overflow; browser console had no errors.
|
||||||
|
|
||||||
|
## Build evidence gap
|
||||||
|
|
||||||
|
The `type-check` script now uses project-reference mode (`vue-tsc -b --noEmit`) so it no longer reports a false pass. `npm run type-check` and `npm run build` remain blocked by pre-existing TypeScript errors in `src/mock/adapter.ts`, `FineTuneCreateView.vue`, and `FineTuneListView.vue`; no remaining error points to `TrainingLogView.vue` or the page-surface files. `npx vite build` succeeds, proving the updated UI bundles for production.
|
||||||
|
|
||||||
|
Design-QA final result: passed
|
||||||
|
|
||||||
|
final result: passed
|
||||||
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 6801`
|
||||||
|
|
||||||
|
Browser checks at `http://localhost:6801/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 覆盖。
|
||||||
|
- 未引入新依赖;计划中所有类型和事件名在前置任务中已有定义。
|
||||||
|
|
||||||
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
@@ -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
@@ -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
@@ -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: 最终验证**
|
||||||
|
|
||||||
|
重新运行专项回归、类型检查和构建,并记录实际结果。
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
# 前端视觉与样式系统重构设计
|
||||||
|
|
||||||
|
**日期**:2026-07-09
|
||||||
|
**范围**:`frontend/` (Vue 3 + Element Plus + Pinia)
|
||||||
|
**目标用户**:内部数据/算法工程师,桌面端浏览器使用
|
||||||
|
|
||||||
|
## 背景与动机
|
||||||
|
|
||||||
|
当前前端已用 Element Plus + 自定义 SCSS 变量搭起一套"深色侧边栏 + 浅色内容区"的现代 B 端布局,但实际页面观感"乱"且有功能性瑕疵,根因不是设计语言本身有问题,而是**设计系统没有真正落地**:
|
||||||
|
|
||||||
|
- 颜色散落硬编码(`#1e293b`、`#475569`、`#909399`、`#1890ff`、`#67c23a`…)
|
||||||
|
- Element Plus 旧默认色与新主题色(`#4f46e5`)并存
|
||||||
|
- 用了 FontAwesome **4.7.0**(2017 年停止维护),大量 `fa-cogs`/`fa-hdd-o` 这种短横线后缀命名
|
||||||
|
- 登录页(`LoginView.vue`)背景是纯色 `#dbe0f9`,与主应用玻璃拟态风格割裂
|
||||||
|
- `AppHeader.vue:189` 引用了主题里**不存在**的 `--warning-color` 变量,导致回退到默认值
|
||||||
|
- `index.scss` 内有 5+ 处 `!important`,说明覆盖策略不对
|
||||||
|
- 装饰性动画(按钮悬停 `translateY(-1px)`、`pulse` 动画、过度渐变)增加视觉噪音
|
||||||
|
|
||||||
|
本次重构目标是**收口样式系统**,让"主题改一处,全站生效",并消除明显观感问题。
|
||||||
|
|
||||||
|
## 范围与非范围
|
||||||
|
|
||||||
|
### 范围内
|
||||||
|
|
||||||
|
1. 统一颜色变量系统(消除硬编码、补齐缺失变量)
|
||||||
|
2. 图标库迁移:FA4 → **Element Plus Icons**(`@element-plus/icons-vue`,已与 Element Plus 同步)
|
||||||
|
3. 重新设计登录页,与主应用视觉一致
|
||||||
|
4. 修复 `AppHeader.vue` 的 `--warning-color` 缺失问题
|
||||||
|
5. 整理 `index.scss` 的 `!important` 覆盖,改用更高特异性选择器
|
||||||
|
6. 适度降低装饰性效果:移除按钮悬停位移、统一卡片阴影阶梯
|
||||||
|
|
||||||
|
### 非范围(本次不做)
|
||||||
|
|
||||||
|
- 布局结构改动(保留 260px 侧边栏 + 60px 顶栏)
|
||||||
|
- 深色模式切换
|
||||||
|
- 任何业务逻辑修改
|
||||||
|
- 移动端适配(当前项目无移动端需求)
|
||||||
|
- `web/` 静态版不动
|
||||||
|
|
||||||
|
## 方案选型
|
||||||
|
|
||||||
|
### 选定方案:方案 A — 克制重塑
|
||||||
|
|
||||||
|
保留现有"深色侧边栏 + 浅色内容区"骨架,做样式系统收口。
|
||||||
|
|
||||||
|
**未选 方案 B(极简重做)**:改动面太广,10+ 视图都要适配,回报/风险比差。
|
||||||
|
**未选 方案 C(全深色科技感)**:Element Plus 暗色需要大量单独调校,工作量与当前诉求不匹配。
|
||||||
|
|
||||||
|
## 设计
|
||||||
|
|
||||||
|
### 1. 颜色变量系统(`src/styles/index.scss` 重构)
|
||||||
|
|
||||||
|
**目标**:所有颜色、阴影、圆角都走 CSS 变量;后续改主题只改 `:root` 即可。
|
||||||
|
|
||||||
|
**新增/补齐变量**(`src/styles/index.scss` 顶部):
|
||||||
|
|
||||||
|
```scss
|
||||||
|
:root {
|
||||||
|
/* Element Plus 主色(已有) */
|
||||||
|
--el-color-primary: #4f46e5;
|
||||||
|
--el-color-primary-light-3: #818cf8;
|
||||||
|
--el-color-primary-light-5: #a5b4fc;
|
||||||
|
--el-color-primary-light-7: #c7d2fe;
|
||||||
|
--el-color-primary-light-8: #e0e7ff;
|
||||||
|
--el-color-primary-light-9: #eef2ff;
|
||||||
|
--el-color-primary-dark-2: #4338ca;
|
||||||
|
|
||||||
|
/* 状态色(已有) */
|
||||||
|
--el-color-success: #10b981;
|
||||||
|
--el-color-warning: #f59e0b; /* 修复:之前未显式定义 */
|
||||||
|
--el-color-danger: #ef4444;
|
||||||
|
--el-color-info: #64748b;
|
||||||
|
|
||||||
|
/* 项目别名(已有 + 新增) */
|
||||||
|
--primary-color: var(--el-color-primary);
|
||||||
|
--primary-hover: var(--el-color-primary-light-3);
|
||||||
|
--success-color: var(--el-color-success);
|
||||||
|
--warning-color: var(--el-color-warning); /* 新增:修复 AppHeader 引用 */
|
||||||
|
--danger-color: var(--el-color-danger);
|
||||||
|
|
||||||
|
/* 圆角(已有) */
|
||||||
|
--el-border-radius-base: 8px;
|
||||||
|
--el-border-radius-small: 6px;
|
||||||
|
--el-border-radius-round: 20px;
|
||||||
|
|
||||||
|
/* ========== 新增:项目语义化变量 ========== */
|
||||||
|
|
||||||
|
/* 文字 */
|
||||||
|
--text-primary: #1e293b; /* 替代硬编码 #1e293b */
|
||||||
|
--text-secondary: #475569; /* 替代 #475569 */
|
||||||
|
--text-tertiary: #64748b; /* 替代 #64748b */
|
||||||
|
--text-placeholder: #94a3b8; /* 替代 #94a3b8 */
|
||||||
|
|
||||||
|
/* 背景 */
|
||||||
|
--bg-page: #f8fafc; /* 替代 --el-bg-color-page */
|
||||||
|
--bg-card: #ffffff;
|
||||||
|
--bg-subtle: #f1f5f9; /* 替代硬编码 #f1f5f9 */
|
||||||
|
--bg-hover: #f8fafc; /* 替代硬编码 #f8fafc */
|
||||||
|
|
||||||
|
/* 边框 */
|
||||||
|
--border-subtle: #e2e8f0; /* 替代硬编码 #e2e8f0 */
|
||||||
|
--border-base: #f1f5f9; /* 替代硬编码 #f1f5f9 */
|
||||||
|
|
||||||
|
/* 侧边栏(已有) */
|
||||||
|
--sidebar-bg: #0f172a;
|
||||||
|
--sidebar-text: #94a3b8;
|
||||||
|
--sidebar-active-bg: rgba(79, 70, 229, 0.15);
|
||||||
|
--sidebar-active-text: #ffffff;
|
||||||
|
--sidebar-section-title: #64748b;
|
||||||
|
|
||||||
|
/* 阴影(统一阶梯) */
|
||||||
|
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||||
|
--shadow-md: 0 2px 8px rgba(15, 23, 42, 0.06); /* 替代 --card-shadow */
|
||||||
|
--shadow-lg: 0 8px 24px rgba(15, 23, 42, 0.08);
|
||||||
|
--shadow-dialog: 0 20px 40px rgba(15, 23, 42, 0.12);
|
||||||
|
|
||||||
|
/* 尺寸 */
|
||||||
|
--header-height: 60px;
|
||||||
|
--sidebar-width: 260px;
|
||||||
|
--content-padding-x: 32px;
|
||||||
|
--content-padding-y: 32px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**改动原则**:
|
||||||
|
- 组件 SCSS 内的硬编码颜色全部替换为对应变量
|
||||||
|
- 任何新增颜色需求都先在 `:root` 定义,再引用
|
||||||
|
- 不再使用 `!important`;如确需覆盖,提升特异性(如 `:deep(.el-button)`)
|
||||||
|
|
||||||
|
### 2. 图标库迁移:FA4 → Element Plus Icons
|
||||||
|
|
||||||
|
**为什么选 Element Plus Icons**:
|
||||||
|
- 零新增依赖成本(仅在 `package.json` 增加一行)
|
||||||
|
- 与 Element Plus 组件视觉风格天然一致
|
||||||
|
- 树摇友好,按需引入
|
||||||
|
- SVG 渲染,可继承 `currentColor` 与 `font-size`,比 FA 字体图标更现代
|
||||||
|
|
||||||
|
**实施**:
|
||||||
|
1. `npm i @element-plus/icons-vue`
|
||||||
|
2. `main.ts` 全局注册常用图标(按需在组件内 import 也可)
|
||||||
|
3. 替换规则(在每个 view/component 内做局部替换):
|
||||||
|
|
||||||
|
| FA4 类名 | Element Plus 图标 | 用在哪 |
|
||||||
|
|---------|------------------|--------|
|
||||||
|
| `fa-cogs` | `Setting` | 侧边栏"模型调优" |
|
||||||
|
| `fa-line-chart` | `TrendCharts` | 侧边栏"模型评测" |
|
||||||
|
| `fa-server` | `Monitor` | 侧边栏"模型推理" |
|
||||||
|
| `fa-cube` | `Box` | 侧边栏"模型管理"、Logo |
|
||||||
|
| `fa-file-text` | `Document` | 侧边栏"数据集" |
|
||||||
|
| `fa-wrench` | `Tools` | 侧边栏"其他工具" |
|
||||||
|
| `fa-bar-chart` | `DataAnalysis` | 侧边栏"平台性能" |
|
||||||
|
| `fa-search` | `Search` | 搜索框前缀 |
|
||||||
|
| `fa-plus` | `Plus` | 新建按钮 |
|
||||||
|
| `fa-trash` | `Delete` | 删除按钮 |
|
||||||
|
| `fa-arrow-left` | `ArrowLeft` | 返回按钮 |
|
||||||
|
| `fa-microchip` | `Cpu` | 性能监控 |
|
||||||
|
| `fa-database` | `Files` | 内存监控(数据文件堆) |
|
||||||
|
| `fa-hdd-o` | `Box` | 磁盘监控(存储箱) |
|
||||||
|
| `fa-sign-out` | `SwitchButton` | 退出登录 |
|
||||||
|
| `fa-angle-down` | `ArrowDown` | 下拉箭头 |
|
||||||
|
| `fa-user-circle-o` | `User` | 个人中心 |
|
||||||
|
| `fa-cog` | `Setting` | 系统设置 |
|
||||||
|
| `fa-stop-circle-o` | `VideoPause` | 停止训练 |
|
||||||
|
| `fa-pencil` | `Edit` | 编辑 |
|
||||||
|
| `fa-times` | `Close` | 关闭 |
|
||||||
|
| `fa-globe` | `Connection` | 网络流量 |
|
||||||
|
| `fa-info-circle` | `InfoFilled` | 系统信息 |
|
||||||
|
| `fa-user-o` | `User` | 登录页账号图标 |
|
||||||
|
| `fa-lock-o` | `Lock` | 登录页密码图标 |
|
||||||
|
| `fa-sign-in` | `Right` | 登录按钮 |
|
||||||
|
|
||||||
|
**注意点**:
|
||||||
|
- 替换后调整图标尺寸(Element Plus Icons 用 `size` prop 或 CSS `font-size`)
|
||||||
|
- 部分 FA4 类名(如 `fa-file-text-o`)在 FA5/FA6 已无 `-o` 后缀
|
||||||
|
- `main.ts` 中删除 `import '@/assets/font-awesome/css/font-awesome.min.css'`
|
||||||
|
- 删 `src/assets/font-awesome/` 整个目录(字体文件 100+KB)
|
||||||
|
|
||||||
|
### 3. 登录页重新设计(`LoginView.vue`)
|
||||||
|
|
||||||
|
**目标**:与主应用保持一致的视觉语言,背景改用渐变 + 玻璃卡片。
|
||||||
|
|
||||||
|
**设计**:
|
||||||
|
- 背景:渐变 `linear-gradient(135deg, #4f46e5 0%, #818cf8 50%, #c7d2fe 100%)`
|
||||||
|
- 卡片:白色,圆角 16px,阴影 `var(--shadow-dialog)`
|
||||||
|
- Logo 区域:上下结构(Logo 48px → 标题 24px → 副标题 14px 灰色)
|
||||||
|
- 表单:单项之间 20px 间距,输入框 44px 高,圆角 8px
|
||||||
|
- 按钮:主色实心,44px 高,全宽
|
||||||
|
- 选项行:flex 布局,记住密码左、忘记密码右
|
||||||
|
- 整体居中,最大宽 420px
|
||||||
|
|
||||||
|
**关键 CSS 改动**:
|
||||||
|
- 背景从 `#dbe0f9` 改为 `linear-gradient(135deg, #4f46e5 0%, #818cf8 50%, #c7d2fe 100%)`
|
||||||
|
- 卡片圆角从 12px 提升到 16px
|
||||||
|
- 阴影替换为 `var(--shadow-dialog)`
|
||||||
|
- 所有内联颜色改用变量
|
||||||
|
|
||||||
|
### 4. 修复 `AppHeader.vue` 的 `--warning-color` 缺失
|
||||||
|
|
||||||
|
**问题**:`AppHeader.vue:189` 使用 `var(--warning-color)`,但 `index.scss` 里只定义了 `--danger-color`、`--success-color`,没有 `--warning-color`。
|
||||||
|
|
||||||
|
**修复**:在 `:root` 中新增 `--warning-color: var(--el-color-warning);`(已在第 1 节列入)。
|
||||||
|
|
||||||
|
### 5. 整理 `index.scss` 的 `!important`
|
||||||
|
|
||||||
|
**现有 `!important` 用法**(来自 `index.scss`):
|
||||||
|
|
||||||
|
| 选择器 | 用途 | 现状 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `.el-card { border: none !important; ... }` | 去掉 EP 默认边框 | 提升为 `.app-container .el-card` 或全局加 `:where()` 重置 |
|
||||||
|
| `.el-tag { border: none !important; ... }` | 去掉 EP 默认边框 | 同上 |
|
||||||
|
| `.el-dialog { border-radius: 16px !important; ... }` | 圆角和阴影 | 同上 |
|
||||||
|
|
||||||
|
**改写策略**:
|
||||||
|
- 用更高特异性选择器(如 `body .el-card`)替代 `!important`
|
||||||
|
- 或用 CSS Layer (`@layer reset, overrides, components`) 控制优先级
|
||||||
|
|
||||||
|
**本次采用**:直接提高特异性,避免引入 CSS Layer 复杂度(属于项目既定风格之外的引入,需要权衡)。
|
||||||
|
|
||||||
|
### 6. 降低装饰性效果
|
||||||
|
|
||||||
|
**调整清单**:
|
||||||
|
- `index.scss` 移除 `.el-button:hover { transform: translateY(-1px); box-shadow: ...; }` — 改为仅改变背景色
|
||||||
|
- `AppHeader.vue` 移除 `pulse` 动画,改为静态红色
|
||||||
|
- 侧边栏菜单图标的 `transform: scale(1.1)` 悬停动画保留(这是细节点缀,不算噪音)
|
||||||
|
- 卡片悬停 `transform` 动效保留(仅在 DataTablePage 等列表卡片上,目前没有,不新增)
|
||||||
|
- 顶栏 `backdrop-filter: blur(12px)` 保留(玻璃感是这个项目的风格语言)
|
||||||
|
|
||||||
|
## 实施步骤(落地顺序)
|
||||||
|
|
||||||
|
按风险从低到高,便于逐步验证:
|
||||||
|
|
||||||
|
1. **步骤 1**:扩展 `src/styles/index.scss` 变量(新增,不删除旧的)
|
||||||
|
2. **步骤 2**:安装 `@element-plus/icons-vue`,在 `main.ts` 注册
|
||||||
|
3. **步骤 3**:逐文件替换图标(按表格顺序)
|
||||||
|
4. **步骤 4**:逐文件替换硬编码颜色为变量
|
||||||
|
5. **步骤 5**:重新设计 `LoginView.vue`
|
||||||
|
6. **步骤 6**:删除 `src/assets/font-awesome/` 目录和 `main.ts` 的 import
|
||||||
|
7. **步骤 7**:运行 `npm run type-check` 和 `npm run build` 验证
|
||||||
|
|
||||||
|
每步完成后截图(手动)确认未引入新问题。
|
||||||
|
|
||||||
|
## 验证标准
|
||||||
|
|
||||||
|
- [ ] `npm run type-check` 通过
|
||||||
|
- [ ] `npm run build` 通过
|
||||||
|
- [ ] 浏览器控制台无 CSS 警告(无未定义变量)
|
||||||
|
- [ ] 全站不再有 `#1890ff`、`#67c23a`、`#e6a23c`、`#f56c6c` 这类 EP 旧色硬编码(grep 验证)
|
||||||
|
- [ ] 全站不再有 `class="fa fa-` 的引用(grep 验证)
|
||||||
|
- [ ] 全站不再有 `--warning-color` 这类指向不存在变量的引用
|
||||||
|
- [ ] `grep -c "!important" src/styles/index.scss` 数量从当前值下降 50% 以上
|
||||||
|
- [ ] 登录页与主应用风格一致
|
||||||
|
|
||||||
|
## 风险与回退
|
||||||
|
|
||||||
|
- **风险 1**:Element Plus Icons 不覆盖某些冷门 FA 图标(如 `fa-stop-circle-o` 训练停止)
|
||||||
|
- **应对**:在替换前先做一次图标对照表,确认每个 FA 类名都有 EP 对应项;没有则保留 FA4(不删除 FA 资源),但推进用 EP 主流图标
|
||||||
|
- **风险 2**:CSS 变量替换漏掉某处导致视觉回退
|
||||||
|
- **应对**:分阶段提交,每阶段都跑 build;如有遗漏,EP 会回退到自身默认色
|
||||||
|
- **风险 3**:`!important` 整理引入新覆盖问题
|
||||||
|
- **应对**:整理前先 `git diff` 对比编译产物颜色变化,保留 `git stash` 能力
|
||||||
|
|
||||||
|
## 后续优化(不在本次范围)
|
||||||
|
|
||||||
|
- 抽出 Design Token 文件(`tokens.scss`)做 Figma → Code 同步
|
||||||
|
- 引入 UnoCSS 或 Windi CSS 做原子化样式(取决于团队接受度)
|
||||||
|
- 给所有列表页加 skeleton 加载态
|
||||||
|
- 暗色模式支持(需要先在 EP 主题层注册暗色)
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
# 数据处理创建流程四步向导设计
|
||||||
|
|
||||||
|
日期:2026-07-10
|
||||||
|
|
||||||
|
## 1. 目标
|
||||||
|
|
||||||
|
重构 `/data-process/create`,把当前职责混杂、步骤数量会变化的流程,调整为四个语义稳定、页面内容相互独立的步骤。用户在任一时刻只处理当前任务,同时始终知道所处阶段、前后关系和唯一主操作。
|
||||||
|
|
||||||
|
本次设计沿用现有平台视觉语言:浅灰应用背景、单层白色页面画布、靛蓝主色、克制边框与阴影、固定四步进度导航和底部操作栏。不得出现页面白底中再次嵌套整页白色大卡片的双层白底。
|
||||||
|
|
||||||
|
## 2. 固定流程
|
||||||
|
|
||||||
|
顶部步骤数量始终为四个,不随结构化或非结构化类型变化:
|
||||||
|
|
||||||
|
1. **创建任务**:填写任务信息、上传源文件并选择处理类型。
|
||||||
|
2. **数据预览**:检查系统解析结果;非结构化数据进入切片预览,结构化数据进入记录预览。
|
||||||
|
3. **开始生成**:确认摘要、启动生成并查看进度或错误。
|
||||||
|
4. **结果编辑与保存**:检查并修改生成结果,通过校验后保存任务。
|
||||||
|
|
||||||
|
步骤状态只有 `未开始`、`当前`、`已完成` 三种。用户返回已完成步骤时保留已经填写或编辑的内容,不因切换处理类型而静默清空任务名称、描述和上传文件。
|
||||||
|
|
||||||
|
## 3. 第一步:创建任务
|
||||||
|
|
||||||
|
页面只显示创建任务所需内容:
|
||||||
|
|
||||||
|
- 任务名称和可选描述。
|
||||||
|
- 结构化数据、非结构化数据两种处理类型。
|
||||||
|
- 单文件上传区、文件格式和大小约束。
|
||||||
|
- 上传后的基础识别结果:文件名、大小、格式、记录数或文本长度、校验状态。
|
||||||
|
|
||||||
|
底部操作:`取消`、`保存草稿`、唯一主操作 `继续:数据预览`。
|
||||||
|
|
||||||
|
切换处理类型时仅更新后续预览和生成策略;如果已经产生预览结果,需要明确提示预览将重新计算,确认后才清除后续步骤数据。
|
||||||
|
|
||||||
|
## 4. 第二步:数据预览
|
||||||
|
|
||||||
|
### 4.1 页面职责
|
||||||
|
|
||||||
|
本页只负责核对源数据与系统预览结果的对应关系,并允许修改不合理的切片或记录内容。不得显示第一步的大量表单、第三步的生成进度或第四步的最终结果表格。
|
||||||
|
|
||||||
|
页面顶部仅保留一行来源摘要,例如:
|
||||||
|
|
||||||
|
`finance_qa.jsonl · 非结构化数据 · 8,560 个切片`
|
||||||
|
|
||||||
|
### 4.2 非结构化数据:源文件与切片对照
|
||||||
|
|
||||||
|
主体为同一白色画布中的左右分栏,使用竖向分隔线区分,不使用两个浮动大卡片:
|
||||||
|
|
||||||
|
- **左侧约 58%:源文件阅读器**
|
||||||
|
- 只读展示完整源文件,支持滚动和行号。
|
||||||
|
- 当前切片对应的原文使用浅靛蓝背景高亮,并在左边缘显示定位条。
|
||||||
|
- 顶部提示当前定位,例如 `已定位到切片 #002`。
|
||||||
|
- 左侧原文始终保持源文件真实性,修改右侧切片不会直接改写源文件。
|
||||||
|
|
||||||
|
- **右侧约 42%:切片内容**
|
||||||
|
- 上方为切片列表,支持按编号或内容搜索、仅查看已修改项。
|
||||||
|
- 每个切片显示编号、来源行范围、Token 数和修改状态。
|
||||||
|
- 下方为当前切片的可编辑正文、字符数、Token 数和自动保存状态。
|
||||||
|
- 提供上一片、下一片和恢复原始内容。
|
||||||
|
|
||||||
|
### 4.3 对照定位交互
|
||||||
|
|
||||||
|
右侧切片是主选择源,左侧原文是定位反馈:
|
||||||
|
|
||||||
|
1. 用户点击右侧切片。
|
||||||
|
2. 系统读取该切片保存的原始来源范围 `sourceStart`、`sourceEnd`。
|
||||||
|
3. 左侧阅读器自动滚动到目标范围,并将对应原文高亮。
|
||||||
|
4. 右侧切片进入选中态,左右使用一致的浅靛蓝语义色建立对应关系。
|
||||||
|
5. 用户切换上一片或下一片时执行相同的同步定位。
|
||||||
|
|
||||||
|
为了避免跳动,目标范围应滚动到阅读区中部;如果目标已完全可见,则只更新高亮,不重复滚动。
|
||||||
|
|
||||||
|
### 4.4 编辑与来源映射
|
||||||
|
|
||||||
|
- 编辑切片正文只改变待生成内容,来源映射仍指向最初的原文范围。
|
||||||
|
- 编辑后显示 `已修改,草稿已自动保存`,并允许恢复到系统初始切片内容。
|
||||||
|
- 修改后的文本与原文不再完全一致时,左侧仍高亮原始来源,右侧显示 `内容已调整`,避免误解为源文件被改写。
|
||||||
|
- 手动新增的切片没有原始来源,显示 `手动新增,无源文件定位`,左侧清除高亮但保留当前位置。
|
||||||
|
- 删除切片需要二次确认;删除只影响本次处理,不修改源文件。
|
||||||
|
- 找不到来源范围或范围越界时,右侧显示 `无法定位原文`,左侧不进行错误滚动,用户仍可编辑该切片。
|
||||||
|
|
||||||
|
### 4.5 结构化数据适配
|
||||||
|
|
||||||
|
第二步的整体布局和交互不变,但语义调整为记录对照:
|
||||||
|
|
||||||
|
- 左侧展示源文件中的表格行或原始 JSON 记录。
|
||||||
|
- 右侧展示系统解析后的单条记录及字段值。
|
||||||
|
- 点击右侧记录,左侧定位并高亮对应源行或 JSON 范围。
|
||||||
|
|
||||||
|
这样可以保持四步流程稳定,同时避免对结构化数据使用不准确的“切片”文案。
|
||||||
|
|
||||||
|
底部操作:`返回:创建任务`、`保存草稿`、唯一主操作 `确认预览并继续`。
|
||||||
|
|
||||||
|
## 5. 第三步:开始生成
|
||||||
|
|
||||||
|
页面只显示:
|
||||||
|
|
||||||
|
- 任务名称、数据类型、文件、预览条目数和修改条目数的只读摘要。
|
||||||
|
- 唯一启动操作 `开始生成`。
|
||||||
|
- 启动后的进度、当前阶段、耗时和状态日志。
|
||||||
|
- 失败时显示错误摘要与 `重试`;运行中提供 `停止生成`,停止前需要二次确认。
|
||||||
|
|
||||||
|
启动前不显示底部“下一步”与页面内“开始生成”两个竞争按钮。生成成功后,唯一主操作切换为 `查看生成结果`。
|
||||||
|
|
||||||
|
## 6. 第四步:结果编辑与保存
|
||||||
|
|
||||||
|
结果不再使用一组原始 JSON 文本框堆叠展示,而采用结构化编辑列表:
|
||||||
|
|
||||||
|
- 桌面端左侧为结果条目列表,支持搜索、错误筛选和已修改筛选。
|
||||||
|
- 桌面端右侧为当前结果的字段编辑区;较窄窗口改为列表在上、编辑区在下。
|
||||||
|
- 每条结果显示校验状态,错误定位到具体字段。
|
||||||
|
- 支持上一条、下一条、恢复生成结果。
|
||||||
|
- 保存前执行完整校验;有阻断错误时禁止保存并聚焦首个错误。
|
||||||
|
|
||||||
|
底部操作:`返回:开始生成`、`保存草稿`、唯一主操作 `保存任务`。
|
||||||
|
|
||||||
|
## 7. 状态与数据模型
|
||||||
|
|
||||||
|
向导需要维护统一草稿状态:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface DataProcessDraft {
|
||||||
|
task: TaskInfo
|
||||||
|
sourceFile: SourceFileMeta
|
||||||
|
processingType: 'structured' | 'unstructured'
|
||||||
|
previewItems: PreviewItem[]
|
||||||
|
selectedPreviewId: string | null
|
||||||
|
generation: GenerationState
|
||||||
|
results: ResultItem[]
|
||||||
|
currentStep: 1 | 2 | 3 | 4
|
||||||
|
dirty: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
源文件位置以字符偏移作为定位依据,行号用于展示。这样即使原文存在长行或不同换行符,也能稳定高亮准确范围。
|
||||||
|
|
||||||
|
## 8. 页面组件边界
|
||||||
|
|
||||||
|
- `DataProcessWizardShell`:固定四步导航、页面标题和底部操作栏。
|
||||||
|
- `TaskSetupStep`:任务信息、类型和文件上传。
|
||||||
|
- `PreviewStep`:数据预览容器,按处理类型选择预览器。
|
||||||
|
- `SourceViewer`:源文件渲染、滚动定位和范围高亮。
|
||||||
|
- `PreviewItemList`:切片或记录列表、搜索与选择。
|
||||||
|
- `PreviewItemEditor`:当前条目编辑、状态和恢复。
|
||||||
|
- `GenerationStep`:启动、进度、失败与重试。
|
||||||
|
- `ResultReviewStep`:生成结果列表、字段编辑和保存校验。
|
||||||
|
|
||||||
|
`SourceViewer` 不感知切片列表内部逻辑,只接收选中范围;`PreviewItemList` 不直接操纵滚动,只上报选中项。同步行为由 `PreviewStep` 统一协调。
|
||||||
|
|
||||||
|
## 9. 异常与离开保护
|
||||||
|
|
||||||
|
- 上传失败、解析失败、预览生成失败分别提供明确原因和重试入口。
|
||||||
|
- 预览数据加载时显示骨架或局部加载状态,不能让整个白色画布闪烁或短暂回退到列表页。
|
||||||
|
- 刷新、返回列表或关闭页面前,如果存在未持久化修改,必须提示保存草稿或放弃更改。
|
||||||
|
- 自动保存失败时保留本地编辑内容,并显示可重试状态。
|
||||||
|
- 生成过程中禁止无提示切换处理类型或替换源文件。
|
||||||
|
|
||||||
|
## 10. 响应式与可访问性
|
||||||
|
|
||||||
|
- 桌面端左右对照为默认布局,分栏宽度允许拖动但设置最小宽度。
|
||||||
|
- 较窄窗口下改为上下布局:源文件在上,切片在下;选中切片后仍自动定位高亮。
|
||||||
|
- 选中、高亮、修改和错误状态不能只依赖颜色,还需文字、图标或边缘标记。
|
||||||
|
- 键盘支持上下切换切片、聚焦编辑器、保存草稿;滚动定位后不抢走编辑器焦点。
|
||||||
|
|
||||||
|
## 11. 验收标准
|
||||||
|
|
||||||
|
1. 结构化和非结构化流程都始终显示四个固定步骤。
|
||||||
|
2. 每一步只包含该阶段必要内容,页面中不存在跨步骤主操作竞争。
|
||||||
|
3. 点击任一有来源映射的右侧切片后,左侧在一次交互内定位并高亮准确原文范围。
|
||||||
|
4. 已完全可见的范围不会产生多余滚动;不可定位时有明确反馈且不破坏编辑。
|
||||||
|
5. 修改切片不会改写左侧源文件,高亮仍指向原始来源。
|
||||||
|
6. 返回前一步再进入时,选中项、编辑内容、滚动上下文和草稿状态可恢复。
|
||||||
|
7. 生成开始前只存在一个明确启动操作;生成成功后才允许进入结果编辑。
|
||||||
|
8. 最终结果保存前执行字段级校验,阻断错误可准确定位。
|
||||||
|
9. 路由切换不出现列表页或灰色背景闪烁,白色页面画布保持连续。
|
||||||
|
10. 页面在常用桌面宽度下无横向溢出,底部操作栏不遮挡主体内容。
|
||||||
|
|
||||||
|
## 12. 视觉参考
|
||||||
|
|
||||||
|
- 当前流程截图:`docs/superpowers/specs/assets/data-process-create-audit/01-create-task.png`
|
||||||
|
- 当前动态步骤截图:`docs/superpowers/specs/assets/data-process-create-audit/02-unstructured-step-change.png`
|
||||||
|
- 最终左右对照修订稿:`/Users/caoxiaozhu/.codex/generated_images/019f4981-cb06-79f0-b387-1d685e74ceea/exec-c72de596-4c50-405b-8987-43d122e02b86.png`
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# 页面表面分类设计
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
修正主布局无条件套用白色页面画布的问题,使页面层级符合以下规则:
|
||||||
|
|
||||||
|
- 列表页已经拥有自己的白色卡片,因此直接放在灰色应用背景上。
|
||||||
|
- 表单页、详情页和对话页继续使用统一的白色圆角页面画布。
|
||||||
|
- 页面分类由路由显式声明,避免依赖路径字符串或 DOM 结构猜测。
|
||||||
|
|
||||||
|
## 根因
|
||||||
|
|
||||||
|
`MainLayout.vue` 当前无条件为所有子路由渲染 `.page-canvas`。同时,列表页通过
|
||||||
|
`DataTablePage`、`.eval-card-container` 或 `.manage-card-container` 自带白色卡片,
|
||||||
|
因此形成“灰色外层 → 白色页面画布 → 白色列表卡片”的重复层级。
|
||||||
|
|
||||||
|
## 设计
|
||||||
|
|
||||||
|
在列表路由的 `meta` 中增加 `pageSurface: 'self'`:
|
||||||
|
|
||||||
|
- `/fine-tune`
|
||||||
|
- `/model-eval`
|
||||||
|
- `/model-inference`
|
||||||
|
- `/model-manage`
|
||||||
|
- `/data-process`
|
||||||
|
- `/dataset`
|
||||||
|
|
||||||
|
其他路由保持默认行为,不需要逐项声明,仍由主布局提供白色页面画布。
|
||||||
|
|
||||||
|
`MainLayout.vue` 根据 `route.meta.pageSurface` 给 `.page-canvas` 添加
|
||||||
|
`.is-self-surface` 状态类。该状态仅取消外层画布的白色背景、圆角、阴影和内边距,
|
||||||
|
保留列表页自身的卡片样式。
|
||||||
|
|
||||||
|
对于默认白色画布页面,如果路由直接输出 `PageCard`,可直接扁平化;如果由页面
|
||||||
|
根容器包裹主 `PageCard`,根容器必须显式声明 `.page-card-host`。只有这两类主卡片
|
||||||
|
改为透明、无圆角、无阴影,内部业务卡片不受影响,避免创建页出现“白色画布再套
|
||||||
|
白色卡片”,同时不误伤训练日志的参数卡片。
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
1. 六个列表路由只显示自身的一层白色卡片,外部可见灰色页面背景。
|
||||||
|
2. `/training-log/:id`、创建页、编辑页等仍显示白色圆角页面画布。
|
||||||
|
3. 不根据 URL 文本或 DOM 自动判断页面类型。
|
||||||
|
4. 不改变列表、表单和详情页现有业务逻辑。
|
||||||
|
5. 桌面宽度下不产生水平滚动。
|
||||||
|
6. 创建训练任务等表单页只显示一层白色画布,根 `PageCard` 不形成第二层表面。
|
||||||
|
|
||||||
|
## 测试策略
|
||||||
|
|
||||||
|
- 静态回归脚本验证六个列表路由都声明 `pageSurface: 'self'`。
|
||||||
|
- 静态回归脚本验证 `MainLayout` 根据路由 meta 设置状态类。
|
||||||
|
- 静态回归脚本验证 `.is-self-surface` 取消白底、内边距、圆角和阴影。
|
||||||
|
- 浏览器分别检查一个列表页和一个详情页。
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# 数据预览切片编辑模式
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
第二步右侧区域不再把切片列表和正文编辑器上下堆叠。默认仅展示可搜索、可分页的切片列表;编辑时在同一右侧区域进入单独的编辑视图。
|
||||||
|
|
||||||
|
## 列表模式
|
||||||
|
|
||||||
|
- 每行显示切片编号与来源行号。
|
||||||
|
- 每行右侧提供铅笔和垃圾桶图标按钮,分别用于编辑与删除。
|
||||||
|
- 不显示 Token、修改状态和“仅看已修改”筛选。
|
||||||
|
- 保留按编号或正文搜索及每页 6 条分页。
|
||||||
|
|
||||||
|
## 编辑模式
|
||||||
|
|
||||||
|
- 点击铅笔后,右侧替换为当前切片的编辑界面,左侧源文件继续高亮对应行。
|
||||||
|
- 编辑使用组件内临时草稿;输入过程不更新父级预览数据。
|
||||||
|
- “保存修改”将草稿写回父级数据并返回列表。
|
||||||
|
- “取消”或“返回列表”丢弃未保存草稿并返回列表,用户可继续选择其他切片。
|
||||||
|
- 删除继续使用确认弹窗;确认后删除该条并返回列表。
|
||||||
|
|
||||||
|
## 边界与验证
|
||||||
|
|
||||||
|
- 翻页、搜索或切换文件时,应退出编辑模式并丢弃未保存草稿,避免将内容写到错误条目。
|
||||||
|
- 切片列表、编辑模式切换、保存和取消行为由向导回归脚本覆盖。
|
||||||
27
docs/superpowers/specs/2026-07-10-route-transition-design.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 二级页面路由转场设计
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
消除列表页进入新建、详情、编辑等二级页面时的透明闪烁,使路由内容与页面表面模式在同一帧完成切换。
|
||||||
|
|
||||||
|
## 根因
|
||||||
|
|
||||||
|
`MainLayout.vue` 使用 `transition name="fade" mode="out-in"`。路由变化时,
|
||||||
|
`route.meta.pageSurface` 会立即切换外层画布模式,但旧页面仍执行 150ms 的离场淡出,
|
||||||
|
因此旧列表会短暂显示在新页面的白色画布中并处于半透明状态。
|
||||||
|
|
||||||
|
## 已选方案
|
||||||
|
|
||||||
|
移除主布局的页面级 `transition` 和对应 `.fade-*` 样式,让 `router-view` 组件立即替换。
|
||||||
|
列表、详情、新建和编辑页面的表面模式将与内容同步切换。
|
||||||
|
|
||||||
|
保留组件内部动效,包括弹窗、折叠、按钮状态、批量操作栏等局部交互动画。
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
1. `MainLayout.vue` 不再使用页面级 `<transition>`。
|
||||||
|
2. 主布局不再包含 `.fade-enter-*` 或 `.fade-leave-*` 透明度规则。
|
||||||
|
3. 列表进入新建、列表进入训练日志、二级页面返回列表时,不出现旧页面半透明中间帧。
|
||||||
|
4. 页面表面分类和根 `PageCard` 扁平化逻辑保持不变。
|
||||||
|
5. 页面无水平溢出,控制台无新增错误。
|
||||||
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# 源数据上传紧凑文件列表设计
|
||||||
|
|
||||||
|
日期:2026-07-10
|
||||||
|
|
||||||
|
## 1. 目标
|
||||||
|
|
||||||
|
优化数据处理创建页“源数据上传”区域中已选文件的呈现方式。当前每个文件
|
||||||
|
占用一张独立的大卡片;文件数量增多时,页面会被文件清单拉长,用户也难以
|
||||||
|
快速定位和删除某一文件。
|
||||||
|
|
||||||
|
本次改动将已上传文件改为每页最多 10 条的紧凑列表,并让上传入口随文件状态切换:
|
||||||
|
空状态使用大尺寸拖拽上传区,已有文件时切换为列表标题右侧的小型继续上传按钮。
|
||||||
|
这样能保持文件状态和逐项删除能力,让大量文件的查看与操作集中在一个稳定区域内。
|
||||||
|
|
||||||
|
## 2. 方案选择
|
||||||
|
|
||||||
|
评估过三种方式:
|
||||||
|
|
||||||
|
1. 文件名标签流:占用最少,但长文件名、状态与删除操作会拥挤,不适合本页。
|
||||||
|
2. 折叠摘要:页面最短,但需要额外展开才能操作,增加一步操作成本。
|
||||||
|
3. 固定高度紧凑列表:每个文件是一行,状态和删除按钮始终可见;超过可见
|
||||||
|
行数后在列表内滚动。
|
||||||
|
|
||||||
|
采用第三种方案。它保留当前已有的逐项删除能力,同时解决页面高度随文件数
|
||||||
|
量线性增长的问题。
|
||||||
|
|
||||||
|
## 3. 布局与交互
|
||||||
|
|
||||||
|
空状态只显示现有的大尺寸拖拽上传区及格式提示,用于首次添加文件。
|
||||||
|
|
||||||
|
存在文件后,隐藏大尺寸拖拽上传区和其格式提示,在原位置显示文件列表:
|
||||||
|
|
||||||
|
- 列表顶部左侧显示 `已添加 N 个文件`,其中 `N` 为当前文件数;右侧显示
|
||||||
|
小型 `继续上传` 按钮。
|
||||||
|
- `继续上传` 复用原上传控件的多文件选择、格式限制、非自动上传和文件变更事件,
|
||||||
|
只改变触发入口的尺寸与位置。
|
||||||
|
- 每页最多显示 10 行;超过 10 个文件时,在列表底部显示分页器,不使用长列表
|
||||||
|
或内部滚动承载额外文件。
|
||||||
|
- 新增文件后自动切换到最后一页,确保用户能立刻看到刚添加的文件;删除当前页
|
||||||
|
的最后一个文件后,自动回退到仍有文件的最后一页。
|
||||||
|
- 单行高度约 48px,使用细分隔线区分,不再使用外框、左侧绿条和大尺寸图标。
|
||||||
|
- 每行从左至右依次为:文件类型图标、文件名、大小与条目数、校验状态、删除。
|
||||||
|
- 文件名占据可伸缩区域;空间不足时省略,保留原始 `title` 以便悬停查看全名。
|
||||||
|
- 删除按钮沿用红色文字链接,点击后继续使用当前的移除事件;不新增确认弹窗
|
||||||
|
或批量删除,以免改变既有操作语义。
|
||||||
|
- 校验状态沿用绿色图标和“校验通过”文案;后续若存在失败状态,可在同一位置
|
||||||
|
显示错误文案而不改变列表结构。
|
||||||
|
|
||||||
|
## 4. 视觉约束
|
||||||
|
|
||||||
|
- 延续页面的靛蓝文件图标、绿色成功状态和红色危险操作色;继续上传按钮使用现有
|
||||||
|
主色的紧凑次级按钮样式。
|
||||||
|
- 列表容器使用现有浅灰边框和 8px 圆角,背景保持白色;不新增大卡片层级。
|
||||||
|
- 仅当文件数超过 10 个时显示分页器;分页器使用紧凑的上一页、页码、下一页布局,
|
||||||
|
不提供页大小选择或跳页输入,避免增加不必要的操作。
|
||||||
|
- 在窄屏下,元信息可收缩,文件名仍保持最优先;删除按钮必须始终可见且可点按。
|
||||||
|
|
||||||
|
## 5. 组件边界与数据流
|
||||||
|
|
||||||
|
改动只在 `TaskSetupStep` 内完成:
|
||||||
|
|
||||||
|
- 继续使用现有 `uploadedFiles` 属性和 `remove-file` 事件。
|
||||||
|
- 不修改文件上传、格式限制、校验逻辑或父组件中的文件数据模型。
|
||||||
|
- 新增的文件总数来自 `uploadedFiles.length`,每行信息继续来自现有文件元数据。
|
||||||
|
|
||||||
|
## 6. 可访问性
|
||||||
|
|
||||||
|
- 删除按钮继续使用真实的 `el-button`,确保键盘可聚焦和激活。
|
||||||
|
- 文件名省略时通过原生 `title` 暴露完整名称。
|
||||||
|
- 校验状态同时保留图标和文字,不能只依靠颜色传达结果。
|
||||||
|
- 滚动列表不截断键盘焦点;聚焦到不可见的删除按钮时,浏览器应能将对应行带入
|
||||||
|
可见区域。
|
||||||
|
|
||||||
|
## 7. 验收标准
|
||||||
|
|
||||||
|
1. 尚未添加文件时,只显示大尺寸拖拽上传区与格式提示,不显示文件列表。
|
||||||
|
2. 上传至少 1 个文件后,大尺寸上传区与格式提示不再显示;文件列表标题右侧显示
|
||||||
|
可继续选择多个文件的小型上传按钮。
|
||||||
|
3. 上传 1 至 10 个文件时,所有文件在紧凑列表中直接可见,每行均可单独删除。
|
||||||
|
4. 上传超过 10 个文件时,列表一次只渲染当前 10 条,分页器可切换其他文件页;
|
||||||
|
页面高度不随总文件数继续增长。
|
||||||
|
5. 新增文件后自动显示最后一页;删除造成当前页没有文件时,自动回到有效页。
|
||||||
|
6. 每一行均展示文件名、大小、可选条目数、校验状态和删除操作。
|
||||||
|
7. 长文件名不会挤压删除按钮,且悬停时可获取完整名称。
|
||||||
|
8. 处理类型切换、格式限制、首次上传、继续上传和删除事件的现有行为不变。
|
||||||
|
9. 常用桌面宽度和窄屏下均无横向溢出,删除操作保持可访问。
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# 训练日志详情页双栏改版设计
|
||||||
|
|
||||||
|
**日期**:2026-07-10
|
||||||
|
**页面**:`frontend/src/views/system/TrainingLogView.vue`
|
||||||
|
**视觉基准**:用户选定的第 2 套方案(双栏任务档案型)
|
||||||
|
**视觉稿**:`docs/superpowers/specs/assets/training-log-detail-option-2.png`
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
把当前多组带边框 `el-descriptions` 表格改成更易扫描的任务档案页:左侧聚合任务信息与数据集,右侧集中展示运行概况,下方用轻量折叠区呈现训练参数。数据请求、日志刷新、训练曲线和原始日志行为保持不变。
|
||||||
|
|
||||||
|
## 选定布局
|
||||||
|
|
||||||
|
1. 页面顶部保留任务名与状态,并增加任务类型、创建时间等辅助上下文。
|
||||||
|
2. 首屏主体使用 `2fr / 0.9fr` 双栏:
|
||||||
|
- 左栏依次为“任务信息”和“训练数据集”;
|
||||||
|
- 右栏为“运行概况”,集中展示时间、时长、存储、训练方式和训练方法。
|
||||||
|
3. 数据集名称与描述优先,条数和大小放入同一条浅色指标带。
|
||||||
|
4. 训练参数与 LoRA 参数使用同一卡片内的两个分组;标签和值采用轻分隔行,不再使用描边表格。
|
||||||
|
5. 缺失值统一显示“未配置”,输出模型缺失显示“暂未生成”,避免大量孤立的 `-`。
|
||||||
|
|
||||||
|
## 视觉规则
|
||||||
|
|
||||||
|
- 应用外壳使用 `--app-shell-bg: #f3f5f8`,灰色只作为页面四周的留白背景。
|
||||||
|
- 每个业务路由统一渲染在 `--app-page-bg: #ffffff` 的白色圆角页面画布中,所有页面内容都基于这层白底布局。
|
||||||
|
- 页面内部白色内容表面继续使用 `--app-surface-bg: #ffffff`;当路由根节点本身是 `PageCard` 时,移除其额外阴影和外边距,避免白底里再套一张重复大卡片。
|
||||||
|
- 沿用项目现有 Indigo `#4f46e5`、Slate 灰阶和成功色 `#10b981`。
|
||||||
|
- 一级卡片使用现有 `PageCard` 和 12px 实际圆角;内部块使用 8px 圆角。
|
||||||
|
- 仅使用 `#e2e8f0` / `#f1f5f9` 轻分隔,不新增阴影层级和渐变。
|
||||||
|
- 标题 18px/600,分组标题 16px/600,正文值 14px,辅助标签 12–13px。
|
||||||
|
- 图标继续使用项目现有 Font Awesome 4,避免引入依赖或扩大本次改造范围。
|
||||||
|
|
||||||
|
## 交互与响应式
|
||||||
|
|
||||||
|
- 参数折叠头改为原生 `button`,提供 `aria-expanded`、可见焦点和至少 44px 点击高度。
|
||||||
|
- 宽屏为双栏;`<= 1100px` 改为单栏;参数明细在窄屏下降为单列。
|
||||||
|
- 保持 5 秒自动刷新、图表、汇总和原始日志逻辑不变。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
### 修改
|
||||||
|
|
||||||
|
- `frontend/src/views/system/TrainingLogView.vue`
|
||||||
|
- `frontend/src/layouts/MainLayout.vue`
|
||||||
|
- `frontend/src/styles/index.scss`
|
||||||
|
- `frontend/package.json`
|
||||||
|
- `frontend/scripts/regression-training-log-layout.mjs`
|
||||||
|
- `frontend/scripts/regression-page-surface.mjs`
|
||||||
|
|
||||||
|
### 不修改
|
||||||
|
|
||||||
|
- API、Pinia、类型定义、路由和 mock 数据。
|
||||||
|
- 全局 `PageCard.vue`。
|
||||||
|
- 训练曲线、训练汇总和日志输出的业务逻辑。
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
- 首屏不再使用带边框的详情表格表达任务、数据集和训练参数。
|
||||||
|
- 双栏信息架构与选定视觉稿一致,缺失值文案可读。
|
||||||
|
- 参数折叠支持鼠标、键盘和辅助技术。
|
||||||
|
- 1100px 以下无双栏挤压或横向滚动。
|
||||||
|
- 两个专项回归脚本与 `npx vite build` 通过。
|
||||||
|
- `npm run type-check` / `npm run build` 使用项目引用模式执行;当前仓库原有的 mock 与微调页面类型错误需如实记录,且本次改动文件不得出现在错误列表中。
|
||||||
|
|
||||||
|
## 风险
|
||||||
|
|
||||||
|
- 生成视觉稿只覆盖首屏,图表区继续沿用现状;实现以首屏信息架构为高保真目标。
|
||||||
|
- 项目目录没有 Git 元数据,无法提交设计文档或代码提交;所有变更保持小范围、可直接审阅。
|
||||||
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 390 KiB |
BIN
docs/superpowers/specs/assets/page-white-canvas-comparison.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
docs/superpowers/specs/assets/page-white-canvas-reference.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
docs/superpowers/specs/assets/route-transition-detail-final.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 851 KiB |
BIN
docs/superpowers/specs/assets/training-log-detail-comparison.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 856 KiB |
|
After Width: | Height: | Size: 451 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 87 KiB |
BIN
docs/superpowers/specs/assets/training-log-detail-option-2.png
Normal file
|
After Width: | Height: | Size: 886 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 83 KiB |