fix: 完善数据预处理与 JSON 上传链路
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
getDataProcessResults,
|
||||
getDataProcessTask,
|
||||
publishDataProcess,
|
||||
repeatDataProcessTask,
|
||||
restoreDataProcessResult,
|
||||
updateDataProcessResult,
|
||||
} from '@/api/modules/dataProcess'
|
||||
@@ -42,6 +43,8 @@ const savingResult = ref(false)
|
||||
const restoringResultId = ref<string | number | null>(null)
|
||||
const publishDialogVisible = ref(false)
|
||||
const publishing = ref(false)
|
||||
const repeatGenerating = ref(false)
|
||||
const repeatRequestId = ref('')
|
||||
const configExpanded = ref(false)
|
||||
const resultCellTooltipOptions = {
|
||||
popperClass: 'data-process-result-tooltip',
|
||||
@@ -113,6 +116,51 @@ const preprocessOptionLabelMap: Record<string, string> = {
|
||||
preserve_context: '保留上下文',
|
||||
}
|
||||
|
||||
const structuredPreprocessOptionKeys = new Set([
|
||||
'clean_invalid',
|
||||
'deduplicate',
|
||||
'detect_structure',
|
||||
'normalize_format',
|
||||
'desensitize',
|
||||
'filter_anomaly',
|
||||
])
|
||||
|
||||
function formatStructuredPreprocessOptions(value: unknown[]) {
|
||||
const options = [...new Set(value.map((item) => String(item)))]
|
||||
const selected = new Set(options)
|
||||
const consumed = new Set<string>()
|
||||
const labels: string[] = []
|
||||
|
||||
function appendGroup(values: string[], groupLabel: string) {
|
||||
const selectedValues = values.filter((item) => selected.has(item))
|
||||
selectedValues.forEach((item) => consumed.add(item))
|
||||
if (selectedValues.length === values.length) {
|
||||
labels.push(groupLabel)
|
||||
return
|
||||
}
|
||||
selectedValues.forEach((item) => {
|
||||
labels.push(`${preprocessOptionLabelMap[item] || item}(历史部分配置)`)
|
||||
})
|
||||
}
|
||||
|
||||
appendGroup(['clean_invalid', 'deduplicate'], '数据清洗')
|
||||
appendGroup(['detect_structure', 'normalize_format'], '结构标准化')
|
||||
|
||||
if (selected.has('desensitize')) {
|
||||
consumed.add('desensitize')
|
||||
labels.push('敏感信息脱敏')
|
||||
}
|
||||
if (selected.has('filter_anomaly')) {
|
||||
consumed.add('filter_anomaly')
|
||||
labels.push('异常数据过滤(历史规则)')
|
||||
}
|
||||
|
||||
options.forEach((item) => {
|
||||
if (!consumed.has(item)) labels.push(preprocessOptionLabelMap[item] || item)
|
||||
})
|
||||
return labels.length ? labels.join('、') : '-'
|
||||
}
|
||||
|
||||
function numeric(value: unknown) {
|
||||
const parsed = typeof value === 'number' ? value : Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : 0
|
||||
@@ -199,6 +247,11 @@ const canRegenerate = computed(() => {
|
||||
|| status === 'stopped'
|
||||
|| (status === 'completed' && (Boolean(outputDatasetId.value) || hasPublishedOutputs.value))
|
||||
})
|
||||
const canRepeatGeneration = computed(() => (
|
||||
detail.value?.status === 'completed'
|
||||
&& detail.value.results_confirmed !== false
|
||||
&& previewCount.value > 0
|
||||
))
|
||||
const creatorName = computed(() => detail.value?.creator_name || detail.value?.creator || '-')
|
||||
const createTime = computed(() => detail.value?.create_time || detail.value?.created_at)
|
||||
const startTime = computed(() => detail.value?.start_time || detail.value?.started_at)
|
||||
@@ -263,9 +316,14 @@ function formatConfigValue(key: string, value: unknown) {
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
if (key === 'preprocess_options') {
|
||||
return value.length
|
||||
? value.map((item) => preprocessOptionLabelMap[String(item)] || String(item)).join('、')
|
||||
: '-'
|
||||
const containsStructuredOption = value.some((item) => (
|
||||
structuredPreprocessOptionKeys.has(String(item))
|
||||
))
|
||||
return containsStructuredOption
|
||||
? formatStructuredPreprocessOptions(value)
|
||||
: value.length
|
||||
? value.map((item) => preprocessOptionLabelMap[String(item)] || String(item)).join('、')
|
||||
: '-'
|
||||
}
|
||||
return value.length ? value.join('、') : '-'
|
||||
}
|
||||
@@ -503,6 +561,48 @@ function startRegeneration() {
|
||||
void router.push({ name: 'data-process-regenerate', params: { id: taskId.value } })
|
||||
}
|
||||
|
||||
function createRepeatRequestId() {
|
||||
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
||||
return globalThis.crypto.randomUUID()
|
||||
}
|
||||
return `${Date.now()}_${Math.random().toString(36).slice(2, 14)}`
|
||||
}
|
||||
|
||||
async function repeatGeneration() {
|
||||
if (!detail.value?.updated_at || repeatGenerating.value) return
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'系统会复制当前配置、源文件和切分结果,创建一个独立的新任务并在后台生成。原任务和原结果不会被修改。',
|
||||
'按原配置再生成一批?',
|
||||
{
|
||||
confirmButtonText: '创建并开始生成',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info',
|
||||
},
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
repeatGenerating.value = true
|
||||
repeatRequestId.value ||= createRepeatRequestId()
|
||||
try {
|
||||
const repeated = await repeatDataProcessTask(taskId.value, {
|
||||
expected_updated_at: detail.value.updated_at,
|
||||
request_id: repeatRequestId.value,
|
||||
})
|
||||
ElMessage.success(repeated.created ? '已创建新任务,正在后台生成' : '已恢复此前创建的新任务')
|
||||
await router.push({
|
||||
name: 'data-process-workflow',
|
||||
params: { id: repeated.task.id },
|
||||
})
|
||||
} catch {
|
||||
// 保留幂等请求 ID;网络超时后再次点击不会重复创建任务。
|
||||
} finally {
|
||||
repeatGenerating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch([currentPage, pageSize], () => void loadResults())
|
||||
|
||||
onMounted(loadPage)
|
||||
@@ -520,22 +620,32 @@ onBeforeUnmount(() => {
|
||||
<el-tag :type="displayStatus.type" size="small" effect="light">
|
||||
{{ displayStatus.label }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
v-if="detail.status === 'completed' && !hasCurrentPublishedDataset"
|
||||
class="publish-button"
|
||||
type="primary"
|
||||
@click="openPublishDialog"
|
||||
>
|
||||
<i class="fa fa-database" style="margin-right: 4px;" />发布为三个数据集
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canRegenerate"
|
||||
class="publish-button"
|
||||
type="primary"
|
||||
@click="startRegeneration"
|
||||
>
|
||||
<i class="fa fa-refresh" style="margin-right: 4px;" />重新生成
|
||||
</el-button>
|
||||
<div class="heading-actions">
|
||||
<el-button
|
||||
v-if="detail.status === 'completed' && !hasCurrentPublishedDataset"
|
||||
type="primary"
|
||||
@click="openPublishDialog"
|
||||
>
|
||||
<i class="fa fa-database" style="margin-right: 4px;" />发布为三个数据集
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canRepeatGeneration"
|
||||
type="primary"
|
||||
:loading="repeatGenerating"
|
||||
:disabled="repeatGenerating"
|
||||
@click="repeatGeneration"
|
||||
>
|
||||
<i class="fa fa-clone" style="margin-right: 4px;" />按原配置再生成一批
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canRegenerate"
|
||||
type="warning"
|
||||
plain
|
||||
@click="startRegeneration"
|
||||
>
|
||||
<i class="fa fa-refresh" style="margin-right: 4px;" />覆盖当前任务重新生成
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<p>{{ detail.description || '暂无任务描述' }}</p>
|
||||
<dl class="heading-meta">
|
||||
@@ -804,7 +914,15 @@ onBeforeUnmount(() => {
|
||||
> p { margin: 8px 0 0; color: #64748b; font-size: 13px; }
|
||||
}
|
||||
|
||||
.publish-button { margin-left: auto; }
|
||||
.heading-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
|
||||
:deep(.el-button + .el-button) { margin-left: 0; }
|
||||
}
|
||||
.load-state-actions { display: flex; gap: 10px; }
|
||||
.compact-empty { padding: 28px 18px; color: #94a3b8; font-size: 13px; text-align: center; }
|
||||
.publish-form-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
|
||||
@@ -968,7 +1086,8 @@ onBeforeUnmount(() => {
|
||||
@media (max-width: 720px) {
|
||||
.metric-grid, .config-grid { grid-template-columns: 1fr; }
|
||||
.detail-heading .heading-row { align-items: flex-start; flex-wrap: wrap; }
|
||||
.publish-button { width: 100%; margin-left: 0; }
|
||||
.heading-actions { width: 100%; margin-left: 0; }
|
||||
.heading-actions :deep(.el-button) { width: 100%; }
|
||||
.publish-form-grid { grid-template-columns: 1fr; gap: 0; }
|
||||
.result-toolbar { align-items: stretch; flex-direction: column; }
|
||||
.result-filters { padding: 0 16px 16px; flex-direction: column; }
|
||||
|
||||
Reference in New Issue
Block a user