Files
YG_FT/frontend/src/views/data-convert/DataConvertView.vue
wuyongtao 93449b7f0e fix(frontend): 修复操作日志与数据转换类型错误,修正普通用户角色取值
- OperationLogView: showDetail 调用补充 OperationLog 类型断言
- DataConvertView: beforeUpload 参数类型改为 UploadRawFile 并直接读取 file.type
- UserCreateView: 普通用户角色值 user 修正为 operator

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 11:33:45 +08:00

276 lines
9.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Delete, Refresh } from '@element-plus/icons-vue'
import type { TagProps, UploadRequestOptions, UploadFile, UploadRawFile } from 'element-plus'
import PageCard from '@/components/PageCard.vue'
import {
getDataConvertTasks,
createDataConvertTask,
uploadSourceFiles,
deleteDataConvertTask,
type DataConvertTask,
} from '@/api/modules/data-convert'
const loading = ref(false)
const tasks = ref<DataConvertTask[]>([])
const showCreate = ref(false)
const form = ref({ name: '', outputName: 'converted-data' })
const fileList = ref<UploadFile[]>([])
const creating = ref(false)
async function load() {
loading.value = true
try {
const res = await getDataConvertTasks()
tasks.value = res.items || []
} finally {
loading.value = false
}
}
// 文件上传前的校验(仅校验文件格式)
function beforeUpload(file: UploadRawFile) {
// 检查文件类型
const isJson = file.name.endsWith('.json') || file.type === 'application/json'
if (!isJson) {
ElMessage.error('只能上传 .json 格式的文件')
return false
}
return true
}
// 手动点击"创建并上传"
async function submitCreate() {
// 校验任务名称
if (!form.value.name || !form.value.name.trim()) {
ElMessage.warning('请填写任务名称')
return
}
// 检查名称是否重复
const exists = tasks.value.some((t) => t.name === form.value.name.trim())
if (exists) {
ElMessage.error(`数据集管理中已存在名为「${form.value.name}」的任务,请换一个名称`)
return
}
// 检查是否选择了文件
if (!fileList.value || fileList.value.length === 0) {
ElMessage.warning('请选择要上传的 JSON 文件')
return
}
creating.value = true
try {
// 1. 创建转换任务
const task = await createDataConvertTask({
name: form.value.name.trim(),
output_filename: form.value.outputName.trim() + '.jsonl',
})
// 2. 获取任务 ID
const taskId = (task as any)?.id || task?.id || (task as any)?.data?.id
if (!taskId) {
throw new Error('创建任务失败,服务端未返回任务 ID')
}
// 3. 上传文件到刚创建的任务
const file = fileList.value[0].raw
if (!file) {
throw new Error('文件信息丢失,请重新选择文件')
}
const res = await uploadSourceFiles(taskId, [file])
const data = (res as any)?.data || res
if (data?.auto_converted) {
ElMessage.success(
`创建成功!文件已上传并自动转换完成(输入 ${data.input_count} 条 / 输出 ${data.output_count} 条),结果已导入数据集`,
)
} else if (data?.error) {
ElMessage.error(`文件上传成功但转换失败:${data.error}`)
} else {
ElMessage.warning('文件已上传,等待后台转换处理...')
}
// 关闭弹窗并刷新列表
showCreate.value = false
resetForm()
load()
} catch (e: any) {
// 根据错误类型给出更清晰的提示
const msg = e?.message || e?.toString() || '未知错误'
if (msg.includes('409') || msg.includes('conflict') || msg.includes('已存在') || msg.includes('duplicate')) {
ElMessage.error(`数据集管理中已存在名为「${form.value.name}」的任务,请换一个名称`)
} else if (msg.includes('400')) {
ElMessage.error(`参数错误:${msg}`)
} else if (msg.includes('403') || msg.includes('权限')) {
ElMessage.error(`没有权限执行此操作:${msg}`)
} else {
ElMessage.error(`操作失败:${msg}`)
}
} finally {
creating.value = false
}
}
// 自定义上传(表格中的上传按钮仍使用此方法)
async function customUpload(options: UploadRequestOptions) {
const taskId = options.data?.taskId as string
if (!taskId) {
ElMessage.error('任务 ID 缺失')
return
}
try {
const res = await uploadSourceFiles(taskId, [options.file])
const data = (res as any)?.data || res
if (data?.auto_converted) {
ElMessage.success(
`上传并自动转换完成,输入 ${data.input_count} / 输出 ${data.output_count},已自动导入数据集`,
)
} else {
ElMessage.warning('上传完成,但转换失败:' + (data?.error || '未知错误'))
}
load()
} catch (e: any) {
ElMessage.error('上传失败:' + (e?.message || '未知错误'))
}
}
// 重置表单
function resetForm() {
form.value = { name: '', outputName: 'converted-data' }
fileList.value = []
}
// 移除已选文件
function handleRemoveFile(file: UploadFile) {
fileList.value = fileList.value.filter((f) => f.uid !== file.uid)
}
async function handleDelete(task: DataConvertTask) {
try {
await ElMessageBox.confirm(
`确定要删除任务「${task.name}」吗?`,
'删除确认',
{ confirmButtonText: '确定删除', cancelButtonText: '取消', type: 'warning' },
)
} catch {
return
}
await deleteDataConvertTask(task.id)
ElMessage.success('任务已删除')
load()
}
function statusTag(status: string) {
const map: Record<string, { type: TagProps['type']; label: string }> = {
pending: { type: 'info', label: '待上传' },
uploaded: { type: 'warning', label: '已上传' },
running: { type: 'warning', label: '转换中' },
completed: { type: 'success', label: '已完成' },
failed: { type: 'danger', label: '失败' },
}
return map[status] || { type: 'info', label: status }
}
onMounted(load)
</script>
<template>
<PageCard
class="data-convert-page"
title="数据类型转换"
subtitle="将 JSON 文件转换为 JSONL 格式,转换结果自动导入到数据集管理"
>
<div class="toolbar">
<el-button type="primary" :icon="Plus" @click="showCreate = true">新建转换任务</el-button>
<el-button :icon="Refresh" @click="load">刷新</el-button>
</div>
<el-table :data="tasks" v-loading="loading" border stripe>
<el-table-column prop="name" label="任务名称" min-width="140" />
<el-table-column label="状态" min-width="100">
<template #default="{ row }">
<el-tag :type="statusTag(row.status).type">{{ statusTag(row.status).label }}</el-tag>
</template>
</el-table-column>
<el-table-column label="数据量" min-width="120">
<template #default="{ row }">
{{ row.output_count }}
</template>
</el-table-column>
<el-table-column prop="output_filename" label="输出文件名" min-width="160" />
<el-table-column prop="create_time" label="创建时间" min-width="180" />
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<el-upload
v-if="row.status === 'pending'"
:auto-upload="true"
:show-file-list="false"
accept=".json"
:http-request="customUpload"
:data="{ taskId: row.id }"
style="display: inline-block; margin-right: 8px"
>
<el-button link type="primary">上传文件</el-button>
</el-upload>
<el-button link type="danger" :icon="Delete" @click="handleDelete(row as DataConvertTask)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新建任务弹窗一步到位填写信息 + 上传文件 -->
<el-dialog v-model="showCreate" title="新建转换任务" width="520px" :close-on-click-modal="false">
<el-form label-width="100px">
<el-form-item label="任务名称" required>
<el-input v-model="form.name" placeholder="请输入任务名称" clearable />
</el-form-item>
<el-form-item label="输出文件名">
<el-input v-model="form.outputName" placeholder="converted-data" clearable>
<template #append>.jsonl</template>
</el-input>
</el-form-item>
<el-form-item label="上传文件" required>
<el-upload
ref="uploadRef"
v-model:file-list="fileList"
:auto-upload="false"
:limit="1"
accept=".json"
:before-upload="beforeUpload"
:on-remove="handleRemoveFile"
:disabled="creating"
drag
>
<el-icon class="el-icon--upload"><Plus /></el-icon>
<div class="el-upload__text"> JSON 文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">仅支持 .json 格式文件点击"创建并上传"按钮后自动创建任务并上传文件</div>
</template>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showCreate = false" :disabled="creating">取消</el-button>
<el-button type="primary" :loading="creating" :disabled="!form.name || fileList.length === 0" @click="submitCreate">
{{ creating ? '处理中...' : '创建并上传' }}
</el-button>
</template>
</el-dialog>
</PageCard>
</template>
<style scoped lang="scss">
.toolbar {
display: flex;
gap: 10px;
margin-bottom: 16px;
}
:deep(.el-upload-dragger) {
width: 100%;
.el-upload__text {
padding: 20px 0;
}
}
</style>