From 3fabd0c0eb4e3b00fde16aaffc287b85978b3506 Mon Sep 17 00:00:00 2001 From: caoxiaozhu Date: Mon, 13 Jul 2026 15:30:11 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=95=B0=E6=8D=AE=E9=9B=86?= =?UTF-8?q?=E9=A2=84=E8=A7=88=E7=BB=84=E4=BB=B6=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 拆分 DatasetVersionBar、DatasetRecordTable、DatasetRawPreview、DatasetRecordEditorDialog 子组件,DatasetPreviewView 聚焦编排,预览类型独立到 preview/types.ts,回归脚本适配。 --- .../scripts/regression-dataset-preview.mjs | 60 +- .../src/views/dataset/DatasetPreviewView.vue | 567 ++---------------- .../dataset/preview/DatasetRawPreview.vue | 63 ++ .../preview/DatasetRecordEditorDialog.vue | 155 +++++ .../dataset/preview/DatasetRecordTable.vue | 335 +++++++++++ .../dataset/preview/DatasetVersionBar.vue | 154 +++++ frontend/src/views/dataset/preview/types.ts | 5 + 7 files changed, 821 insertions(+), 518 deletions(-) create mode 100644 frontend/src/views/dataset/preview/DatasetRawPreview.vue create mode 100644 frontend/src/views/dataset/preview/DatasetRecordEditorDialog.vue create mode 100644 frontend/src/views/dataset/preview/DatasetRecordTable.vue create mode 100644 frontend/src/views/dataset/preview/DatasetVersionBar.vue create mode 100644 frontend/src/views/dataset/preview/types.ts diff --git a/frontend/scripts/regression-dataset-preview.mjs b/frontend/scripts/regression-dataset-preview.mjs index 97954ae..28b7a78 100644 --- a/frontend/scripts/regression-dataset-preview.mjs +++ b/frontend/scripts/regression-dataset-preview.mjs @@ -4,12 +4,24 @@ import ts from 'typescript' const root = process.cwd() const previewPath = path.join(root, 'src/views/dataset/DatasetPreviewView.vue') +const previewComponentsDir = path.join(root, 'src/views/dataset/preview') +const previewComponentPaths = [ + 'DatasetVersionBar.vue', + 'DatasetRecordTable.vue', + 'DatasetRecordEditorDialog.vue', + 'DatasetRawPreview.vue', +].map((name) => path.join(previewComponentsDir, name)) const apiPath = path.join(root, 'src/api/modules/dataset.ts') const adapterPath = path.join(root, 'src/mock/adapter.ts') const mockPath = path.join(root, 'src/mock/data.ts') const recordsPath = path.join(root, 'src/views/dataset/datasetRecords.ts') const versionsPath = path.join(root, 'src/mock/datasetVersions.ts') const source = fs.readFileSync(previewPath, 'utf8') +const previewComponentSources = previewComponentPaths.map((componentPath) => { + expect(fs.existsSync(componentPath), `缺少数据集预览叶子组件:${path.basename(componentPath)}`) + return fs.readFileSync(componentPath, 'utf8') +}) +const previewSurfaceSource = [source, ...previewComponentSources].join('\n') const apiSource = fs.readFileSync(apiPath, 'utf8') const adapterSource = fs.readFileSync(adapterPath, 'utf8') const mockSource = fs.readFileSync(mockPath, 'utf8') @@ -35,33 +47,55 @@ function expect(condition, message) { if (!condition) throw new Error(message) } +expect(source.split('\n').length < 800, 'DatasetPreviewView.vue 应拆分到 800 行以内') +for (const componentName of ['DatasetVersionBar', 'DatasetRecordTable', 'DatasetRecordEditorDialog', 'DatasetRawPreview']) { + expect(source.includes(`import ${componentName}`), `主页面未导入叶子组件:${componentName}`) + expect(source.includes(`<${componentName}`), `主页面未挂载叶子组件:${componentName}`) +} expect(source.includes('class="preview-workspace"'), '详情页应提供数据文件工作区') expect(!source.includes('class="file-pane"'), '单文件数据集详情不应展示文件选择侧栏') -expect(source.includes('class="code-line"'), '内容查看器应提供逐行展示') -expect(source.includes('class="line-number"'), '内容查看器应显示行号') +expect(previewSurfaceSource.includes('class="code-line"'), '内容查看器应提供逐行展示') +expect(previewSurfaceSource.includes('class="line-number"'), '内容查看器应显示行号') expect(source.includes('previewLoading'), '切换文件时应提供独立加载状态') expect(source.includes('previewError'), '内容加载失败时应提供错误状态') -expect(source.includes('class="records-viewer"'), '详情页应将结构化文件展示为样本列表') -expect(source.includes('class="record-table"'), '样本数据应使用企业级表格展示') -expect(source.includes('fixed="right"'), '逐条编辑操作列应固定在表格右侧') -expect(source.includes('v-for="fieldKey in tableFieldKeys"'), '企业表格应根据数据结构动态生成字段列') -expect(source.includes('openRecordEditor(asDatasetRecord(row))'), '每条样本应提供独立编辑入口') -expect(source.includes('暂存修改'), '单条编辑应先暂存到当前页面') +expect(previewSurfaceSource.includes('class="records-viewer"'), '详情页应将结构化文件展示为样本列表') +expect(previewSurfaceSource.includes('class="record-table"'), '样本数据应使用企业级表格展示') +expect(previewSurfaceSource.includes('fixed="right"'), '逐条编辑操作列应固定在表格右侧') +expect(previewSurfaceSource.includes('v-for="fieldKey in tableFieldKeys"'), '企业表格应根据数据结构动态生成字段列') +expect(previewSurfaceSource.includes("emit('edit', asDatasetRecord(row))"), '每条样本应向主页面发送编辑事件') +expect(previewSurfaceSource.includes('暂存修改'), '单条编辑应先暂存到当前页面') expect(source.includes('hasPendingVersionChanges && isViewingActiveVersion'), '存在暂存修改时才应显示保存版本按钮') expect(source.includes('保存版本'), '文件工具栏应提供保存版本按钮') expect(source.includes('baseVersionContent'), '页面应区分已保存版本与待保存工作副本') -expect(source.includes('class="version-control-bar"'), '详情页应提供独立版本控制区') -expect(source.includes('设为当前版本'), '历史版本应支持显式切换为当前版本') -expect(source.includes('历史版本(只读)'), '历史版本应明确展示只读状态') +expect(previewSurfaceSource.includes('class="version-control-bar"'), '详情页应提供独立版本控制区') +expect(previewSurfaceSource.includes('设为当前版本'), '历史版本应支持显式切换为当前版本') +expect(previewSurfaceSource.includes('历史版本(只读)'), '历史版本应明确展示只读状态') expect(source.includes('loadedVersionId.value'), '下载和展示应绑定正在查看的具体版本') expect(source.includes('versionRequestId'), '快速切换历史版本时应防止旧响应覆盖新内容') -expect(source.includes('v-model:current-page="currentPage"'), '样本列表应支持分页浏览') +expect(previewSurfaceSource.includes('v-model:current-page="currentPage"'), '样本列表应支持分页浏览') expect(!source.includes('class="content-editor"'), '详情页不应继续提供整文件编辑器') expect(source.includes('saveRecord'), '逐条编辑器应提供单条保存动作') expect(source.includes('hasUnsavedChanges'), '在线编辑器应跟踪未保存修改') expect(source.includes('onBeforeRouteLeave'), '离开页面时应保护未保存修改') -expect(source.includes("event.key.toLowerCase() === 's'"), '在线编辑器应支持快捷键保存') +expect(previewSurfaceSource.includes("event.key.toLowerCase() === 's'"), '在线编辑器应支持快捷键保存') expect(source.includes('loadVersions(selectedFile)'), '版本内容加载失败后应支持重新加载') +expect(source.includes('function resetRecordEditor()'), '主页面应提供统一的编辑器状态清理函数') +const resetEditorStart = source.indexOf('function resetRecordEditor()') +const updateFieldStart = source.indexOf('function updateEditField', resetEditorStart) +const resetEditorSource = source.slice(resetEditorStart, updateFieldStart) +for (const marker of [ + 'editorVisible.value = false', + 'editingRecord.value = null', + 'editFields.value = []', + "rawDraft.value = ''", + "originalDraft.value = ''", +]) { + expect(resetEditorSource.includes(marker), `编辑器状态清理不完整:${marker}`) +} +const versionChangeStart = source.indexOf('async function handleViewedVersionChange') +const activateVersionStart = source.indexOf('async function activateViewedVersion', versionChangeStart) +const versionChangeSource = source.slice(versionChangeStart, activateVersionStart) +expect(versionChangeSource.includes('resetRecordEditor()'), '成功切换版本后必须关闭并清空旧编辑器状态') expect(!source.includes('handleDownloadAll'), '页面不应保留整包下载逻辑') expect(!source.includes('handleDelete'), '页面不应保留删除逻辑') expect(!source.includes('router.back()'), '页面不应保留页头返回逻辑') diff --git a/frontend/src/views/dataset/DatasetPreviewView.vue b/frontend/src/views/dataset/DatasetPreviewView.vue index e861b79..ad835eb 100644 --- a/frontend/src/views/dataset/DatasetPreviewView.vue +++ b/frontend/src/views/dataset/DatasetPreviewView.vue @@ -14,14 +14,13 @@ import { import { DATASET_TYPE_MAP, STORAGE_MAP } from '@/constants' import { parseDatasetRecords, updateDatasetRecord } from './datasetRecords' import type { DatasetRecord } from './datasetRecords' +import DatasetVersionBar from './preview/DatasetVersionBar.vue' +import DatasetRecordTable from './preview/DatasetRecordTable.vue' +import DatasetRecordEditorDialog from './preview/DatasetRecordEditorDialog.vue' +import DatasetRawPreview from './preview/DatasetRawPreview.vue' +import type { EditField } from './preview/types' import type { DatasetFile, DatasetItem, DatasetVersion } from '@/types' -interface EditField { - key: string - value: string - isJson: boolean -} - const route = useRoute() const datasetId = route.params.id as string @@ -32,9 +31,6 @@ const selectedFileId = ref('') const previewContent = ref('') const baseVersionContent = ref('') const previewError = ref('') -const searchText = ref('') -const currentPage = ref(1) -const pageSize = 10 const editorVisible = ref(false) const editingRecord = ref(null) const editFields = ref([]) @@ -56,37 +52,9 @@ const totalLines = computed(() => previewContent.value ? previewContent.value.sp const recordResult = computed(() => parseDatasetRecords(previewContent.value, selectedFile.value?.name || '')) const isRecordFile = computed(() => recordResult.value.supported) const records = computed(() => recordResult.value.records) -const invalidRecordCount = computed(() => records.value.filter((record) => record.kind === 'invalid').length) -const allTableFieldKeys = computed(() => { - const keys = new Set() - records.value.forEach((record) => { - if (record.kind !== 'object') return - Object.keys(record.value as Record).forEach((key) => keys.add(key)) - }) - const preferred = ['instruction', 'question', 'prompt', 'input', 'context', 'output', 'answer', 'response'] - return [...keys].sort((a, b) => { - const rank = (key: string) => { - const index = preferred.indexOf(key) - return index === -1 ? preferred.length : index - } - return rank(a) - rank(b) - }) -}) -const tableFieldKeys = computed(() => allTableFieldKeys.value.slice(0, 6)) -const hasExtraTableFields = computed(() => allTableFieldKeys.value.length > tableFieldKeys.value.length) const viewedVersion = computed(() => versions.value.find((item) => item.id === loadedVersionId.value)) const isViewingActiveVersion = computed(() => Boolean(loadedVersionId.value) && loadedVersionId.value === activeVersionId.value) const nextVersionNumber = computed(() => Math.max(0, ...versions.value.map((item) => item.version)) + 1) -const filteredRecords = computed(() => { - const query = searchText.value.trim().toLowerCase() - if (!query) return records.value - return records.value.filter((record) => JSON.stringify(record.value).toLowerCase().includes(query)) -}) -const pagedRecords = computed(() => { - const start = (currentPage.value - 1) * pageSize - return filteredRecords.value.slice(start, start + pageSize) -}) -const rawPreviewLines = computed(() => previewContent.value ? previewContent.value.split('\n').slice(0, 100) : []) const currentDraft = computed(() => editingRecord.value?.kind === 'object' ? JSON.stringify(editFields.value) : rawDraft.value) @@ -105,47 +73,10 @@ const createdAt = computed(() => dataset.value?.create_time ? new Date(dataset.value.create_time).toLocaleString('zh-CN') : '-') -function formatVersionTime(value: string) { - return new Date(value).toLocaleString('zh-CN', { hour12: false }) -} - -function versionOptionLabel(version: DatasetVersion) { - const status = version.id === activeVersionId.value ? '当前版本' : '历史版本' - return `V${version.version} · ${status} · ${formatVersionTime(version.create_time)}` -} - function fileKey(file: DatasetFile) { return String(file.id || file.name) } -function formatFieldValue(value: unknown) { - if (typeof value === 'string') return value || '(空)' - return JSON.stringify(value) -} - -function asDatasetRecord(row: unknown) { - return row as DatasetRecord -} - -function recordFieldValue(record: DatasetRecord, key: string) { - if (record.kind !== 'object') return record.kind === 'invalid' ? record.raw : formatFieldValue(record.value) - const data = record.value as Record - return Object.prototype.hasOwnProperty.call(data, key) ? formatFieldValue(data[key]) : '-' -} - -function extraFieldValue(record: DatasetRecord) { - if (record.kind !== 'object') return '-' - const data = record.value as Record - return allTableFieldKeys.value - .filter((key) => !tableFieldKeys.value.includes(key) && Object.prototype.hasOwnProperty.call(data, key)) - .map((key) => `${key}: ${formatFieldValue(data[key])}`) - .join(';') || '-' -} - -function recordRowClassName({ row }: { row: unknown }) { - return asDatasetRecord(row).kind === 'invalid' ? 'record-table-row-invalid' : '' -} - async function loadDataset() { loading.value = true try { @@ -174,6 +105,7 @@ async function loadVersions(file: DatasetFile) { loadedVersionId.value = result.active_version_id previewContent.value = activeContent.content baseVersionContent.value = activeContent.content + resetRecordEditor() } catch { if (requestId === versionRequestId) previewError.value = '版本内容加载失败,请稍后重试' } finally { @@ -197,8 +129,7 @@ async function handleViewedVersionChange(nextVersionId: string) { previewContent.value = result.content baseVersionContent.value = result.content loadedVersionId.value = result.version.id - currentPage.value = 1 - searchText.value = '' + resetRecordEditor() } catch { if (requestId === versionRequestId) selectedVersionId.value = previousVersionId } finally { @@ -259,6 +190,19 @@ function openRecordEditor(record: DatasetRecord) { originalDraft.value = record.kind === 'object' ? JSON.stringify(editFields.value) : rawDraft.value } +function resetRecordEditor() { + editorVisible.value = false + editingRecord.value = null + editFields.value = [] + rawDraft.value = '' + originalDraft.value = '' +} + +function updateEditField(index: number, value: string) { + const field = editFields.value[index] + if (field) field.value = value +} + async function confirmDiscardChanges(message = '当前存在尚未保存为版本的修改,放弃后将无法恢复。') { if (!hasUnsavedChanges.value) return true try { @@ -287,18 +231,11 @@ async function confirmEditorDiscardChanges() { } } -async function closeRecordEditor() { +async function closeRecordEditor(done?: () => void) { if (savingRecord.value) return if (!await confirmEditorDiscardChanges()) return - editorVisible.value = false - editingRecord.value = null -} - -async function handleDialogBeforeClose(done: () => void) { - if (savingRecord.value) return - if (!await confirmEditorDiscardChanges()) return - editingRecord.value = null - done() + resetRecordEditor() + done?.() } function buildNextRecordValue() { @@ -330,9 +267,7 @@ async function saveRecord() { nextValue, ) previewContent.value = nextContent - originalDraft.value = currentDraft.value - editorVisible.value = false - editingRecord.value = null + resetRecordEditor() ElMessage.success(`第 ${record.displayIndex} 条修改已暂存`) } finally { savingRecord.value = false @@ -370,13 +305,6 @@ async function saveVersion() { } } -function handleEditorKeydown(event: KeyboardEvent) { - if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') { - event.preventDefault() - saveRecord() - } -} - function handleBeforeUnload(event: BeforeUnloadEvent) { if (!hasUnsavedChanges.value) return event.preventDefault() @@ -419,46 +347,19 @@ onBeforeUnmount(() => window.removeEventListener('beforeunload', handleBeforeUnl
创建时间{{ createdAt }}
-
-
- - - - -
- -
- -
- - - - 设为当前版本 - -
+ window.removeEventListener('beforeunload', handleBeforeUnl