- OperationLogView: showDetail 调用补充 OperationLog 类型断言 - DataConvertView: beforeUpload 参数类型改为 UploadRawFile 并直接读取 file.type - UserCreateView: 普通用户角色值 user 修正为 operator Co-Authored-By: Claude <noreply@anthropic.com>
276 lines
9.2 KiB
Vue
276 lines
9.2 KiB
Vue
<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>
|