fix(dataset): 展示并切换三路数据切分

This commit is contained in:
caoxiaozhu
2026-07-24 21:11:01 +08:00
parent 9cb77c251a
commit 9114f3d4c7
6 changed files with 193 additions and 11 deletions

View File

@@ -448,6 +448,33 @@ async function publishDataset() {
}
}
async function resyncPublishedDataset() {
if (!detail.value || !outputDatasetId.value || publishing.value) return
try {
await ElMessageBox.confirm(
'将按当前 8:1:1 配置重新生成训练、验证、测试三个切分文件,数据集 ID 保持不变。',
'重新同步数据切分?',
{ type: 'warning', confirmButtonText: '重新同步', cancelButtonText: '取消' },
)
} catch {
return
}
publishing.value = true
try {
const published = await publishDataProcess(taskId.value, {
...publishForm,
dataset_name: outputDatasetName.value || `${detail.value.name}-数据集`,
split: configuredSplit(),
})
ElMessage.success('训练集、验证集和测试集已重新同步')
const datasetId = published.dataset?.id || outputDatasetId.value
await router.push(`/dataset/${datasetId}/preview`)
} finally {
publishing.value = false
}
}
watch([currentPage, pageSize], () => void loadResults())
onMounted(loadPage)
@@ -472,6 +499,13 @@ onBeforeUnmount(() => {
<i class="fa" :class="outputDatasetId ? 'fa-external-link' : 'fa-database'" />
{{ outputDatasetId ? '查看输出数据集' : '发布为数据集' }}
</el-button>
<el-button
v-if="detail.status === 'completed' && outputDatasetId"
:loading="publishing"
@click="resyncPublishedDataset"
>
<i class="fa fa-refresh" />重新同步切分
</el-button>
</div>
<p>{{ detail.description || '暂无任务描述' }}</p>
<dl class="heading-meta">
@@ -717,7 +751,7 @@ onBeforeUnmount(() => {
<el-alert
type="info"
:closable="false"
:title="`数据集划分:训练集 ${publishForm.split.train}% / 验证集 ${publishForm.split.validation}% / 测试集 ${publishForm.split.test}%`"
:title="`将生成三个独立文件:训练集 ${publishForm.split.train}% / 验证集 ${publishForm.split.validation}% / 测试集 ${publishForm.split.test}%`"
/>
</el-form>
<template #footer>

View File

@@ -21,6 +21,17 @@ const filteredDataList = computed(() => {
return dataList.value.filter((item) => item.source !== 'task')
})
const splitLabels = [
{ key: 'train', label: '训练', type: 'primary' },
{ key: 'validation', label: '验证', type: 'warning' },
{ key: 'test', label: '测试', type: 'success' },
] as const
function hasSplitCounts(row: Partial<DatasetItem>) {
return row.source === 'task'
&& Object.values(row.split_counts || {}).some((value) => Number(value) > 0)
}
async function loadData() {
loading.value = true
try {
@@ -87,9 +98,17 @@ onMounted(loadData)
<el-table-column label="数据集名称" prop="name" align="center" />
<el-table-column label="数据类型" align="center" width="110">
<template #default="{ row }">
<el-tag type="primary" size="small">
{{ DATASET_TYPE_MAP[String(row.type).toLowerCase()] || row.type || '-' }}
</el-tag>
<div v-if="hasSplitCounts(row)" class="split-tags">
<el-tag
v-for="item in splitLabels"
:key="item.key"
:type="item.type"
size="small"
>
{{ item.label }} {{ row.split_counts?.[item.key] || 0 }}
</el-tag>
</div>
<el-tag v-else type="primary" size="small">{{ DATASET_TYPE_MAP[String(row.type).toLowerCase()] || row.type || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="存储位置" align="center" width="110">
@@ -167,4 +186,11 @@ onMounted(loadData)
font-weight: 600;
}
}
.split-tags {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 4px;
}
</style>

View File

@@ -20,6 +20,7 @@ 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 DatasetSplitNavigator from './preview/DatasetSplitNavigator.vue'
import type { EditField } from './preview/types'
import type { DatasetFile, DatasetItem, DatasetVersion } from '@/types'
@@ -53,6 +54,7 @@ let versionRequestId = 0
const files = computed<DatasetFile[]>(() => dataset.value?.files || [])
const selectedFile = computed(() => files.value.find((file) => fileKey(file) === selectedFileId.value))
const splitCounts = computed(() => dataset.value?.split_counts || { train: 0, validation: 0, test: 0 })
const totalLines = computed(() => previewContent.value ? previewContent.value.split('\n').length : 0)
const recordResult = computed(() => parseDatasetRecords(previewContent.value, selectedFile.value?.name || ''))
const isRecordFile = computed(() => recordResult.value.supported)
@@ -81,6 +83,13 @@ function fileKey(file: DatasetFile) {
return String(file.id || file.name)
}
async function handleFileChange(file: DatasetFile) {
if (fileKey(file) === selectedFileId.value) return
if (!await confirmDiscardChanges('切换数据切分后,当前样本的未保存修改将丢失。')) return
selectedFileId.value = fileKey(file)
await loadVersions(file)
}
async function loadDataset() {
loading.value = true
try {
@@ -400,6 +409,12 @@ onBeforeUnmount(() => window.removeEventListener('beforeunload', handleBeforeUnl
<div class="overview-item overview-item-wide"><span>创建时间</span><strong>{{ createdAt }}</strong></div>
</section>
<DatasetSplitNavigator
v-if="Object.values(splitCounts).some((value) => value > 0)"
:files="files" :selected-file-id="selectedFileId" :split-counts="splitCounts"
@select="handleFileChange"
/>
<DatasetVersionBar
v-model="selectedVersionId"
:versions="versions"
@@ -488,10 +503,7 @@ onBeforeUnmount(() => window.removeEventListener('beforeunload', handleBeforeUnl
</section>
<DatasetRecordEditorDialog
:model-value="editorVisible"
:record="editingRecord"
:edit-fields="editFields"
:raw-draft="rawDraft"
:model-value="editorVisible" :record="editingRecord" :edit-fields="editFields" :raw-draft="rawDraft"
:saving-record="savingRecord"
:has-draft-changes="hasEditorDraftChanges"
:next-version-number="nextVersionNumber"
@@ -500,7 +512,6 @@ onBeforeUnmount(() => window.removeEventListener('beforeunload', handleBeforeUnl
@update:field="updateEditField"
@update:raw-draft="rawDraft = $event"
/>
<AppConfirmDialog ref="confirmDialogRef" />
</PageCard>
</template>

View File

@@ -0,0 +1,103 @@
<script setup lang="ts">
import type { DatasetFile } from '@/types'
defineProps<{
files: DatasetFile[]
selectedFileId: string
splitCounts: { train: number; validation: number; test: number }
}>()
const emit = defineEmits<{ select: [file: DatasetFile] }>()
function fileKey(file: DatasetFile) {
return String(file.id || file.name)
}
function splitLabel(split?: DatasetFile['split']) {
if (!split) return '数据文件'
return { train: '训练集', validation: '验证集', test: '测试集' }[split]
}
function splitTagType(split?: DatasetFile['split']) {
if (!split) return 'info'
return ({ train: 'primary', validation: 'warning', test: 'success' } as const)[split]
}
</script>
<template>
<section class="split-summary" aria-label="数据切分统计">
<div class="split-stat train"><span>训练集</span><strong>{{ splitCounts.train.toLocaleString() }}</strong></div>
<div class="split-stat validation"><span>验证集</span><strong>{{ splitCounts.validation.toLocaleString() }}</strong></div>
<div class="split-stat test"><span>测试集</span><strong>{{ splitCounts.test.toLocaleString() }}</strong></div>
</section>
<nav v-if="files.length > 1" class="split-file-tabs" aria-label="数据切分文件">
<button
v-for="file in files"
:key="fileKey(file)"
type="button"
class="split-file-tab"
:class="{ active: fileKey(file) === selectedFileId }"
@click="emit('select', file)"
>
<el-tag :type="splitTagType(file.split)" size="small">{{ splitLabel(file.split) }}</el-tag>
<strong>{{ (file.record_count || 0).toLocaleString() }} </strong>
<span>{{ file.name }}</span>
</button>
</nav>
</template>
<style scoped lang="scss">
.split-summary,
.split-file-tabs {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-bottom: 14px;
}
.split-stat {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border: 1px solid #e5eaf2;
border-radius: 10px;
background: #fbfcfe;
color: #667085;
}
.split-stat strong { font-size: 18px; }
.split-stat.train strong { color: #4f46e5; }
.split-stat.validation strong { color: #d97706; }
.split-stat.test strong { color: #059669; }
.split-file-tab {
display: grid;
grid-template-columns: auto auto 1fr;
align-items: center;
gap: 8px;
min-width: 0;
padding: 12px 14px;
border: 1px solid #e5eaf2;
border-radius: 10px;
background: #fff;
color: #344054;
cursor: pointer;
text-align: left;
}
.split-file-tab:hover,
.split-file-tab.active {
border-color: #818cf8;
background: #f7f7ff;
}
.split-file-tab span:last-child {
overflow: hidden;
color: #8a94a6;
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>