fix: 提交代码
This commit is contained in:
parent
fda6156df5
commit
87ad6f9428
@ -3,44 +3,44 @@ import request from '@/config/axios'
|
||||
// 获取规则类目列表
|
||||
export const getAttCleanCatList = (params) => {
|
||||
return request.get({
|
||||
url: '/v2/att/attCleanCat/list',
|
||||
url: '/att/attCleanCat/list',
|
||||
params
|
||||
})
|
||||
}
|
||||
// 添加规则类目
|
||||
export const saveAttCleanCat = (data) => {
|
||||
return request.post({ url: '/v2/att/attCleanCat', data })
|
||||
return request.post({ url: '/att/attCleanCat', data })
|
||||
}
|
||||
|
||||
// 更新规则类目
|
||||
export const updateAttCleanCat = (data) => {
|
||||
return request.put({ url: '/v2/att/attCleanCat', data })
|
||||
return request.put({ url: '/att/attCleanCat', data })
|
||||
}
|
||||
|
||||
// 删除规则类目
|
||||
export const deleteAttCleanCat = (id) => {
|
||||
return request.delete({ url: `/v2/att/attCleanCat/${id}`})
|
||||
return request.delete({ url: `/att/attCleanCat/${id}`})
|
||||
}
|
||||
|
||||
// 获取规则列表
|
||||
export const getAttCleanRuleList = (params) => {
|
||||
return request.get({
|
||||
url: '/v2/att/attCleanRule/list',
|
||||
url: '/att/attCleanRule/list',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 添加规则
|
||||
export const saveAttCleanRule = (data) => {
|
||||
return request.post({ url: '/v2/att/attCleanRule', data })
|
||||
return request.post({ url: '/att/attCleanRule', data })
|
||||
}
|
||||
|
||||
// 更新规则
|
||||
export const updateAttCleanRule = (data) => {
|
||||
return request.put({ url: '/v2/att/attCleanRule', data })
|
||||
return request.put({ url: '/att/attCleanRule', data })
|
||||
}
|
||||
|
||||
// 删除规则
|
||||
export const deleteAttCleanRule = (id) => {
|
||||
return request.delete({ url: `/v2/att/attCleanRule/${id}`})
|
||||
return request.delete({ url: `/att/attCleanRule/${id}`})
|
||||
}
|
@ -3,22 +3,22 @@ import request from '@/config/axios'
|
||||
// 获取规则列表
|
||||
export const getAttAuditRuleList = (params) => {
|
||||
return request.get({
|
||||
url: '/v2/att/attAuditRule/list',
|
||||
url: '/att/attAuditRule/list',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 添加规则
|
||||
export const addAttAuditRule = (data) => {
|
||||
return request.post({ url: '/v2/att/attAuditRule', data })
|
||||
return request.post({ url: '/att/attAuditRule', data })
|
||||
}
|
||||
|
||||
// 更新规则
|
||||
export const updateAttAuditRule = (data) => {
|
||||
return request.put({ url: '/v2/att/attAuditRule', data })
|
||||
return request.put({ url: '/att/attAuditRule', data })
|
||||
}
|
||||
|
||||
// 删除规则
|
||||
export const deleteAttAuditRule = (id) => {
|
||||
return request.delete({ url: `/v2/att/attAuditRule/${id}`})
|
||||
return request.delete({ url: `/att/attAuditRule/${id}`})
|
||||
}
|
@ -78,3 +78,7 @@ export const importUserTemplates = (params: any) => {
|
||||
return request.download({ url: '/llm/dataset/download-example', params })
|
||||
}
|
||||
|
||||
export const getAllList = async () => {
|
||||
return await request.get({url: '/data/data-set-middle/getAllList'})
|
||||
}
|
||||
|
||||
|
80
src/api/dataService/dataset.ts
Normal file
80
src/api/dataService/dataset.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
|
||||
export interface DataSetVO {
|
||||
id?: string //数据集ID
|
||||
datasetName: string //数据集名称
|
||||
datasetCategory: string //数据集类型
|
||||
status: string //状态
|
||||
datasetIntro: string //数据集描述
|
||||
datasetFile: string //数据文件
|
||||
datasetType: string //数据集类型
|
||||
datasetFileUrl: string //文件URL地址
|
||||
tenantId: number //租户编号
|
||||
}
|
||||
|
||||
|
||||
// 新增
|
||||
export const createDataSet = (data: DataSetVO) => {
|
||||
return request.post({url: '/data/data-set-middle/create', data})
|
||||
}
|
||||
|
||||
// 新增 - 用于datasetParentType为2的情况
|
||||
export const createDataSetV2 = (data: DataSetVO) => {
|
||||
return request.post({url: '/llm/dataset/createDatasetMoreModal', data})
|
||||
}
|
||||
|
||||
// 修改
|
||||
export const updateDataSet = (data: DataSetVO) => {
|
||||
return request.put({url: '/data/data-set-middle/update', data})
|
||||
}
|
||||
|
||||
// 修改 - 用于datasetParentType为2的情况
|
||||
export const updateDataSetV2 = (data: DataSetVO) => {
|
||||
return request.put({url: '/llm/dataset/update-v2', data})
|
||||
}
|
||||
|
||||
// 删除
|
||||
export const deleteDataSet = (id: number) => {
|
||||
return request.delete({url: '/llm/dataset/delete?id=' + id})
|
||||
}
|
||||
|
||||
// 查询详情
|
||||
export const getDeta = (id: number) => {
|
||||
return request.get({url: '/data/data-set-middle/getOneInfo?id=' + id})
|
||||
}
|
||||
|
||||
// 查询列表
|
||||
export const getPage = async (params: PageParam) => {
|
||||
return await request.get({url: '/data/data-set-middle/page', params})
|
||||
}
|
||||
|
||||
// 查询全部
|
||||
export const getAll = async () => {
|
||||
return await request.get({url: '/llm/dataset/all'})
|
||||
}
|
||||
|
||||
// 获得数据集数据问题分页
|
||||
export const getQuestionPageList = async (params: any) => {
|
||||
return await request.get({url: '/platform/dataset-question/page', params})
|
||||
}
|
||||
|
||||
// 保存标注接口
|
||||
export const updateQuestion = (data: any) => {
|
||||
return request.put({url: '/platform/dataset-question/data-anno', data})
|
||||
}
|
||||
|
||||
// 导出数据集
|
||||
export const exportData = (params: any) => {
|
||||
return request.download({ url: '/llm/dataset-question/export-excel', params })
|
||||
}
|
||||
|
||||
// 下载数据集模板
|
||||
export const importUserTemplate = () => {
|
||||
return request.download({ url: '/infra/file/29/get/8e05fde5b769415cc7a6323aa3d7b86fdd2daa067018d16d03eb3b7cf9707801.json' })
|
||||
}
|
||||
// 下载数据集模板
|
||||
export const importUserTemplates = (params: any) => {
|
||||
return request.download({ url: '/llm/dataset/download-example', params })
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="asset-map-container">
|
||||
<el-card shadow="never" class="mb-20">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>资产地图</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 页面内容区域 -->
|
||||
<div class="content-area">
|
||||
<div class="empty-state" v-if="!dataLoaded">
|
||||
<el-empty description="暂无数据" />
|
||||
</div>
|
||||
<div class="map-content" v-else>
|
||||
<!-- 地图内容将在这里渲染 -->
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
// 响应式数据
|
||||
const dataLoaded = ref(false)
|
||||
|
||||
// 国际化和消息提示
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
try {
|
||||
// 这里可以添加获取数据的逻辑
|
||||
// await fetchMapData()
|
||||
dataLoaded.value = true
|
||||
} catch (error) {
|
||||
console.error('初始化资产地图数据失败:', error)
|
||||
message.error('加载数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.asset-map-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.map-content {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="asset-map-container">
|
||||
<el-card shadow="never" class="mb-20">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>数据链接</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 页面内容区域 -->
|
||||
<div class="content-area">
|
||||
<div class="empty-state" v-if="!dataLoaded">
|
||||
<el-empty description="暂无数据" />
|
||||
</div>
|
||||
<div class="map-content" v-else>
|
||||
<!-- 地图内容将在这里渲染 -->
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
// 响应式数据
|
||||
const dataLoaded = ref(false)
|
||||
|
||||
// 国际化和消息提示
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
try {
|
||||
// 这里可以添加获取数据的逻辑
|
||||
// await fetchMapData()
|
||||
dataLoaded.value = true
|
||||
} catch (error) {
|
||||
console.error('初始化资产地图数据失败:', error)
|
||||
message.error('加载数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.asset-map-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.map-content {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="asset-map-container">
|
||||
<el-card shadow="never" class="mb-20">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>数据查询</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 页面内容区域 -->
|
||||
<div class="content-area">
|
||||
<div class="empty-state" v-if="!dataLoaded">
|
||||
<el-empty description="暂无数据" />
|
||||
</div>
|
||||
<div class="map-content" v-else>
|
||||
<!-- 地图内容将在这里渲染 -->
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
// 响应式数据
|
||||
const dataLoaded = ref(false)
|
||||
|
||||
// 国际化和消息提示
|
||||
const message = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
try {
|
||||
// 这里可以添加获取数据的逻辑
|
||||
// await fetchMapData()
|
||||
dataLoaded.value = true
|
||||
} catch (error) {
|
||||
console.error('初始化资产地图数据失败:', error)
|
||||
message.error('加载数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.asset-map-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.map-content {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
</style>
|
@ -121,6 +121,7 @@ const submitForm = async () => {
|
||||
}
|
||||
|
||||
const fileItem = fileList.value[0];
|
||||
console.log('========>',fileItem.raw)
|
||||
if (!fileItem || !fileItem.raw) {
|
||||
message.error('文件上传异常,请重新选择文件');
|
||||
return;
|
||||
|
@ -132,8 +132,10 @@ const submitForm = async () => {
|
||||
|
||||
console.log(importFormRef.value.fileList)
|
||||
if (importFormRef.value.fileList.length == 0 && route?.query.id || (Array.isArray(form.value.datasetFiles) && form.value.datasetFiles.length > 0)) {
|
||||
console.log(111)
|
||||
await importSuccess()
|
||||
} else {
|
||||
console.log(222)
|
||||
await importFormRef.value.submitForm()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,519 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="新增清洗规则"
|
||||
width="900px"
|
||||
:before-close="handleCancel"
|
||||
>
|
||||
<!-- 左侧导航菜单 -->
|
||||
<div class="dialog-content">
|
||||
<el-aside width="240px" class="sidebar">
|
||||
<!-- 导航头部搜索框 -->
|
||||
<div class="sidebar-header">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入规则类型"
|
||||
:prefix-icon="Search"
|
||||
class="sidebar-search"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 动态导航菜单 -->
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
class="quality-menu"
|
||||
background-color="#ffffff"
|
||||
text-color="#1D2129"
|
||||
active-text-color="#165DFF"
|
||||
:unique-opened="true"
|
||||
>
|
||||
<!-- 动态生成菜单 -->
|
||||
<template v-for="menu in filteredMenus" :key="menu.index">
|
||||
<!-- 有子菜单的情况 -->
|
||||
<el-sub-menu v-if="menu.children && menu.children.length" :index="menu.index">
|
||||
<template #title>
|
||||
<span><el-icon><List /></el-icon>{{ menu.title }}</span>
|
||||
</template>
|
||||
<!-- 二级菜单 -->
|
||||
<el-menu-item
|
||||
v-for="subMenu in menu.children"
|
||||
:key="subMenu.index"
|
||||
:index="subMenu.index"
|
||||
@click="handleMenuClick(subMenu)"
|
||||
>
|
||||
<span><el-icon><List /></el-icon>{{ subMenu.title }}</span>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<!-- 没有子菜单的情况 -->
|
||||
<el-menu-item v-else :index="menu.index" @click="handleMenuClick(menu)">
|
||||
<span><el-icon><List /></el-icon>{{ menu.title }}</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<el-main class="main">
|
||||
<!-- 规则列表 -->
|
||||
<div class="rule-grid">
|
||||
<div
|
||||
v-for="rule in filteredRules"
|
||||
:key="rule.id"
|
||||
class="rule-card"
|
||||
:class="{ 'selected': selectedRules.includes(rule.id) }"
|
||||
@click="toggleRuleSelection(rule)"
|
||||
>
|
||||
<div class="rule-card-header">
|
||||
<div class="rule-title">{{ rule.name }}</div>
|
||||
<el-checkbox v-model="rule.selected" @change="toggleRuleSelection(rule)">
|
||||
<span v-if="rule.status" class="status-badge">上线</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<div class="rule-code">{{ rule.rule }}</div>
|
||||
<div class="rule-description">{{ rule.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { Search, List } from '@element-plus/icons-vue'
|
||||
// 导入API,这里使用和index.vue相同的API
|
||||
import { getAttCleanCatList, getAttCleanRuleList } from '@/api/base/cleaningRules'
|
||||
|
||||
// 定义类型
|
||||
interface MenuNode {
|
||||
index: string
|
||||
title: string
|
||||
id: number
|
||||
parentId: number
|
||||
sortOrder?: number
|
||||
children: MenuNode[]
|
||||
}
|
||||
|
||||
interface CleaningRule {
|
||||
id: string
|
||||
name: string
|
||||
rule: string
|
||||
description: string
|
||||
category: string
|
||||
status: boolean
|
||||
selected: boolean
|
||||
}
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false
|
||||
})
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
'confirm': [selectedRules: any[]]
|
||||
'cancel': []
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
|
||||
// 搜索关键词
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 当前激活的菜单
|
||||
const activeMenu = ref('')
|
||||
|
||||
// 已选择的规则ID列表
|
||||
const selectedRules = ref<string[]>([])
|
||||
|
||||
// 导航菜单数据
|
||||
const menuData = ref<MenuNode[]>([])
|
||||
// 菜单加载状态
|
||||
const menuLoading = ref(false)
|
||||
|
||||
// 从接口获取菜单数据并转换为树形结构
|
||||
const loadMenuData = async () => {
|
||||
try {
|
||||
menuLoading.value = true
|
||||
// 调用接口获取数据,传入空对象作为参数
|
||||
const response = await getAttCleanCatList({})
|
||||
|
||||
// 将扁平数据转换为树形结构
|
||||
menuData.value = convertToTree(response.data || response)
|
||||
|
||||
// 设置默认激活的第一个菜单项
|
||||
if (menuData.value.length > 0) {
|
||||
const firstMenu = menuData.value[0]
|
||||
if (firstMenu.children && firstMenu.children.length > 0) {
|
||||
activeMenu.value = firstMenu.children[0].index
|
||||
} else {
|
||||
activeMenu.value = firstMenu.index
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取清洗规则类型数据失败:', error)
|
||||
// 如果接口调用失败,使用模拟数据
|
||||
const mockData = [
|
||||
{ id: 1, code: 'accuracy', name: '准确性修正', parentId: 0, sortOrder: 1 },
|
||||
{ id: 2, code: 'accuracy-1', name: '数值边界调整', parentId: 1, sortOrder: 1 },
|
||||
{ id: 3, code: 'accuracy-2', name: '字段前后缀统一', parentId: 1, sortOrder: 2 },
|
||||
{ id: 4, code: 'completeness', name: '完整性修复', parentId: 0, sortOrder: 2 },
|
||||
{ id: 5, code: 'completeness-1', name: '组合字段为空删除', parentId: 4, sortOrder: 1 },
|
||||
{ id: 6, code: 'completeness-2', name: '字段补零', parentId: 4, sortOrder: 2 },
|
||||
{ id: 7, code: 'consistency', name: '一致性修正', parentId: 0, sortOrder: 3 },
|
||||
{ id: 8, code: 'consistency-1', name: '枚举值映射标准化', parentId: 7, sortOrder: 1 },
|
||||
{ id: 9, code: 'consistency-2', name: '字段值替换', parentId: 7, sortOrder: 2 },
|
||||
{ id: 10, code: 'uniqueness', name: '唯一性维护', parentId: 0, sortOrder: 4 },
|
||||
{ id: 11, code: 'uniqueness-1', name: '手机号格式统一', parentId: 10, sortOrder: 1 },
|
||||
{ id: 12, code: 'uniqueness-2', name: '日期格式统一', parentId: 10, sortOrder: 2 },
|
||||
{ id: 13, code: 'validity', name: '有效性处理', parentId: 0, sortOrder: 5 },
|
||||
{ id: 14, code: 'validity-1', name: '字符串转数值', parentId: 13, sortOrder: 1 },
|
||||
{ id: 15, code: 'validity-2', name: '字符串转日期', parentId: 13, sortOrder: 2 },
|
||||
{ id: 16, code: 'validity-3', name: '任意类型转布尔值', parentId: 13, sortOrder: 3 },
|
||||
{ id: 17, code: 'timeliness', name: '及时性调整', parentId: 0, sortOrder: 6 },
|
||||
{ id: 18, code: 'timeliness-1', name: '小数位统一', parentId: 17, sortOrder: 1 },
|
||||
{ id: 19, code: 'timeliness-2', name: '去除字段空格', parentId: 17, sortOrder: 2 },
|
||||
{ id: 20, code: 'timeliness-3', name: '正则表达式替换', parentId: 17, sortOrder: 3 },
|
||||
{ id: 21, code: 'timeliness-4', name: '超长字段截断', parentId: 17, sortOrder: 4 },
|
||||
{ id: 22, code: 'timeliness-5', name: '数值空值填充', parentId: 17, sortOrder: 5 }
|
||||
]
|
||||
menuData.value = convertToTree(mockData)
|
||||
|
||||
// 设置默认激活的第一个菜单项
|
||||
if (menuData.value.length > 0) {
|
||||
const firstMenu = menuData.value[0]
|
||||
if (firstMenu.children && firstMenu.children.length > 0) {
|
||||
activeMenu.value = firstMenu.children[0].index
|
||||
} else {
|
||||
activeMenu.value = firstMenu.index
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
menuLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 将扁平数组转换为树形结构
|
||||
const convertToTree = (items: any[]): MenuNode[] => {
|
||||
if (!items || !Array.isArray(items)) return []
|
||||
|
||||
// 首先将所有项按id映射,方便查找
|
||||
const map: Record<number, MenuNode> = {}
|
||||
const tree: MenuNode[] = []
|
||||
|
||||
// 先创建所有节点的映射
|
||||
items.forEach(item => {
|
||||
map[item.id] = {
|
||||
index: item.code, // 使用code作为索引值
|
||||
title: item.name, // 使用name作为标题
|
||||
id: item.id,
|
||||
parentId: item.parentId,
|
||||
sortOrder: item.sortOrder,
|
||||
children: []
|
||||
}
|
||||
})
|
||||
|
||||
// 构建树形结构
|
||||
items.forEach(item => {
|
||||
const node = map[item.id]
|
||||
if (item.parentId === 0) {
|
||||
// 根节点
|
||||
tree.push(node)
|
||||
} else {
|
||||
// 找到父节点并添加到其子节点中
|
||||
const parent = map[item.parentId]
|
||||
if (parent) {
|
||||
parent.children.push(node)
|
||||
} else {
|
||||
// 如果找不到父节点,作为根节点处理
|
||||
tree.push(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 按sortOrder排序
|
||||
const sortByOrder = (nodes: MenuNode[]) => {
|
||||
if (!nodes || !nodes.length) return
|
||||
|
||||
nodes.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
|
||||
nodes.forEach(node => {
|
||||
if (node.children && node.children.length) {
|
||||
sortByOrder(node.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sortByOrder(tree)
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
// 清洗规则数据
|
||||
const rulesData = ref<CleaningRule[]>([])
|
||||
// 规则加载状态
|
||||
const rulesLoading = ref(false)
|
||||
|
||||
// 从接口获取规则数据
|
||||
const loadRulesData = async () => {
|
||||
try {
|
||||
rulesLoading.value = true
|
||||
// 调用接口获取规则数据
|
||||
const response = await getAttCleanRuleList({})
|
||||
|
||||
// 处理返回的数据,确保每个规则都有selected属性
|
||||
const rules = response.data || response || []
|
||||
rulesData.value = rules.map(rule => ({
|
||||
...rule,
|
||||
selected: false // 初始化selected属性
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('获取清洗规则数据失败:', error)
|
||||
// 如果接口调用失败,使用模拟数据
|
||||
rulesData.value = [
|
||||
{ id: '1', name: '数值边界调整', rule: '数值边界调整', description: '将超过预设范围的数值调整为合理的边界值。', category: 'accuracy-1', status: true, selected: false },
|
||||
{ id: '2', name: '字段前后缀统一', rule: '字段前后缀统一', description: '对宇段值统一加前后缀,或去除非业务所需的标记。', category: 'accuracy-2', status: true, selected: false },
|
||||
{ id: '3', name: '组合字段为空删除', rule: '组合字段为空删除', description: '配置关键宇段(如主键)为空时,删除整行记录。', category: 'completeness-1', status: true, selected: false },
|
||||
{ id: '4', name: '枚举值映射标准化', rule: '枚举值映射标准化', description: '将宇段值根据字典进行映射转换,保留值统一。', category: 'consistency-1', status: true, selected: false },
|
||||
{ id: '5', name: '字段值替换', rule: '字段值替换', description: '将不符合规则设置的数值替换为指定的默认或空值。', category: 'consistency-2', status: true, selected: false },
|
||||
{ id: '6', name: '字符串转数值', rule: '字符串转数值', description: '将规范宇符串转换为数值类型。', category: 'validity-1', status: true, selected: false },
|
||||
{ id: '7', name: '字符串转日期', rule: '字符串转日期', description: '将符合格式的宇符串转换为日期类型。', category: 'validity-2', status: true, selected: false },
|
||||
{ id: '8', name: '任意类型转布尔值', rule: '任意类型转布尔值', description: '将是/否、"1"/"0"、"true"/"false"等转换为布尔值。', category: 'validity-3', status: true, selected: false },
|
||||
{ id: '9', name: '手机号格式统一', rule: '手机号格式统一', description: '统一手机号为国内11位数宇格式,去掉国家位、空格、短横线等。', category: 'uniqueness-1', status: true, selected: false },
|
||||
{ id: '10', name: '日期格式统一', rule: '日期格式统一', description: '统一各种格式日期为指定标准格式(如yyyy-MM-dd),支持配置模板。', category: 'uniqueness-2', status: true, selected: false },
|
||||
{ id: '11', name: '小数位统一', rule: '小数位统一', description: '将数值字段统一保留指定的小数位数(保留两位小数)。', category: 'timeliness-1', status: true, selected: false },
|
||||
{ id: '12', name: '去除字段空格', rule: '去除字段空格', description: '去除宇段中的前导空格或中间多余空格,提升匹配准确性。', category: 'timeliness-2', status: true, selected: false },
|
||||
{ id: '13', name: '正则表达式替换', rule: '正则表达式替换', description: '使用正则表达式匹配并替换字段值。', category: 'timeliness-3', status: true, selected: false },
|
||||
{ id: '14', name: '超长字段截断', rule: '超长字段截断', description: '当字段长度超过指定长度时进行截断处理。', category: 'timeliness-4', status: true, selected: false },
|
||||
{ id: '15', name: '数值空值填充', rule: '数值空值填充', description: '将空的数值字段填充为指定默认值(如0、1、平均值等)。', category: 'timeliness-5', status: true, selected: false },
|
||||
{ id: '16', name: '字段补零', rule: '字段补零', description: '在字段前后补零,保持指定长度。', category: 'completeness-2', status: true, selected: false }
|
||||
]
|
||||
} finally {
|
||||
rulesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 根据搜索关键词过滤菜单
|
||||
const filteredMenus = computed(() => {
|
||||
if (!searchKeyword.value) return menuData.value
|
||||
|
||||
const filterMenu = (menus: MenuNode[]): MenuNode[] => {
|
||||
return menus.filter(menu => {
|
||||
// 如果标题匹配,保留整个菜单项
|
||||
if (menu.title.includes(searchKeyword.value)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果有子菜单,递归过滤
|
||||
if (menu.children && menu.children.length) {
|
||||
const filteredChildren = filterMenu(menu.children)
|
||||
if (filteredChildren.length > 0) {
|
||||
menu.children = filteredChildren
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
return filterMenu(JSON.parse(JSON.stringify(menuData.value)))
|
||||
})
|
||||
|
||||
// 根据选中的菜单过滤规则
|
||||
const filteredRules = computed(() => {
|
||||
if (!activeMenu.value) {
|
||||
return rulesData.value
|
||||
}
|
||||
return rulesData.value.filter(rule => rule.category === activeMenu.value)
|
||||
})
|
||||
|
||||
// 处理菜单点击
|
||||
const handleMenuClick = (menu: MenuNode) => {
|
||||
activeMenu.value = menu.index
|
||||
}
|
||||
|
||||
// 处理规则选择
|
||||
const toggleRuleSelection = (rule: CleaningRule) => {
|
||||
if (rule.selected) {
|
||||
// 取消选择
|
||||
const index = selectedRules.value.indexOf(rule.id)
|
||||
if (index > -1) {
|
||||
selectedRules.value.splice(index, 1)
|
||||
}
|
||||
rule.selected = false
|
||||
} else {
|
||||
// 选择规则
|
||||
selectedRules.value.push(rule.id)
|
||||
rule.selected = true
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载菜单数据和规则数据
|
||||
onMounted(async () => {
|
||||
// 同时加载菜单数据和规则数据
|
||||
await Promise.all([loadMenuData(), loadRulesData()])
|
||||
})
|
||||
|
||||
// 处理确定
|
||||
const handleConfirm = () => {
|
||||
const selected = rulesData.value.filter(rule => selectedRules.value.includes(rule.id))
|
||||
emit('confirm', selected)
|
||||
resetState()
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
// 处理取消
|
||||
const handleCancel = () => {
|
||||
emit('cancel')
|
||||
resetState()
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
const resetState = () => {
|
||||
selectedRules.value = []
|
||||
rulesData.value.forEach(rule => {
|
||||
rule.selected = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-content {
|
||||
display: flex;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: #ffffff;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 侧边栏头部搜索样式 */
|
||||
.sidebar-header {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.sidebar-search {
|
||||
width: 100%;
|
||||
--el-input-height: 32px;
|
||||
background-color: #f5f7fa;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.sidebar-search .el-input__wrapper {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 菜单样式调整 */
|
||||
.quality-menu {
|
||||
border-right: none;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.quality-menu :deep(.el-sub-menu__title),
|
||||
.quality-menu :deep(.el-menu-item) {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 14px;
|
||||
color: #1D2129;
|
||||
}
|
||||
|
||||
.quality-menu :deep(.el-sub-menu__title:hover),
|
||||
.quality-menu :deep(.el-menu-item:hover) {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.quality-menu :deep(.el-menu-item.is-active) {
|
||||
background-color: #e8f3ff !important;
|
||||
color: #165DFF !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 主内容区域样式 */
|
||||
.main {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
/* 规则卡片网格 */
|
||||
.rule-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* 规则卡片样式 */
|
||||
.rule-card {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.rule-card:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border-color: #165DFF;
|
||||
}
|
||||
|
||||
.rule-card.selected {
|
||||
border-color: #165DFF;
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
|
||||
.rule-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.rule-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #1D2129;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.rule-code {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.rule-description {
|
||||
font-size: 13px;
|
||||
color: #86909C;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
background-color: #f0f9eb;
|
||||
color: #67c23a;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
389
src/views/dataPlan/standardDataElement/detail.vue
Normal file
389
src/views/dataPlan/standardDataElement/detail.vue
Normal file
@ -0,0 +1,389 @@
|
||||
<template>
|
||||
<el-card shadow="never" class="b-r-top-15" style="margin-bottom: 10px">
|
||||
<div class="card-header">
|
||||
<el-icon @click="goBack"><ArrowLeft /></el-icon>
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="b-r-bottom-15">
|
||||
<div style="padding: 0 20px;">
|
||||
<!-- 流域代码基本信息 -->
|
||||
<div class="basin-info-container">
|
||||
<h3 class="info-title">流域代码</h3>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">英文名称:</span>
|
||||
<span class="info-value">{{ form.basCode }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">类目编码:</span>
|
||||
<span class="info-value">{{ form.categoryCode || '水土保持' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">类型:</span>
|
||||
<span class="info-value">{{ form.type || '数据元' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">责任人:</span>
|
||||
<span class="info-value">{{ form.responsiblePerson || '管理员' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系电话:</span>
|
||||
<span class="info-value">{{ form.contactPhone || '13800000001' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">字段类型:</span>
|
||||
<span class="info-value">{{ form.fieldType || 'VARCHAR2' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">状态:</span>
|
||||
<span class="info-value">{{ form.status || '启用' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">创建时间:</span>
|
||||
<span class="info-value">{{ form.createTime || '2025-06-12' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">创建人:</span>
|
||||
<span class="info-value">{{ form.creator || 'admin' }}</span>
|
||||
</div>
|
||||
<div class="info-item full-width">
|
||||
<span class="info-label">描述:</span>
|
||||
<span class="info-value">{{ form.description || '主键,唯一标识流域' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关联清洗规则 -->
|
||||
<div class="cleaning-rules-container">
|
||||
<div class="rules-header">
|
||||
<h3 class="rules-title">关联清洗规则</h3>
|
||||
<el-button type="primary" size="small" class="add-rule-btn" @click="showRuleSelector = true">
|
||||
关联 +
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="cleaningRulesList"
|
||||
show-header="true"
|
||||
:header-cell-style="{ background: '#f2f3f5', color: '#6b7785', fontWeight: 'bold' }"
|
||||
border
|
||||
>
|
||||
<el-table-column label="序号" width="80">
|
||||
<template #default="scope">
|
||||
{{ scope.$index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="清洗名称" prop="name" min-width="150" />
|
||||
<el-table-column label="清洗规则" prop="rule" min-width="150" />
|
||||
<el-table-column label="规则描述" prop="description" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ scope.row.description || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="维度" prop="dimension" min-width="150" />
|
||||
<el-table-column label="状态" prop="status" min-width="100">
|
||||
<template #default="scope">
|
||||
<span class="status-online">{{ scope.row.status || '上线' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="关联时间" prop="关联时间" min-width="180" />
|
||||
<el-table-column label="操作" width="80">
|
||||
<!-- <template #default="scope">
|
||||
<a href="#" class="view-link">查看</a>
|
||||
</template> -->
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div style="margin-top: 15px; text-align: right;">
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getCleaningRulesList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 清洗规则选择弹窗 -->
|
||||
<CleaningRuleSelector
|
||||
v-model:visible="showRuleSelector"
|
||||
@confirm="handleRuleConfirm"
|
||||
@cancel="handleRuleCancel"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
// import { getBasinCodeDetail, getCleaningRulesPageList } from '@/api/basinCode'
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import CleaningRuleSelector from './components/CleaningRuleSelector.vue'
|
||||
|
||||
// 表单集合
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const title = ref('数据元详情')
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
const total = ref(0) // 列表的总页数
|
||||
|
||||
const form = ref<any>({
|
||||
basCode: 'BAS_CODE',
|
||||
categoryCode: '水土保持',
|
||||
type: '数据元',
|
||||
responsiblePerson: '管理员',
|
||||
contactPhone: '13800000001',
|
||||
fieldType: 'VARCHAR2',
|
||||
status: '启用',
|
||||
createTime: '2025-06-12',
|
||||
creator: 'admin',
|
||||
description: '主键,唯一标识流域'
|
||||
})
|
||||
|
||||
// 显示清洗规则选择弹窗
|
||||
const showRuleSelector = ref(false)
|
||||
|
||||
// 清洗规则列表
|
||||
const cleaningRulesList = ref([
|
||||
{
|
||||
name: '数值边界调整',
|
||||
rule: '数值边界调整',
|
||||
dimension: '异常值修正类',
|
||||
status: '上线',
|
||||
'关联时间': '2025-08-28 11:54:04'
|
||||
},
|
||||
{
|
||||
name: '字段前后缀统一',
|
||||
rule: '字段前后缀统一',
|
||||
dimension: '格式标准化类',
|
||||
status: '上线',
|
||||
'关联时间': '2025-08-28 13:47:29'
|
||||
},
|
||||
{
|
||||
name: '枚举值映射标准化',
|
||||
rule: '枚举值映射标准化',
|
||||
dimension: '字段映射替换类',
|
||||
status: '上线',
|
||||
'关联时间': '2025-08-28 16:27:23'
|
||||
},
|
||||
{
|
||||
name: '23',
|
||||
rule: '数值边界调整',
|
||||
dimension: '异常值修正类',
|
||||
status: '上线',
|
||||
'关联时间': '2025-09-24 14:38:23'
|
||||
}
|
||||
])
|
||||
|
||||
// 返回
|
||||
const goBack = () => {
|
||||
router.push({
|
||||
path: '/basinCodeManagement',
|
||||
query: {
|
||||
// 可以根据实际需求传递参数
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getDetail = async () => {
|
||||
const id: number = Number(route.query?.id) || 0
|
||||
let res = await getBasinCodeDetail(id)
|
||||
if (res) {
|
||||
form.value = res
|
||||
}
|
||||
await getCleaningRulesList()
|
||||
}
|
||||
|
||||
const getCleaningRulesList = async () => {
|
||||
let res: any = await getCleaningRulesPageList({
|
||||
basinCodeId: Number(route.query?.id) || 0,
|
||||
...queryParams
|
||||
})
|
||||
if (res) {
|
||||
cleaningRulesList.value = res.list
|
||||
total.value = res.total
|
||||
}
|
||||
}
|
||||
|
||||
// 处理规则选择确认
|
||||
const handleRuleConfirm = (selectedRules) => {
|
||||
// 为每个选中的规则添加维度和关联时间
|
||||
selectedRules.forEach(rule => {
|
||||
// 根据规则类别确定维度
|
||||
let dimension = ''
|
||||
if (rule.category.includes('accuracy')) {
|
||||
dimension = '异常值修正类'
|
||||
} else if (rule.category.includes('completeness')) {
|
||||
dimension = '完整性修复类'
|
||||
} else if (rule.category.includes('consistency')) {
|
||||
dimension = '字段映射替换类'
|
||||
} else if (rule.category.includes('uniqueness')) {
|
||||
dimension = '唯一性维护类'
|
||||
} else if (rule.category.includes('validity')) {
|
||||
dimension = '有效性处理类'
|
||||
} else if (rule.category.includes('timeliness')) {
|
||||
dimension = '及时性调整类'
|
||||
}
|
||||
|
||||
// 检查是否已存在该规则
|
||||
const exists = cleaningRulesList.value.some(item => item.name === rule.name)
|
||||
if (!exists) {
|
||||
cleaningRulesList.value.push({
|
||||
name: rule.name,
|
||||
rule: rule.rule,
|
||||
dimension: dimension,
|
||||
status: rule.status ? '上线' : '下线',
|
||||
'关联时间': new Date().toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).replace(/\//g, '-')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 更新总数
|
||||
total.value = cleaningRulesList.value.length
|
||||
}
|
||||
|
||||
// 处理规则选择取消
|
||||
const handleRuleCancel = () => {
|
||||
// 可以在这里添加取消逻辑
|
||||
console.log('取消选择规则')
|
||||
}
|
||||
|
||||
getDetail()
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.el-icon {
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.basin-info-container {
|
||||
margin-bottom: 30px;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.info-title {
|
||||
margin: 0 0 20px 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 15px 20px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
flex: 0 0 100px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
color: #409eff;
|
||||
padding: 2px 6px;
|
||||
background-color: #ecf5ff;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cleaning-rules-container {
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.rules-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.rules-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.add-rule-btn {
|
||||
background-color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
color: #67c23a;
|
||||
padding: 2px 6px;
|
||||
background-color: #f0f9eb;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.view-link {
|
||||
color: #409eff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.view-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
::v-deep .el-table {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
@ -51,108 +51,142 @@
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
<!-- 没有子菜单的情况 -->
|
||||
<el-menu-item v-else :index="menu.index" @click="handleMenuClick(menu)">
|
||||
<span><el-icon><list /></el-icon>{{ menu.title }}</span>
|
||||
</el-menu-item>
|
||||
<el-sub-menu v-else :index="menu.index" @click="handleMenuClick(menu)">
|
||||
<template #title>
|
||||
<span><el-icon><list /></el-icon>{{ menu.title }}</span>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<el-main class="main">
|
||||
<!-- 搜索区域 -->
|
||||
<!-- 搜索区域 - 调整为三输入框布局 -->
|
||||
<div class="search-container">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="5">
|
||||
<el-row :gutter="15">
|
||||
<el-col :span="6">
|
||||
<el-input
|
||||
v-model="searchForm.ruleType"
|
||||
placeholder="请输入规则类型"
|
||||
v-model="searchForm.chineseName"
|
||||
placeholder="请输入中文名称"
|
||||
prefix-icon="Search"
|
||||
class="search-input"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-input
|
||||
v-model="searchForm.englishName"
|
||||
placeholder="请输入英文名称"
|
||||
prefix-icon="Search"
|
||||
class="search-input"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-input
|
||||
v-model="searchForm.ruleName"
|
||||
placeholder="请输入规则名称"
|
||||
prefix-icon="Search"
|
||||
class="search-input"
|
||||
/>
|
||||
<el-select
|
||||
v-model="searchForm.type"
|
||||
placeholder="请选择类型"
|
||||
class="search-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-input
|
||||
v-model="searchForm.ruleCode"
|
||||
placeholder="请输入规则编码"
|
||||
prefix-icon="Search"
|
||||
class="search-input"
|
||||
/>
|
||||
<el-col :span="3">
|
||||
<el-button type="primary" @click="handleSearch" class="search-btn">查询</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
<el-col :span="3">
|
||||
<el-button @click="handleReset" class="reset-btn">重置</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-container">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增
|
||||
</el-button>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
height="500"
|
||||
show-header=true
|
||||
:header-cell-style="{ background: '#f2f3f5', color: '#6b7785', fontWeight: 'bold' }"
|
||||
border
|
||||
>
|
||||
<el-table-column
|
||||
prop="ruleCode"
|
||||
label="规则编码"
|
||||
type="index"
|
||||
label="编号"
|
||||
width="60"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="chineseName"
|
||||
label="中文名称"
|
||||
width="160"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="englishName"
|
||||
label="英文名称"
|
||||
width="160"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="type"
|
||||
label="类型"
|
||||
width="100"
|
||||
/>
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-tag type="success" size="small">{{ scope.row.type }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="ruleName"
|
||||
label="规则名称"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="qualityDimension"
|
||||
label="质量维度"
|
||||
prop="dataElement"
|
||||
label="数据元素类目"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="fieldType"
|
||||
label="字段类型"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="getDimensionTagType(scope.row.qualityDimension)"
|
||||
size="small"
|
||||
>
|
||||
{{ scope.row.qualityDimension }}
|
||||
</el-tag>
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
active-color="#13ce66"
|
||||
inactive-color="#ff4949"
|
||||
:disabled="true"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="ruleDescription"
|
||||
label="规则描述"
|
||||
min-width="200"
|
||||
prop="description"
|
||||
label="描述"
|
||||
min-width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="useScenario"
|
||||
label="使用场景"
|
||||
min-width="200"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="example"
|
||||
label="示例"
|
||||
min-width="200"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div v-html="scope.row.example"></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="120"
|
||||
width="180"
|
||||
align="center"
|
||||
>
|
||||
<template #default>
|
||||
<el-button size="small" type="link" class="operation-btn">编辑</el-button>
|
||||
<el-button size="small" type="link" class="operation-btn" style="color: #ff4d4f">删除</el-button>
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="text" class="operation-btn edit-btn" @click="handleEdit(scope.row)">
|
||||
<el-icon><Edit /></el-icon>修改
|
||||
</el-button>
|
||||
<el-button size="small" type="text" class="operation-btn delete-btn" @click="handleDelete(scope.row)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
<el-button size="small" type="text" class="operation-btn detail-btn" @click="handleDetail(scope.row)">
|
||||
<el-icon><View /></el-icon>详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -167,16 +201,142 @@
|
||||
</el-main>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 表单弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="700px"
|
||||
:before-close="handleDialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
class="data-form"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="中文名称" prop="chineseName">
|
||||
<el-input v-model="formData.chineseName" placeholder="请输入中文名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="英文名称" prop="englishName">
|
||||
<el-input v-model="formData.englishName" placeholder="请输入英文名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-radio-group v-model="formData.type">
|
||||
<el-radio label="数据元">数据元</el-radio>
|
||||
<el-radio label="代码集">代码集</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="数据元素类目" prop="dataElement">
|
||||
<el-select
|
||||
v-model="formData.dataElement"
|
||||
placeholder="请选择所属类目"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dataElementOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="字段类型" prop="fieldType">
|
||||
<el-select
|
||||
v-model="formData.fieldType"
|
||||
placeholder="请选择字段类型"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in fieldTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :label="true">启用</el-radio>
|
||||
<el-radio :label="false">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="负责人" prop="responsiblePerson">
|
||||
<el-select
|
||||
v-model="formData.responsiblePerson"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
v-for="person in responsiblePersons"
|
||||
:key="person.value"
|
||||
:label="person.label"
|
||||
:value="person.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input v-model="formData.contactPhone" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="元描述" prop="description">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
placeholder="请输入元描述"
|
||||
type="textarea"
|
||||
rows="4"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { Search, List } from "@element-plus/icons-vue";
|
||||
import { getAttCleanCatList } from '@/api/base/cleaningRules'
|
||||
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue';
|
||||
import { Search, List, Plus, Edit, Delete, View, Refresh } from "@element-plus/icons-vue";
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
// 注意:实际项目中请使用真实的API导入
|
||||
import { getAttCleanCatList, getAttCleanRuleList, saveAttCleanRule, updateAttCleanRule, deleteAttCleanRule } from '@/api/base/cleaningRules'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
// 获取组件实例,用于表单验证
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter() // 路由
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
serviceName: ''
|
||||
chineseName: '',
|
||||
englishName: '',
|
||||
type: ''
|
||||
})
|
||||
|
||||
// 当前激活的菜单
|
||||
@ -184,19 +344,62 @@ const activeMenu = ref('');
|
||||
|
||||
// 导航菜单数据
|
||||
const menuData = ref([]);
|
||||
// 加载状态
|
||||
// 菜单加载状态
|
||||
const menuLoading = ref(false);
|
||||
|
||||
// 类型选项数据
|
||||
const typeOptions = ref([
|
||||
{ label: '数据元', value: '数据元' },
|
||||
{ label: '代码集', value: '代码集' },
|
||||
]);
|
||||
|
||||
// 数据元素类目选项
|
||||
const dataElementOptions = ref([
|
||||
{ label: '用户信息', value: 'user_info' },
|
||||
{ label: '订单信息', value: 'order_info' },
|
||||
{ label: '产品信息', value: 'product_info' },
|
||||
{ label: '交易信息', value: 'transaction_info' },
|
||||
{ label: '系统配置', value: 'system_config' },
|
||||
]);
|
||||
|
||||
// 字段类型选项
|
||||
const fieldTypeOptions = ref([
|
||||
{ label: '字符串', value: 'string' },
|
||||
{ label: '整数', value: 'integer' },
|
||||
{ label: '浮点数', value: 'float' },
|
||||
{ label: '日期', value: 'date' },
|
||||
{ label: '布尔值', value: 'boolean' },
|
||||
{ label: '数组', value: 'array' },
|
||||
{ label: '对象', value: 'object' },
|
||||
]);
|
||||
|
||||
// 负责人选项
|
||||
const responsiblePersons = ref([
|
||||
{ label: '张三', value: 'zhangsan' },
|
||||
{ label: '李四', value: 'lisi' },
|
||||
{ label: '王五', value: 'wangwu' },
|
||||
{ label: '赵六', value: 'zhaoliu' },
|
||||
{ label: '钱七', value: 'qianqi' },
|
||||
]);
|
||||
|
||||
// 从接口获取菜单数据并转换为树形结构
|
||||
const loadMenuData = async () => {
|
||||
try {
|
||||
menuLoading.value = true;
|
||||
const response = await getAttCleanCatList();
|
||||
// 假设接口返回格式为 { data: [...] }
|
||||
const flatData = response || [];
|
||||
// 模拟接口返回
|
||||
const response = [
|
||||
{ id: 1, code: 'category1', name: '基础数据', parentId: 0, sortOrder: 1 },
|
||||
{ id: 2, code: 'category1-1', name: '用户数据', parentId: 1, sortOrder: 1 },
|
||||
{ id: 3, code: 'category1-2', name: '产品数据', parentId: 1, sortOrder: 2 },
|
||||
{ id: 4, code: 'category2', name: '业务数据', parentId: 0, sortOrder: 2 },
|
||||
{ id: 5, code: 'category2-1', name: '订单数据', parentId: 4, sortOrder: 1 },
|
||||
{ id: 6, code: 'category2-2', name: '交易数据', parentId: 4, sortOrder: 2 },
|
||||
{ id: 7, code: 'category2-2-1', name: '线上交易', parentId: 6, sortOrder: 1 },
|
||||
{ id: 8, code: 'category2-2-2', name: '线下交易', parentId: 6, sortOrder: 2 },
|
||||
];
|
||||
|
||||
// 将扁平数据转换为树形结构
|
||||
menuData.value = convertToTree(flatData);
|
||||
menuData.value = convertToTree(response);
|
||||
|
||||
// 设置默认激活的第一个菜单项
|
||||
if (menuData.value.length > 0) {
|
||||
@ -261,11 +464,6 @@ const convertToTree = (items) => {
|
||||
return tree;
|
||||
};
|
||||
|
||||
// 在组件挂载时加载菜单数据
|
||||
onMounted(() => {
|
||||
loadMenuData();
|
||||
});
|
||||
|
||||
// 导航搜索关键词
|
||||
const searchKeyword = ref('');
|
||||
|
||||
@ -298,80 +496,317 @@ const filteredMenus = computed(() => {
|
||||
|
||||
// 搜索表单数据
|
||||
const searchForm = ref({
|
||||
ruleType: '',
|
||||
ruleName: '',
|
||||
ruleCode: ''
|
||||
chineseName : '',
|
||||
englishName : '',
|
||||
type : ''
|
||||
});
|
||||
|
||||
// 表格数据
|
||||
// 表格内搜索
|
||||
const tableSearch = ref('');
|
||||
|
||||
// 表格数据 - 模拟数据
|
||||
const tableData = ref([
|
||||
{
|
||||
ruleCode: '311',
|
||||
ruleName: '多字段组合唯一性校验',
|
||||
qualityDimension: '一致性',
|
||||
ruleDescription: '检查字段组合在全表中是否唯一',
|
||||
useScenario: '检查字段组合在全表中是否唯一',
|
||||
example: '<ul><li>同一用户在某一天只能有一条记录</li></ul>'
|
||||
id: '1',
|
||||
chineseName: '用户ID',
|
||||
englishName: 'userId',
|
||||
type: '数据元',
|
||||
dataElement: 'user_info',
|
||||
fieldType: 'string',
|
||||
status: true,
|
||||
responsiblePerson: 'zhangsan',
|
||||
contactPhone: '13800138000',
|
||||
description: '用户唯一标识符,系统自动生成'
|
||||
},
|
||||
{
|
||||
ruleCode: '110',
|
||||
ruleName: '数值字段精度校验',
|
||||
qualityDimension: '完整性',
|
||||
ruleDescription: '检查小数位数是否符合业务定义(如保留2位)',
|
||||
useScenario: '检查小数位数是否符合业务定义(如保留2位)',
|
||||
example: '<ul><li>金额字段小数点后最多保留2位</li></ul>'
|
||||
id: '2',
|
||||
chineseName: '用户姓名',
|
||||
englishName: 'userName',
|
||||
type: '数据元',
|
||||
dataElement: 'user_info',
|
||||
fieldType: 'string',
|
||||
status: true,
|
||||
responsiblePerson: 'zhangsan',
|
||||
contactPhone: '13800138000',
|
||||
description: '用户的真实姓名'
|
||||
},
|
||||
{
|
||||
ruleCode: '107',
|
||||
ruleName: '字段字符类型校验',
|
||||
qualityDimension: '完整性',
|
||||
ruleDescription: '检查字段值是否为期望的字符组成(字母、数字等)',
|
||||
useScenario: '检查字段值是否为期望的字符组成(字母、数字等)',
|
||||
example: '<ul><li>手机号码段应为纯数字,出现字母则为异常</li></ul>'
|
||||
id: '3',
|
||||
chineseName: '用户状态',
|
||||
englishName: 'userStatus',
|
||||
type: '代码集',
|
||||
dataElement: 'user_info',
|
||||
fieldType: 'integer',
|
||||
status: true,
|
||||
responsiblePerson: 'lisi',
|
||||
contactPhone: '13900139000',
|
||||
description: '0-禁用,1-正常,2-锁定'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
chineseName: '订单编号',
|
||||
englishName: 'orderNo',
|
||||
type: '数据元',
|
||||
dataElement: 'order_info',
|
||||
fieldType: 'string',
|
||||
status: false,
|
||||
responsiblePerson: 'wangwu',
|
||||
contactPhone: '13700137000',
|
||||
description: '订单唯一标识符,系统自动生成'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
chineseName: '订单金额',
|
||||
englishName: 'orderAmount',
|
||||
type: '数据元',
|
||||
dataElement: 'order_info',
|
||||
fieldType: 'float',
|
||||
status: true,
|
||||
responsiblePerson: 'wangwu',
|
||||
contactPhone: '13700137000',
|
||||
description: '订单总金额,保留两位小数'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
chineseName: '订单状态',
|
||||
englishName: 'orderStatus',
|
||||
type: '代码集',
|
||||
dataElement: 'order_info',
|
||||
fieldType: 'integer',
|
||||
status: true,
|
||||
responsiblePerson: 'zhaoliu',
|
||||
contactPhone: '13600136000',
|
||||
description: '0-待支付,1-已支付,2-已发货,3-已完成,4-已取消'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
chineseName: '产品编码',
|
||||
englishName: 'productCode',
|
||||
type: '数据元',
|
||||
dataElement: 'product_info',
|
||||
fieldType: 'string',
|
||||
status: true,
|
||||
responsiblePerson: 'qianqi',
|
||||
contactPhone: '13500135000',
|
||||
description: '产品唯一编码'
|
||||
}
|
||||
]);
|
||||
|
||||
// 分页相关数据
|
||||
const total = ref(3);
|
||||
// 分页总数
|
||||
const total = ref(7);
|
||||
// 表格加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单弹窗相关
|
||||
const dialogVisible = ref(false);
|
||||
const dialogTitle = ref('新增数据元');
|
||||
const currentId = ref('');
|
||||
const formRef = ref(null);
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
chineseName: '',
|
||||
englishName: '',
|
||||
type: '数据元',
|
||||
dataElement: '',
|
||||
fieldType: '',
|
||||
status: false, // 默认禁用
|
||||
responsiblePerson: '',
|
||||
contactPhone: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = ref({
|
||||
chineseName: [
|
||||
{ required: true, message: '请输入中文名称', trigger: 'blur' }
|
||||
],
|
||||
englishName: [
|
||||
{ required: true, message: '请输入英文名称', trigger: 'blur' }
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: '请选择类型', trigger: 'change' }
|
||||
],
|
||||
dataElement: [
|
||||
{ required: true, message: '请选择数据元素类目', trigger: 'change' }
|
||||
],
|
||||
fieldType: [
|
||||
{ required: true, message: '请选择字段类型', trigger: 'change' }
|
||||
],
|
||||
contactPhone: [
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
// 获取列表数据
|
||||
const getList = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
// 模拟接口请求延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// 实际项目中这里会使用接口获取数据
|
||||
// const response = await getAttCleanRuleList(params);
|
||||
// tableData.value = response.data.list || [];
|
||||
// total.value = response.data.total || 0;
|
||||
} catch (error) {
|
||||
console.error('获取列表数据失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理菜单点击
|
||||
const handleMenuClick = (menu) => {
|
||||
activeMenu.value = menu.index;
|
||||
console.log('选中菜单:', menu);
|
||||
// 这里可以根据菜单项加载对应的表格数据
|
||||
// 重置页码,重新加载数据
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
console.log('搜索条件:', searchForm.value);
|
||||
// 重置页码,重新加载数据
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
// 表格内搜索
|
||||
const handleTableSearch = () => {
|
||||
// 可以根据需要实现表格内搜索逻辑
|
||||
console.log('表格搜索:', tableSearch.value);
|
||||
// 这里可以添加过滤表格数据的逻辑
|
||||
};
|
||||
|
||||
// 刷新表格
|
||||
const refreshTable = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
// 处理重置
|
||||
const handleReset = () => {
|
||||
searchForm.value = {
|
||||
ruleType: '',
|
||||
ruleName: '',
|
||||
ruleCode: ''
|
||||
chineseName: '',
|
||||
englishName: '',
|
||||
type: ''
|
||||
};
|
||||
// 重置页码,重新加载数据
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
// 根据质量维度获取标签类型
|
||||
const getDimensionTagType = (dimension) => {
|
||||
const tagTypes = {
|
||||
'准确性': 'primary',
|
||||
'一致性': 'success',
|
||||
'规范性': 'warning',
|
||||
'时效性': 'info',
|
||||
'完整性': 'info'
|
||||
};
|
||||
return tagTypes[dimension] || 'default';
|
||||
// 处理新增
|
||||
const handleAdd = () => {
|
||||
dialogTitle.value = '新增数据元';
|
||||
currentId.value = '';
|
||||
// 重置表单
|
||||
Object.keys(formData).forEach(key => {
|
||||
formData[key] = key === 'status' ? false : '';
|
||||
if (key === 'type') formData[key] = '数据元';
|
||||
});
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 分页事件处理
|
||||
const getList = () => {
|
||||
console.log('分页查询:', queryParams);
|
||||
// 处理编辑
|
||||
const handleEdit = (row) => {
|
||||
dialogTitle.value = '修改数据元';
|
||||
currentId.value = row.id;
|
||||
// 填充表单数据
|
||||
Object.keys(formData).forEach(key => {
|
||||
formData[key] = row[key] !== undefined ? row[key] : '';
|
||||
});
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 处理详情
|
||||
const handleDetail = (row) => {
|
||||
router.push({
|
||||
path: '/dataplan/standardDE/detail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// 处理删除
|
||||
// 处理删除
|
||||
const handleDelete = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要删除这条数据吗?',
|
||||
'确认删除',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
// 模拟删除操作
|
||||
tableData.value = tableData.value.filter(item => item.id !== row.id);
|
||||
total.value = tableData.value.length;
|
||||
|
||||
ElMessage.success('删除成功');
|
||||
// 重新加载数据
|
||||
getList();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除数据失败:', error);
|
||||
ElMessage.error('删除失败,请重试');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 保存数据(新增或编辑)
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
// 表单验证
|
||||
await formRef.value.validate();
|
||||
|
||||
if (currentId.value) {
|
||||
// 编辑操作
|
||||
const index = tableData.value.findIndex(item => item.id === currentId.value);
|
||||
if (index !== -1) {
|
||||
tableData.value[index] = { ...tableData.value[index], ...formData, id: currentId.value };
|
||||
}
|
||||
proxy.$message.success('更新成功');
|
||||
} else {
|
||||
// 新增操作
|
||||
const newId = Math.max(...tableData.value.map(item => parseInt(item.id)), 0) + 1;
|
||||
tableData.value.unshift({
|
||||
id: newId.toString(),
|
||||
...formData
|
||||
});
|
||||
total.value = tableData.value.length;
|
||||
proxy.$message.success('新增成功');
|
||||
}
|
||||
|
||||
dialogVisible.value = false;
|
||||
// 重新加载数据
|
||||
getList();
|
||||
} catch (error) {
|
||||
if (error.name !== 'Error') {
|
||||
// 表单验证失败不显示错误提示
|
||||
return;
|
||||
}
|
||||
console.error('保存数据失败:', error);
|
||||
proxy.$message.error('保存失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleDialogClose = () => {
|
||||
// 重置表单验证状态
|
||||
formRef.value.resetFields();
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
// 在组件挂载时加载菜单和表格数据
|
||||
onMounted(() => {
|
||||
loadMenuData().then(() => {
|
||||
getList();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -379,6 +814,7 @@ const getList = () => {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@ -386,7 +822,6 @@ const getList = () => {
|
||||
border-right: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 侧边栏头部搜索样式 */
|
||||
@ -395,19 +830,20 @@ const getList = () => {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.sidebar-search {
|
||||
width: 100%;
|
||||
--el-input-height: 32px;
|
||||
background-color: #f5f7fa;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.sidebar-search :deep(.el-input__wrapper) {
|
||||
background-color: #f7f8fa;
|
||||
.sidebar-search .el-input__wrapper {
|
||||
box-shadow: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sidebar-search :deep(.el-input__inner) {
|
||||
color: #1D2129;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 菜单样式调整 */
|
||||
@ -438,12 +874,12 @@ const getList = () => {
|
||||
/* 一级菜单 */
|
||||
.quality-menu :deep(.el-sub-menu:first-child .el-sub-menu__title) {
|
||||
font-weight: 500;
|
||||
padding-left: 16px !important;
|
||||
padding-left: 22px !important;
|
||||
}
|
||||
|
||||
/* 二级菜单 - 有子菜单的 */
|
||||
.quality-menu :deep(.el-sub-menu .el-sub-menu__title) {
|
||||
padding-left: 42px !important;
|
||||
padding-left: 22px !important;
|
||||
font-size: 14px;
|
||||
color: #4E5969;
|
||||
}
|
||||
@ -455,7 +891,7 @@ const getList = () => {
|
||||
|
||||
/* 三级菜单项 */
|
||||
.quality-menu :deep(.el-menu-item) {
|
||||
padding-left: 70px !important;
|
||||
padding-left: 47px !important;
|
||||
font-size: 14px;
|
||||
color: #4E5969;
|
||||
}
|
||||
@ -473,11 +909,12 @@ const getList = () => {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
.search-container {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
@ -485,14 +922,73 @@ const getList = () => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
.search-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 表格区域样式 */
|
||||
.table-container {
|
||||
border-radius: 4px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
:deep(.el-table) {
|
||||
border-top: none !important;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f2f3f5 !important;
|
||||
color: #6b7785 !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
:deep(.el-table tr:hover > td) {
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.operation-btn {
|
||||
padding: 0 5px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
color: #165DFF;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
color: #00b42a;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.data-form {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.data-form .el-form-item {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
:deep(.el-radio-group) {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
|
File diff suppressed because it is too large
Load Diff
276
src/views/dataService/datasetManage/dataAnnotationIsSortEdit.vue
Normal file
276
src/views/dataService/datasetManage/dataAnnotationIsSortEdit.vue
Normal file
@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<el-card style="min-height: 100px">
|
||||
<template #header>
|
||||
<div class="card-header" @click="goReturn">
|
||||
<el-icon>
|
||||
<ArrowLeft/>
|
||||
</el-icon>
|
||||
<span>返回</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="20" class="toolbar">
|
||||
<el-col :span="4">数据集名称:{{ form.datasetName }}</el-col>
|
||||
<el-col :span="4">数据集类型:
|
||||
{{
|
||||
getDictLabel(`llm_dataset_category_${form.datasetType}`, form.datasetCategory) || '无'
|
||||
}}
|
||||
</el-col>
|
||||
<el-col :span="2">标注进度</el-col>
|
||||
<el-col :span="4">
|
||||
<el-progress :percentage="form.annotateProgress"/>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<Pagination
|
||||
:total="total"
|
||||
size="small"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
layout="prev, pager, next"
|
||||
style="float: none"
|
||||
@pagination="getQuestionList"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-button type="primary" @click="save" :loading="loading">保存标注</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="questionList"
|
||||
style="width: 100%; margin-top: 10px; margin-bottom: 10px"
|
||||
:header-cell-style="{
|
||||
backgroundColor: '#F7F7F9',
|
||||
color: '#84868C',
|
||||
fontSize: '14px',
|
||||
textAlign: 'center'
|
||||
}"
|
||||
>
|
||||
<el-table-column align="center" label="序号" type="index" width="80"/>
|
||||
<el-table-column prop="question" label="问题" width="300" align="center"/>
|
||||
<el-table-column
|
||||
prop="datasetAnswerRespVO" label="回答" align="center"
|
||||
v-if="route.query.datasetType == '1'">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<div style="padding: 10px">
|
||||
<el-row
|
||||
v-for="(item, index) in scope.row.datasetAnswerRespVO"
|
||||
:key="index"
|
||||
style="margin-bottom: 10px"
|
||||
>
|
||||
<el-col :span="4">回答{{ index + 1 }}</el-col>
|
||||
<el-col :span="18">
|
||||
<el-input placeholder="请输入回答内容" v-model="item.answer" type="textarea"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
text
|
||||
bg
|
||||
size="small"
|
||||
@click="addAnswer(scope.row, scope.$index)"
|
||||
>
|
||||
<Icon icon="ep:plus"/>
|
||||
</el-button>
|
||||
<el-button
|
||||
text
|
||||
bg
|
||||
size="small"
|
||||
@click="deleteAnswer(scope.row, scope.$index)"
|
||||
>
|
||||
<Icon icon="ep:semi-select"/>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="answer" label="回答" align="center"
|
||||
v-if="route.query.datasetType == '2'">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.answer" placeholder="请输入"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ArrowLeft} from '@element-plus/icons-vue'
|
||||
import {getDeta, getQuestionPageList, updateQuestion} from '@/api/dataService/dataset'
|
||||
import {getDictLabel} from "@/utils/dict";
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const route = useRoute() // 查询参数
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const title = ref('返回')
|
||||
|
||||
const questionList = ref<any>([])
|
||||
const loading = ref(false) // Add loading state
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
const total = ref(0) // 列表的总页数
|
||||
|
||||
const form = ref<any>({
|
||||
datasetName: '',
|
||||
datasetCategory: ''
|
||||
})
|
||||
|
||||
// 增加排序
|
||||
const addAnswer = (row: any, index: number) => {
|
||||
// 如果 datasetAnswerRespVO 是 null,则初始化为空数组 []
|
||||
if (!questionList.value[index].datasetAnswerRespVO) {
|
||||
questionList.value[index].datasetAnswerRespVO = [];
|
||||
}
|
||||
questionList.value[index].datasetAnswerRespVO.push({
|
||||
answer: '',
|
||||
datasetId: row.datasetId,
|
||||
datasetFilesId: row.datasetFilesId,
|
||||
questionId: row.id
|
||||
})
|
||||
}
|
||||
|
||||
const deleteAnswer = (row: any, index: any)=> {
|
||||
if (row.datasetAnswerRespVO.length == 1) {
|
||||
ElMessage.error('至少保留一个选项')
|
||||
return
|
||||
}
|
||||
let returnData = [...row.datasetAnswerRespVO]
|
||||
returnData.pop()
|
||||
questionList.value[index].datasetAnswerRespVO = returnData
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
const id: number = route.query?.id
|
||||
let res = await getDeta(id)
|
||||
form.value = res
|
||||
title.value = res.datasetType == 1 ? '训练数据集详情' : '评估数据集详情'
|
||||
await getQuestionList()
|
||||
}
|
||||
|
||||
const getQuestionList = async () => {
|
||||
let res: any = await getQuestionPageList({
|
||||
datasetId: route.query?.id,
|
||||
...queryParams
|
||||
})
|
||||
questionList.value = res.list.map((e: any) => {
|
||||
return {
|
||||
...e,
|
||||
answer: e.datasetAnswerRespVO?.[0]?.answer ?? '' // 先检查 datasetAnswerRespVO 是否存在
|
||||
}
|
||||
})
|
||||
console.log(questionList.value, 'questionList.value')
|
||||
total.value = res.total
|
||||
}
|
||||
|
||||
// 返回
|
||||
const goReturn = () => {
|
||||
// 获取数据集类型,如果没有则默认为 1
|
||||
const datasetType = route.query?.datasetType || '1'
|
||||
// 获取数据集父类型,如果没有则默认为 1
|
||||
const datasetParentType = route.query?.datasetParentType || '1'
|
||||
router.push({
|
||||
|
||||
path: `/dataService/dataset/manage`,
|
||||
query: {
|
||||
activeTab: datasetType, // 传递应该激活的 tab
|
||||
parentType: datasetParentType // 传递数据集父类型
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (loading.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
let hasAnswer = false;
|
||||
for (const question of questionList.value) {
|
||||
if (route.query.datasetType == '1') {
|
||||
if (question.datasetAnswerRespVO && question.datasetAnswerRespVO.length > 0 && question.datasetAnswerRespVO.some(answer => answer.answer)) {
|
||||
hasAnswer = true;
|
||||
break;
|
||||
}
|
||||
} else if (route.query.datasetType == '2') {
|
||||
if (question.answer) {
|
||||
hasAnswer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAnswer) {
|
||||
message.error('至少有一个问题必须有回答!');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const question of questionList.value) {
|
||||
if (route.query.datasetType == '1') {
|
||||
if (question.datasetAnswerRespVO && question.datasetAnswerRespVO.length > 0 && question.datasetAnswerRespVO.some(answer => !answer.answer)) {
|
||||
message.error('请全部回答完毕!');
|
||||
return;
|
||||
}
|
||||
} else if (route.query.datasetType == '2') {
|
||||
if (!question.answer) {
|
||||
message.error('请全部回答完毕!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const data = [...questionList.value]
|
||||
if (route.query.datasetType == '2') {
|
||||
data.forEach((e: any) => {
|
||||
if (e.datasetAnswerRespVO[0] && e.datasetAnswerRespVO[0].id) {
|
||||
e.datasetAnswerRespVO[0].answer = e.answer
|
||||
} else {
|
||||
e.datasetAnswerRespVO = [
|
||||
{
|
||||
answer: e.answer,
|
||||
datasetId: e.datasetId,
|
||||
datasetFilesId: e.datasetFilesId,
|
||||
questionId: e.id
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
await updateQuestion(route.query.datasetType == '1' ? questionList.value : data)
|
||||
await getList()
|
||||
message.success('标注保存成功!')
|
||||
} catch (e) {
|
||||
message.error('标注保存失败!')
|
||||
}finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
getList()
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.divLine {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
|
||||
.el-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
581
src/views/dataService/datasetManage/datasetImportForm.vue
Normal file
581
src/views/dataService/datasetManage/datasetImportForm.vue
Normal file
@ -0,0 +1,581 @@
|
||||
<template>
|
||||
<div class="data-import-container">
|
||||
<!-- 标签页 -->
|
||||
<el-tabs v-model="activeTab" class="import-tabs">
|
||||
<!-- 上传文件标签页 -->
|
||||
<el-tab-pane label="上传文件" name="file">
|
||||
<div class="upload-content">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:accept="accept"
|
||||
v-model:file-list="fileList"
|
||||
:action="importUrl + '?updateSupport=' + updateSupport"
|
||||
:auto-upload="false"
|
||||
:disabled="formLoading"
|
||||
:headers="uploadHeaders"
|
||||
:limit="1"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
:on-remove="handleRemove"
|
||||
drag
|
||||
>
|
||||
<Icon icon="ep:upload" />
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center" style="width: 100%; max-width: 510px; margin: 0 auto;">
|
||||
<span>{{ accept.includes('.zip') && accept === '.zip' ? '仅支持zip格式压缩包文件上传' : '支持json、csv、xlsx、txt格式文件及包含上述文件类型的tar.gz/zip压缩包文件上传' }}</span>
|
||||
<template v-if="accept !== '.zip'">
|
||||
<el-link
|
||||
:underline="false"
|
||||
style="font-size: 12px; vertical-align: baseline;margin-right: 10px"
|
||||
type="primary"
|
||||
@click="importTemplate(4)"
|
||||
>
|
||||
下载json示例数据集
|
||||
</el-link>
|
||||
|
||||
<el-link
|
||||
:underline="false"
|
||||
style="font-size: 12px; vertical-align: baseline;margin-right: 10px"
|
||||
type="primary"
|
||||
@click="importTemplate(2)"
|
||||
>
|
||||
下载xlsx示例数据集
|
||||
</el-link>
|
||||
|
||||
<el-link
|
||||
:underline="false"
|
||||
style="font-size: 12px; vertical-align: baseline;margin-right: 10px"
|
||||
type="primary"
|
||||
@click="importTemplate(3)"
|
||||
>
|
||||
下载csv示例数据集
|
||||
</el-link>
|
||||
|
||||
<el-link
|
||||
:underline="false"
|
||||
style="font-size: 12px; vertical-align: baseline"
|
||||
type="primary"
|
||||
@click="importTemplate(1)"
|
||||
>
|
||||
下载txt示例数据集
|
||||
</el-link>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-link
|
||||
:underline="false"
|
||||
style="font-size: 12px; vertical-align: baseline;margin-right: 10px"
|
||||
type="primary"
|
||||
@click="importTemplate(5)"
|
||||
>
|
||||
下载zip示例数据集
|
||||
</el-link>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 文件夹标签页 -->
|
||||
<el-tab-pane label="文件夹" name="folder">
|
||||
<div class="upload-content folder-content">
|
||||
<div class="folder-upload-area">
|
||||
<Icon icon="ep:folder" class="folder-icon" />
|
||||
<p>拖拽文件夹到此处,或点击选择文件夹按钮</p>
|
||||
<el-button type="primary" @click="selectFolder">选择文件夹</el-button>
|
||||
<input
|
||||
ref="folderInputRef"
|
||||
type="file"
|
||||
webkitdirectory
|
||||
directory
|
||||
style="display: none"
|
||||
@change="handleFolderSelect"
|
||||
/>
|
||||
<p class="support-tip">支持上传整个文件夹及其子文件</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- URL爬取数据标签页 -->
|
||||
<el-tab-pane label="URL爬取数据" name="url">
|
||||
<div class="upload-content form-content">
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="URL地址" required>
|
||||
<el-input v-model="urlForm.url" placeholder="请输入合法地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Prompt">
|
||||
<div class="prompt-input-wrapper">
|
||||
<el-input v-model="urlForm.prompt" placeholder="请输入Prompt内容" />
|
||||
<el-button type="primary" @click="addPrompt">添加Prompt</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<p class="hint-text">提示:请输入想要爬取的内容名称</p>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- API获取标签页 -->
|
||||
<el-tab-pane label="API获取" name="api">
|
||||
<div class="upload-content form-content">
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="API地址" required>
|
||||
<el-input v-model="apiForm.url" placeholder="请输入合法地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求方式">
|
||||
<el-select v-model="apiForm.method">
|
||||
<el-option label="GET" value="GET" />
|
||||
<el-option label="POST" value="POST" />
|
||||
<el-option label="PUT" value="PUT" />
|
||||
<el-option label="DELETE" value="DELETE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as dataSetApi from '@/api/dataControl'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
import download from '@/utils/download'
|
||||
import { ref, nextTick } from 'vue'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
|
||||
defineOptions({ name: 'SystemUserImportForm' })
|
||||
|
||||
const props = defineProps({
|
||||
accept: {
|
||||
type: String,
|
||||
default: '.json,.csv,.xlsx,.txt,.zip,.gz'
|
||||
},
|
||||
query: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const uploadRef = ref()
|
||||
const importUrl =
|
||||
import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/llmUpload'
|
||||
const uploadHeaders = ref() // 上传 Header 头
|
||||
const fileList = ref<any[]>([]) // 文件列表
|
||||
const updateSupport = ref(0) // 是否更新已经存在的用户数据
|
||||
const activeTab = ref('file') // 默认选中上传文件标签页
|
||||
|
||||
// 文件夹上传相关
|
||||
const folderInputRef = ref()
|
||||
const selectedFolder = ref<FileList | null>(null)
|
||||
|
||||
// URL爬取相关
|
||||
const urlForm = ref({
|
||||
url: '',
|
||||
prompt: ''
|
||||
})
|
||||
|
||||
// API获取相关
|
||||
const apiForm = ref({
|
||||
url: '',
|
||||
method: 'GET'
|
||||
})
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = async (): Promise<void> => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
fileList.value = []
|
||||
selectedFolder.value = null
|
||||
urlForm.value = {
|
||||
url: '',
|
||||
prompt: ''
|
||||
}
|
||||
apiForm.value = {
|
||||
url: '',
|
||||
method: 'GET'
|
||||
}
|
||||
await nextTick()
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
updateSupport.value = 0
|
||||
fileList.value = []
|
||||
resetForm()
|
||||
// 可以在这里使用 props.query 中的参数进行初始化
|
||||
console.log('导入参数:', props.query)
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
try {
|
||||
formLoading.value = true
|
||||
|
||||
switch (activeTab.value) {
|
||||
case 'file':
|
||||
await submitFileUpload()
|
||||
break
|
||||
case 'folder':
|
||||
await submitFolderUpload()
|
||||
break
|
||||
case 'url':
|
||||
await submitUrlCrawl()
|
||||
break
|
||||
case 'api':
|
||||
await submitApiFetch()
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('操作失败,请重试')
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交文件上传 */
|
||||
const submitFileUpload = async () => {
|
||||
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const fileItem = fileList.value[0];
|
||||
console.log('========>', fileItem)
|
||||
if (!fileItem || !fileItem.raw) {
|
||||
message.error('文件上传异常,请重新选择文件');
|
||||
formLoading.value = false
|
||||
return;
|
||||
}
|
||||
const file = fileItem.raw;
|
||||
const maxSize = 256 * 1024 * 1024 // 256MB
|
||||
|
||||
if (file.size > maxSize) {
|
||||
message.error('文件大小不能超过256MB')
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 提交请求
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
}
|
||||
uploadRef.value!.submit()
|
||||
}
|
||||
|
||||
/** 选择文件夹 */
|
||||
const selectFolder = () => {
|
||||
folderInputRef.value?.click()
|
||||
}
|
||||
|
||||
/** 处理文件夹选择 */
|
||||
const handleFolderSelect = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
if (target.files && target.files.length > 0) {
|
||||
selectedFolder.value = target.files
|
||||
message.success(`已选择文件夹,包含 ${target.files.length} 个文件`)
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交文件夹上传 */
|
||||
const submitFolderUpload = async () => {
|
||||
if (!selectedFolder.value) {
|
||||
message.error('请选择文件夹')
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 模拟文件夹上传处理
|
||||
message.success('文件夹上传成功')
|
||||
emits('success', { folder: selectedFolder.value })
|
||||
formLoading.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
/** 提交URL爬取 */
|
||||
const submitUrlCrawl = async () => {
|
||||
if (!urlForm.value.url) {
|
||||
message.error('请输入URL地址')
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 模拟URL爬取处理
|
||||
message.success('URL爬取数据成功')
|
||||
emits('success', { url: urlForm.value })
|
||||
formLoading.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
/** 添加Prompt */
|
||||
const addPrompt = () => {
|
||||
if (!urlForm.value.prompt) {
|
||||
message.warning('请输入Prompt内容')
|
||||
return
|
||||
}
|
||||
message.success('Prompt添加成功')
|
||||
}
|
||||
|
||||
/** 提交API获取 */
|
||||
const submitApiFetch = async () => {
|
||||
if (!apiForm.value.url) {
|
||||
message.error('请输入API地址')
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 模拟API数据获取处理
|
||||
message.success('API数据获取成功')
|
||||
emits('success', { api: apiForm.value })
|
||||
formLoading.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
defineExpose({ open, submitForm, fileList, resetForm }) // 提供 open 方法,用于打开弹窗,同时暴露resetForm方法
|
||||
|
||||
/** 文件上传成功 */
|
||||
const emits = defineEmits(['success', 'removeFile'])
|
||||
const submitFormSuccess = (response: any) => {
|
||||
if (response.code !== 0) {
|
||||
message.error(response.msg)
|
||||
formLoading.value = false
|
||||
return
|
||||
}
|
||||
// 拼接提示语
|
||||
const data = response.data
|
||||
console.log(data,'data')
|
||||
formLoading.value = false
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emits('success', data)
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
|
||||
const handleRemove = () =>{
|
||||
resetForm()
|
||||
emits('removeFile')
|
||||
}
|
||||
/** 下载模板操作 */
|
||||
// const base64DecodeUnicode = (str) => {
|
||||
// // 先对 Base64 进行解码
|
||||
// const binaryStr = atob(str);
|
||||
// const len = binaryStr.length;
|
||||
// const bytes = new Uint8Array(len);
|
||||
// for (let i = 0; i < len; i++) {
|
||||
// bytes[i] = binaryStr.charCodeAt(i);
|
||||
// }
|
||||
// // 使用 TextDecoder 进行 UTF-8 解码
|
||||
// const decoder = new TextDecoder('utf-8');
|
||||
// return decoder.decode(bytes);
|
||||
// }
|
||||
const importTemplate = async (type) => {
|
||||
const data = await dataSetApi.importUserTemplates({type:type})
|
||||
if(type === 1){
|
||||
download.txt(data, '示例数据集.txt')
|
||||
}else if(type === 2){
|
||||
download.excel(data, '示例数据集.xlsx')
|
||||
}else if(type === 3){
|
||||
download.csv(data, '示例数据集.csv')
|
||||
}else if(type === 4){
|
||||
download.json(data, '示例数据集.json')
|
||||
}else if(type === 5){
|
||||
download.zip(data, '示例数据集.zip')
|
||||
}
|
||||
}
|
||||
|
||||
open()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 容器样式 */
|
||||
.data-import-container {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 标签页样式 */
|
||||
.import-tabs {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 统一所有上传内容区域的容器样式 */
|
||||
.upload-content {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 核心样式 - 统一所有内容区域的虚线框样式 */
|
||||
.upload-content :deep(.el-upload-dragger),
|
||||
.folder-upload-area,
|
||||
.form-content :deep(.el-form) {
|
||||
/* 基础样式 */
|
||||
width: 100%;
|
||||
padding: 50px 20px;
|
||||
box-sizing: border-box;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background-color: #ffffff;
|
||||
transition: border-color 0.3s;
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
/* 鼠标悬停效果 - 统一虚线框颜色变化 */
|
||||
.upload-content :deep(.el-upload-dragger:hover),
|
||||
.folder-upload-area:hover,
|
||||
.form-content :deep(.el-form:hover) {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
/* 文件上传区域特殊样式 */
|
||||
.upload-content :deep(.el-upload-dragger) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 文件上传提示文字样式 */
|
||||
.upload-content :deep(.el-upload__text) {
|
||||
color: #606266;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.upload-content :deep(.el-upload__text em) {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
/* 文件上传提示信息容器 */
|
||||
.upload-content :deep(.el-upload__tip) {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
/* 文件上传和文件夹上传区域容器样式 */
|
||||
.upload-content,
|
||||
.folder-content {
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 文件夹上传区域样式 */
|
||||
.folder-upload-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 文件夹图标样式 */
|
||||
.folder-icon {
|
||||
font-size: 60px;
|
||||
color: #409eff;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 文件夹文字样式 */
|
||||
.folder-upload-area p {
|
||||
margin: 8px 0;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 文件夹选择按钮样式 */
|
||||
.folder-upload-area .el-button {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* 表单内容区域样式 */
|
||||
.form-content {
|
||||
padding: 0;
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 表单容器样式 */
|
||||
.form-content :deep(.el-form) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 表单项样式 - 统一宽度和间距 */
|
||||
.form-content :deep(.el-form-item) {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 表单标签样式 */
|
||||
.form-content :deep(.el-form-item__label) {
|
||||
width: 80px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/* 表单内容区域样式 */
|
||||
.form-content :deep(.el-form-item__content) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 输入框和选择器统一样式 */
|
||||
.form-content :deep(.el-input),
|
||||
.form-content :deep(.el-select) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Prompt输入框和按钮组合样式 */
|
||||
.form-content :deep(.el-form-item) .prompt-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-content :deep(.el-form-item) .prompt-input-wrapper .el-input {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* 提示文本样式 - 统一所有标签页的提示文字 */
|
||||
.support-tip,
|
||||
.hint-text {
|
||||
margin-top: 15px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 必填项标记样式 */
|
||||
:deep(.el-form-item__label.is-required:not(.is-no-asterisk):before) {
|
||||
content: '*';
|
||||
color: #f56c6c;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* 确保所有标签页内容居中且宽度一致 */
|
||||
.el-tab-pane {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
213
src/views/dataService/datasetManage/detail.vue
Normal file
213
src/views/dataService/datasetManage/detail.vue
Normal file
@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<el-card shadow="never" class="b-r-top-15" style="margin-bottom: 10px">
|
||||
<div class="card-header">
|
||||
<el-icon @click="goBack"><ArrowLeft /></el-icon>
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="b-r-bottom-15">
|
||||
<div style="height: 70vh">
|
||||
<div style="margin: 20px">
|
||||
<el-row>
|
||||
<el-col :span="6"> 数据集名称:{{ form.datasetName }}</el-col>
|
||||
<el-col :span="6"> 数据集类型:{{ getDictLabel(`llm_dataset_category_${form.datasetType}`, form.datasetCategory) || '无' }}</el-col>
|
||||
<el-col :span="6"> 清洗规则:无</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<!-- 当datasetParentType为2时显示新的表格结构 -->
|
||||
<el-table v-if="datasetParentType === '2'" :data="questionList" show-header=true :header-cell-style="{ background: '#f2f3f5', color: '#6b7785', fontWeight: 'bold' }">
|
||||
<el-table-column label="序号">
|
||||
<template #default="scope">
|
||||
{{ scope.$index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="图片">
|
||||
<template #default="scope">
|
||||
<!-- 显示imagesList中的第一张图片 -->
|
||||
<img
|
||||
v-if="Array.isArray(scope.row.imagesList) && scope.row.imagesList[0]"
|
||||
:src="scope.row.imagesList[0]"
|
||||
alt="图片"
|
||||
style="max-width: 100px; max-height: 100px; border-radius: 4px;"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="问题">
|
||||
<template #default="scope">
|
||||
<el-popover
|
||||
:content="scope.row.question || ''"
|
||||
placement="top-start"
|
||||
width="300"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
{{ (scope.row.question?.length || 0) > 49 ? (scope.row.question || '').substring(0,49)+'...' : (scope.row.question || '-') }}
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回答">
|
||||
<template #default="scope">
|
||||
<!-- 遍历datasetAnswerRespVO数组显示所有回答 -->
|
||||
<div
|
||||
:class="`${index === (scope.row.datasetAnswerRespVO?.length || 1) - 1 ? 'el-table-column-children' : 'el-table-column-children el-table-column-children-border'}`"
|
||||
v-for="(item, index) in (Array.isArray(scope.row.datasetAnswerRespVO) ? scope.row.datasetAnswerRespVO : [])"
|
||||
:key="index">
|
||||
<el-popover
|
||||
:content="item.answer || ''"
|
||||
placement="top-start"
|
||||
:width="300"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
{{ (item.answer?.length || 0) > 18 ? (item.answer || '').substring(0, 18) + '...' : (item.answer || '-') }}
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 默认显示原有的表格结构 -->
|
||||
<el-table v-else :data="questionList" show-header=true :header-cell-style="{ background: '#f2f3f5', color: '#6b7785', fontWeight: 'bold' }">
|
||||
<el-table-column label="序号" prop="id">
|
||||
<template #default="scope">
|
||||
{{ scope.$index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="system" label="System"/>
|
||||
<el-table-column prop="question" label="Prompt">
|
||||
<template #default="scope">
|
||||
<el-popover
|
||||
:content="scope.row.question"
|
||||
placement="top-start"
|
||||
width="300"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
{{ scope.row.question.length>49 ? scope.row.question.substring(0,49)+'...' : scope.row.question }}
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="response" label="Response">
|
||||
<template #default="scope">
|
||||
<div
|
||||
:class="`${index === scope.row.datasetAnswerRespVO.length - 1 ? 'el-table-column-children' : 'el-table-column-children el-table-column-children-border'}`"
|
||||
v-for="(item, index) in scope.row.datasetAnswerRespVO"
|
||||
:key="index">
|
||||
<el-popover
|
||||
:content="item.answer"
|
||||
placement="top-start"
|
||||
:width="300"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
{{ item.answer.length>18 ? item.answer.substring(0,18)+'...' : item.answer }}
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top: 15px">
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getQuestionList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { getDeta, getQuestionPageList } from '@/api/dataService/dataset'
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import { getDictLabel } from '@/utils/dict'
|
||||
// 表单集合
|
||||
const router = useRouter() // 路由
|
||||
const route = useRoute() // 查询参数
|
||||
const title = ref('训练数据集详情')
|
||||
|
||||
// 获取datasetParentType参数
|
||||
const datasetParentType = computed(() => {
|
||||
return route.query?.datasetParentType || '1'; // 默认值为1
|
||||
})
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
const total = ref(0) // 列表的总页数
|
||||
|
||||
const form = ref<any>({
|
||||
datasetName: '',
|
||||
datasetCategory: ''
|
||||
})
|
||||
|
||||
const questionList = ref([])
|
||||
|
||||
// 返回
|
||||
const goBack = () => {
|
||||
// 获取数据集类型和父类型,如果没有则默认为 1
|
||||
const datasetType = route.query?.datasetType || '1'
|
||||
const datasetParentType = route.query?.datasetParentType || '1'
|
||||
|
||||
router.push({
|
||||
path: `/dataService/dataset/manage`,
|
||||
query: {
|
||||
activeTab: datasetType, // 传递应该激活的 tab
|
||||
parentType: datasetParentType // 传递数据集父类型(文本/多模态)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
const id: number = Number(route.query?.id) || 0
|
||||
let res = await getDeta(id)
|
||||
form.value = res
|
||||
title.value = res.datasetType == 1 ? '训练数据集详情' : '评估数据集详情'
|
||||
await getQuestionList()
|
||||
}
|
||||
|
||||
const getQuestionList = async () => {
|
||||
let res: any = await getQuestionPageList({
|
||||
datasetId: Number(route.query?.id) || 0,
|
||||
datasetParentType: datasetParentType.value,
|
||||
...queryParams
|
||||
})
|
||||
questionList.value = res.list
|
||||
total.value = res.total
|
||||
}
|
||||
|
||||
getList()
|
||||
onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.divLine {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
|
||||
.el-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table-column-children {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.el-table-column-children-border {
|
||||
border-bottom: var(--el-table-border);
|
||||
}
|
||||
</style>
|
408
src/views/dataService/datasetManage/form.vue
Normal file
408
src/views/dataService/datasetManage/form.vue
Normal file
@ -0,0 +1,408 @@
|
||||
<template>
|
||||
<el-card shadow="never" style="margin-bottom: 10px">
|
||||
<el-form
|
||||
v-loading="formLoading"
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="-mb-15px"
|
||||
label-width="auto"
|
||||
:rules="formRules"
|
||||
style="max-width: 1000px"
|
||||
>
|
||||
<el-form-item label="数据集名称" prop="datasetName" required>
|
||||
<el-input
|
||||
v-model="form.datasetName"
|
||||
maxlength="20"
|
||||
show-word-limit
|
||||
class="!w-560px"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 数据集类型 -->
|
||||
<div v-if="!route?.query.id">
|
||||
<el-form-item label="数据集类型" prop="datasetCategory">
|
||||
<el-select v-model="form.datasetCategory" placeholder="请选择" style="width: 180px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions((form.datasetParentType === '2' ? 'vl_llm_dataset_category_' : 'llm_dataset_category_') + datasetType)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 标注状态 -->
|
||||
<div>
|
||||
<el-form-item label="标注状态" prop="markStatus" required v-if="datasetType!='2' && form.datasetParentType!='2'">
|
||||
<el-select v-model="form.markStatus" placeholder="请选择" style="width: 180px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions('llm_dataset_mark_status')"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 清洗规则 -->
|
||||
<div>
|
||||
<el-form-item label="清洗规则" prop="cleanRule" required v-if="datasetType!='2' && form.datasetParentType!='2'">
|
||||
<el-select v-model="form.cleanRule" placeholder="请选择" style="width: 180px">
|
||||
<!-- 模拟两条清洗规则 -->
|
||||
<el-option
|
||||
v-for="rule in [
|
||||
{value: '1', label: '基础数据清洗(去除空值、重复数据)'},
|
||||
{value: '2', label: '高级数据标准化(格式化日期、统一单位)'}
|
||||
]"
|
||||
:key="rule.value"
|
||||
:label="rule.label"
|
||||
:value="rule.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="数据集描述" prop="datasetIntro" style="width: 100%; max-width: 600px;">
|
||||
<el-input v-model="form.datasetIntro" placeholder="请输入" type="textarea" :rows="5" maxlength="200" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label="" required>
|
||||
<div class="upload-container">
|
||||
<DatasetImportForm @success="importSuccess" @remove-file="handleRemoveFile" ref="importFormRef" :accept="form.datasetParentType === '2' ? '.zip' : ''" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="b-r-bottom-15" style="text-align: right; border: none;">
|
||||
<el-button @click="handleCancel" class="no-border-btn">取消</el-button>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm" class="no-border-btn"> 确认</el-button>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {getIntDictOptions} from "@/utils/dict";
|
||||
import {FormRules} from "element-plus";
|
||||
import * as dataSetApi from '@/api/dataService/dataset'
|
||||
import DatasetImportForm from "@/views/dataService/datasetManage/datasetImportForm.vue";
|
||||
import { watch, ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
const props = defineProps({
|
||||
query: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['create-success', 'close'])
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
// 表单集合
|
||||
const form = ref<any>({
|
||||
datasetName: '', //数据集名称
|
||||
datasetCategory: '', //数据集类型
|
||||
markStatus: '', //状态
|
||||
cleanRule: '', //清洗规则
|
||||
datasetIntro: '', //数据集描述
|
||||
datasetType: '', //数据集类型
|
||||
datasetParentType: '', //数据集父类型
|
||||
type: '', // 0 普通 1 官方
|
||||
filesList: []
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单引用
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
|
||||
const importFormRef = ref() // 导入表单引用
|
||||
const isPopupMode = ref(false) // 是否为弹窗模式
|
||||
|
||||
const formRules = reactive<FormRules>({
|
||||
datasetName: [{required: true, message: '数据集名称不能为空', trigger: 'blur'}],
|
||||
markStatus: [{required: true, message: '状态不能为空', trigger: 'blur'}],
|
||||
cleanRule: [{required: true, message: '请选择清洗规则', trigger: 'change'}],
|
||||
datasetCategory: [{required: true, message: '请选择数据集类型', trigger: 'change'}],
|
||||
})
|
||||
|
||||
// 重复的变量声明已移除
|
||||
const datasetType = ref('')
|
||||
|
||||
|
||||
// 获取详情
|
||||
const getDetails = async (id) => {
|
||||
let da = await dataSetApi.getDeta(id)
|
||||
|
||||
importFormRef.value.fileList = []
|
||||
// 确保 datasetFiles 存在且是数组,避免调用不存在的 forEach 方法
|
||||
const datasetFiles = da.datasetFiles;
|
||||
if (datasetFiles && Array.isArray(datasetFiles)) {
|
||||
for (const x of datasetFiles) {
|
||||
importFormRef.value.fileList.push({
|
||||
url: x.datasetFileUrl,
|
||||
name: x.datasetFileName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(importFormRef.value.fileList)
|
||||
|
||||
form.value = da
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
await formRef.value.validate()
|
||||
|
||||
console.log('11111',importFormRef.value.fileList.length)
|
||||
console.log(form.value)
|
||||
if (importFormRef.value.fileList.length == 0 || (Array.isArray(form.value.filesList) && form.value.filesList.length > 0)) {
|
||||
console.log(111)
|
||||
await importSuccess()
|
||||
} else {
|
||||
console.log(2222)
|
||||
await importFormRef.value.submitForm()
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
const handleRemoveFile = () => {
|
||||
importFormRef.value.fileList = []
|
||||
form.value.filesList = []
|
||||
}
|
||||
|
||||
const importSuccess = async (data?: any) => {
|
||||
if (data) {
|
||||
form.value.filesList = [
|
||||
{
|
||||
id: data.id,
|
||||
url: data.url,
|
||||
filename: data.fileName
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 校验表单
|
||||
if (!formRef.value) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
form.value.datasetType = datasetType.value || form.value.datasetType
|
||||
form.value.datasetParentType = route.query.datasetParentType as string || form.value.datasetParentType
|
||||
form.value.type = route.query.type || form.value.type
|
||||
try {
|
||||
const data = form.value as unknown as dataSetApi.DataSetVO
|
||||
let result
|
||||
if (!route?.query.id && !isPopupMode.value) {
|
||||
// 根据datasetParentType选择不同的创建接口
|
||||
if (form.value.datasetParentType === '2') {
|
||||
result = await dataSetApi.createDataSetV2(data)
|
||||
} else {
|
||||
result = await dataSetApi.createDataSet(data)
|
||||
}
|
||||
// message.success(t('common.createSuccess'))
|
||||
} else if (isPopupMode.value && !props.query.id) {
|
||||
// 弹窗模式下的创建
|
||||
if (form.value.datasetParentType === '2') {
|
||||
result = await dataSetApi.createDataSetV2(data)
|
||||
} else {
|
||||
result = await dataSetApi.createDataSet(data)
|
||||
}
|
||||
// message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
// 更新
|
||||
if (form.value.datasetParentType === '2') {
|
||||
result = await dataSetApi.updateDataSetV2(data)
|
||||
} else {
|
||||
result = await dataSetApi.updateDataSet(data)
|
||||
}
|
||||
// message.success(t('common.updateSuccess'))
|
||||
}
|
||||
|
||||
if (isPopupMode.value) {
|
||||
// 在弹窗模式下,触发成功事件
|
||||
emits('create-success', result)
|
||||
} else {
|
||||
router.back()
|
||||
}
|
||||
} catch (error) {
|
||||
// 创建/更新失败时,清空上传组件的文件并重置状态
|
||||
if (importFormRef.value) {
|
||||
// 清空文件列表
|
||||
importFormRef.value.fileList = []
|
||||
// 重置上传组件状态
|
||||
await importFormRef.value.resetForm()
|
||||
}
|
||||
// 清空表单中的文件数据
|
||||
form.value.filesList = []
|
||||
// 重新抛出错误,让上层处理错误提示
|
||||
throw error
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理取消按钮点击
|
||||
const handleCancel = () => {
|
||||
console.log('取消按钮点击,当前是否为弹窗模式:', isPopupMode.value);
|
||||
if (isPopupMode.value) {
|
||||
// 在弹窗模式下,触发关闭事件
|
||||
console.log('触发close事件');
|
||||
emits('close');
|
||||
} else {
|
||||
// 非弹窗模式下,返回上一页
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单 - 确保完全清空所有用户输入字段,移除标注状态的默认值
|
||||
const resetForm = () => {
|
||||
// 先保存需要保留的类型信息
|
||||
const currentDatasetType = datasetType.value;
|
||||
const currentParentType = form.value.datasetParentType;
|
||||
const currentType = form.value.type;
|
||||
|
||||
// 强制完全重置表单数据,只保留类型信息
|
||||
// 移除标注状态的默认值,设置为空字符串
|
||||
form.value = {
|
||||
datasetName: '',
|
||||
datasetCategory: '',
|
||||
markStatus: '',
|
||||
cleanRule: '',
|
||||
datasetIntro: '',
|
||||
datasetType: currentDatasetType,
|
||||
datasetParentType: currentParentType,
|
||||
type: currentType,
|
||||
filesList: []
|
||||
}
|
||||
|
||||
// 强制重置表单验证状态,确保清除所有验证提示
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
|
||||
// 清空上传组件的文件列表,确保没有残留文件
|
||||
if (importFormRef.value) {
|
||||
importFormRef.value.fileList = [];
|
||||
if (importFormRef.value.resetForm) {
|
||||
importFormRef.value.resetForm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ submitForm, resetForm })
|
||||
|
||||
// 监听props变化,确保弹窗打开时表单重置
|
||||
watch(() => props.query, (newQuery) => {
|
||||
if (newQuery && Object.keys(newQuery).length > 0) {
|
||||
isPopupMode.value = true
|
||||
// 先设置必要的类型信息
|
||||
datasetType.value = newQuery?.datasetType as string || ''
|
||||
form.value.datasetParentType = newQuery?.datasetParentType as string || ''
|
||||
form.value.type = newQuery?.type || 0
|
||||
|
||||
// 强制重置表单
|
||||
resetForm();
|
||||
|
||||
// 只有在编辑模式(有ID)时才获取详情
|
||||
if (newQuery?.id) {
|
||||
getDetails(newQuery.id)
|
||||
}
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
onMounted(() => {
|
||||
// 非弹窗模式的初始化逻辑
|
||||
if (!isPopupMode.value) {
|
||||
const id = route?.query.id
|
||||
datasetType.value = route.query.datasetType as string
|
||||
form.value.datasetParentType = route.query.datasetParentType as string
|
||||
if (id) {
|
||||
getDetails(id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 从查询参数初始化表单
|
||||
const initFormFromQuery = (query: any) => {
|
||||
datasetType.value = query?.datasetType as string || ''
|
||||
form.value = {
|
||||
datasetName: '',
|
||||
datasetCategory: '',
|
||||
markStatus: '',
|
||||
cleanRule: '',
|
||||
datasetIntro: '',
|
||||
datasetType: query?.datasetType || '',
|
||||
datasetParentType: query?.datasetParentType || '',
|
||||
type: query?.type || 0,
|
||||
filesList: []
|
||||
}
|
||||
|
||||
// 如果有ID,获取详情
|
||||
if (query?.id) {
|
||||
getDetails(query.id)
|
||||
}
|
||||
}
|
||||
watch(() => form.value.filesList, (newVal) => {
|
||||
if (newVal?.length > 0) {
|
||||
nextTick(() => {
|
||||
formRef.value?.validateField('fildData')
|
||||
})
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.divLine {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 行内字段容器样式 */
|
||||
.inline-fields-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 上传区域容器样式 */
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin-left: 90px; /* 与数据集描述输入框对齐 */
|
||||
}
|
||||
|
||||
/* 减小el-tabs__content的高度 */
|
||||
.upload-container :deep(.el-tabs__content) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 确保上传区域高度减小 */
|
||||
.upload-container :deep(.el-upload-dragger) {
|
||||
height: 40px !important;
|
||||
line-height: 40px !important;
|
||||
}
|
||||
|
||||
/* 无边框按钮样式 */
|
||||
.no-border-btn {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* 确保表单项样式 */
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* 调整表单项内容区域 */
|
||||
:deep(.el-form-item__content) {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 968px) {
|
||||
:deep(.el-select) {
|
||||
width: 240px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
637
src/views/dataService/datasetManage/index.vue
Normal file
637
src/views/dataService/datasetManage/index.vue
Normal file
@ -0,0 +1,637 @@
|
||||
<template>
|
||||
<!-- 列表 -->
|
||||
<el-card shadow="never" class="b-r-15">
|
||||
<div style="position: relative">
|
||||
<!-- 数据集类型选择区域 -->
|
||||
<!-- <div class="dataset-selection-area">
|
||||
<div class="selection-row">
|
||||
<span class="selection-label">数据集类型:</span>
|
||||
<el-radio-group v-model="datasetParentType" class="radio-group">
|
||||
<el-radio-button :label="1">文本数据集</el-radio-button>
|
||||
<el-radio-button :label="2">多模态数据集</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 数据集用途选项卡 -->
|
||||
<el-tabs v-model="datasetType" class="demo-tabs">
|
||||
<el-tab-pane label="训练数据集" :name="1">
|
||||
<template v-if="datasetType === 1">
|
||||
<dataSetTable :datasetType="1" :dataset-parent-type="datasetParentType" />
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="评估数据集" :name="2">
|
||||
<template v-if="datasetType === 2">
|
||||
<dataSetTable :datasetType="2" :dataset-parent-type="datasetParentType" />
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="添加文档" style="width: 60%; height: 80%">
|
||||
<el-form ref="queryFormRef" :inline="true" :model="dialogForm" class="-mb-15px" label-width="auto">
|
||||
<div class="diaBox">
|
||||
<el-steps :active="diaSteps" align-center>
|
||||
<el-step title="选择文件" />
|
||||
<el-step title="规则配置" />
|
||||
</el-steps>
|
||||
<div v-if="diaSteps == 1">
|
||||
<div style="padding: 10px">
|
||||
<el-upload
|
||||
ref="uploadRef" v-model:file-list="fileList" :auto-upload="false" :disabled="formLoading"
|
||||
:limit="1" :on-exceed="handleExceed" accept=".pdf,.txt,.doc,.ppt,.docx,.pptx,.epub,.md" action="none"
|
||||
drag>
|
||||
<h5 class="el-upload__text">点击或将文件拖拽到这里上传</h5>
|
||||
<div>
|
||||
文档格式支持[".pdf",".txt”,".doc",".ppt",".docx",".pptx",".epub”,".md”]请确保内容可复制,文档格式30MB以内,单次最多上传1个。请确保文档内容可复制,文档中的表格和图片暂时无法学习音频上传后会自动解析成文字存储并且学习,内容可修改
|
||||
</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 10px; left: 45%">
|
||||
<el-button type="primary" @click="next">下一步</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="diaSteps == 2">
|
||||
<div class="secondBox">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="5">
|
||||
<div>
|
||||
{{ diaForm.title }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="11">
|
||||
<div class="textareaClass">
|
||||
<el-input v-model="diaForm.text" type="textarea" :rows="20" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8" style="border-left: 1px solid #eeeff3">
|
||||
<div>
|
||||
<el-radio-group v-model="diaForm.radio1">
|
||||
<el-radio value="1" size="large" style="width: 100%">自动分段</el-radio>
|
||||
会将上传的文档自动进行分段和去除敏感数据,如个人信息、url及敏感词
|
||||
<el-radio value="2" size="large">自定义</el-radio>
|
||||
自定义数据处理规则
|
||||
</el-radio-group>
|
||||
<div v-if="diaForm.radio1 == 2">
|
||||
<el-form-item label="分段标识" prop="name" required>
|
||||
<el-input v-model="diaForm.name" placeholder="请输入分段标识符" />
|
||||
</el-form-item>
|
||||
<el-form-item label="长度" prop="length" required>
|
||||
<el-input v-model="diaForm.length" placeholder="请输入分段长度" />
|
||||
</el-form-item>
|
||||
<el-checkbox-group v-model="diaForm.checkList">
|
||||
<el-checkbox label="对连续的空格、换行符和制表符进行去重" value="1" />
|
||||
<el-checkbox label="删除所有URL和电子邮件地址" value="2" />
|
||||
<el-checkbox label="删除敏感词" value="3" />
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 10px; left: 45%">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-button @click="goBack">上一步</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-button type="primary" @click="trim">完成</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="knowDialogVisible" title="数据预览" style="width: 60%">
|
||||
<el-form ref="configurationFormRef" :inline="true" :model="configurationForm" class="-mb-15px" label-width="auto">
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item label="文档内容" prop="text">
|
||||
<el-input v-model="configurationForm.text" placeholder="请输入文档内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="使用状态" prop="stutas">
|
||||
<el-select v-model="configurationForm.stutas" clearable placeholder="请选择使用状态" class="!w-240px">
|
||||
<el-option v-for="item in statusList" :key="item.id" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-button type="primary">查询</el-button>
|
||||
<el-button>重置</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<div class="box">
|
||||
<div class="smallBox" v-for="(item, index) in boxList" :key="index">
|
||||
<el-row>
|
||||
<el-col :span="12">{{ item.title }}</el-col>
|
||||
<el-col :span="12" style="text-align: right">
|
||||
<Icon icon="ep:delete" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div>
|
||||
{{ item.content }}
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-switch v-model="item.switch" />
|
||||
</el-col>
|
||||
<el-col :span="12" style="text-align: right">
|
||||
{{ item.num }}
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 分页 -->
|
||||
<div style="display: flex; justify-content: flex-end; margin: 10px">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage2" v-model:page-size="pageSize2"
|
||||
:page-sizes="[100, 200, 300, 400]" :size="size" :disabled="disabled" :background="background"
|
||||
layout="sizes, prev, pager, next" :total="1000" @size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange" />
|
||||
</div>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="testDialogVisible" title="命中测试" style="width: 60%">
|
||||
<div class="textBox">
|
||||
<div>
|
||||
<el-input placeholder="输入文档中的内容进行命中测试" v-model="testForm.text" type="textarea" :rows="5" />
|
||||
</div>
|
||||
<el-row :gutter="20" style="margin-top: 10px">
|
||||
<el-col :span="12">
|
||||
<div style="margin: 10px 0">
|
||||
<span style="font-weight: bold"> 测试记录 </span>
|
||||
<span style="margin-left: 10px; color: #b1b7c0">仅展示最近15条</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-table
|
||||
:header-cell-style="{ background: '#E7EBFF', color: '#606266' }" :data="testLefeData"
|
||||
style="height: 200px; border: 1px solid #e5e6ec; border-radius: 10px">
|
||||
<el-table-column prop="time" label="时间" />
|
||||
<el-table-column prop="content" label="测试内容" />
|
||||
<el-table-column prop="duan" label="命中段落" />
|
||||
</el-table>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12" style="border-left: 1px solid #eee">
|
||||
<div style="width: 100%">
|
||||
<img style="width: 100%" src="@/assets/imgs/1733561223884.jpg" alt="" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div style="display: flex; justify-content: center; margin-top: 10px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-button @click="testDialogVisible = false">
|
||||
<Icon icon="ep:circle-close" />
|
||||
取消
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-button @click="testDialogVisible = false" type="primary">
|
||||
<Icon icon="ep:circle-check" />
|
||||
测试
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="oaDialogVisible" :title="oaTitle" style="width: 60%">
|
||||
<el-form ref="configurationFormRef" :inline="true" :model="oaForm" class="-mb-15px" label-width="auto">
|
||||
<el-form-item label="问题" prop="problem" required style="width: 100%">
|
||||
<el-input
|
||||
v-model="oaForm.problem" style="width: 80%; margin-right: 10px" maxlength="100" show-word-limit
|
||||
type="text" />
|
||||
<el-button>
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
<el-button>
|
||||
<Icon icon="ep:close" />
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="答案" prop="answer" required style="width: 90%">
|
||||
<el-input
|
||||
v-model="oaForm.answer" style="width: 100%" maxlength="3000" show-word-limit :rows="10"
|
||||
type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div style="display: flex; justify-content: center; margin-top: 10px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-button @click="oaDialogVisible = false">
|
||||
<Icon icon="ep:circle-close" />
|
||||
取消
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-button @click="oaDialogVisible = false" type="primary">
|
||||
<Icon icon="ep:circle-check" />
|
||||
确认
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import dataSetTable from './table.vue'
|
||||
import { getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
// 新增的数据集类型状态
|
||||
const dataCategory = ref('text') // 保留用于向后兼容
|
||||
const datasetParentType = ref(1) // 1: 文本数据集, 2: 多模态数据集
|
||||
|
||||
// 原有代码保持不变
|
||||
const oaForm = ref({})
|
||||
const selectable = (row) => ![1, 2].includes(row.id)
|
||||
const wenSelectable = (row) => ![1, 2].includes(row.id)
|
||||
const oaSelectable = (row) => ![1, 2].includes(row.id)
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const router = useRouter() // 路由
|
||||
const route = useRoute() // 当前路由信息
|
||||
|
||||
const knowDialogVisible = ref(false)
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
datasetType: 1,
|
||||
datasetName: undefined,
|
||||
datasetCategory: undefined,
|
||||
})
|
||||
|
||||
const datasetType = ref(1)
|
||||
const oaTitle = ref('')
|
||||
const oaDialogVisible = ref(false)
|
||||
const testDialogVisible = ref(false)
|
||||
const configurationForm = ref({
|
||||
topK: 4,
|
||||
Score: 0.6,
|
||||
knowLength: 100
|
||||
})
|
||||
const tableData = ref([
|
||||
{
|
||||
name: '数据集名称1',
|
||||
type: 'DPO',
|
||||
size: '这是描述内容~这是描述内容~这是描述内容~',
|
||||
length: '1000',
|
||||
status: '进行中',
|
||||
jindu: '6%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
},
|
||||
{
|
||||
name: '数据集名称2',
|
||||
type: 'SFT',
|
||||
size: '这是描述内容~这是描述内容~',
|
||||
length: '2000',
|
||||
status: '进行中',
|
||||
jindu: '80%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
},
|
||||
{
|
||||
name: '数据集名称3',
|
||||
type: 'DPO',
|
||||
size: '这是描述内容~',
|
||||
length: '5000',
|
||||
status: '已完成',
|
||||
jindu: '100%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
},
|
||||
{
|
||||
name: '数据集名称4',
|
||||
type: 'SFT',
|
||||
size: '这是描述内容~这是描述内容~',
|
||||
length: '8000',
|
||||
status: '已完成',
|
||||
jindu: '100%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
},
|
||||
{
|
||||
name: '数据集名称5',
|
||||
type: 'SFT',
|
||||
size: '这是描述内容~',
|
||||
length: '2000',
|
||||
status: '已完成',
|
||||
jindu: '100%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
}
|
||||
])
|
||||
const tableData1 = ref([
|
||||
{
|
||||
name: '数据集名称1',
|
||||
type: 'Only Query',
|
||||
size: '这是描述内容~这是描述内容~这是描述内容~',
|
||||
length: '1000',
|
||||
status: '进行中',
|
||||
jindu: '6%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
},
|
||||
{
|
||||
name: '数据集名称2',
|
||||
type: 'QA对',
|
||||
size: '这是描述内容~这是描述内容~',
|
||||
length: '2000',
|
||||
status: '进行中',
|
||||
jindu: '80%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
},
|
||||
{
|
||||
name: '数据集名称3',
|
||||
type: 'Only Query',
|
||||
size: '这是描述内容~',
|
||||
length: '5000',
|
||||
status: '已完成',
|
||||
jindu: '100%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
},
|
||||
{
|
||||
name: '数据集名称4',
|
||||
type: 'QA对',
|
||||
size: '这是描述内容~这是描述内容~',
|
||||
length: '8000',
|
||||
status: '已完成',
|
||||
jindu: '100%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
},
|
||||
{
|
||||
name: '数据集名称5',
|
||||
type: 'Only Query',
|
||||
size: '这是描述内容~',
|
||||
length: '2000',
|
||||
status: '已完成',
|
||||
jindu: '100%',
|
||||
time: '2024-03-29 12:30:00'
|
||||
}
|
||||
])
|
||||
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const cardList = ref([
|
||||
{
|
||||
name: 'MES知识数据库',
|
||||
title: 'MES知识数据库',
|
||||
num: 2,
|
||||
content: '存储mes业务功能框架以及影响的业务场景说明'
|
||||
},
|
||||
{
|
||||
name: 'MES知识数据库',
|
||||
title: 'MES知识数据库',
|
||||
num: 2,
|
||||
content: '存储mes业务功能框架以及影响的业务场景说明'
|
||||
},
|
||||
{
|
||||
name: 'MES知识数据库',
|
||||
title: 'MES知识数据库',
|
||||
num: 2,
|
||||
content: '存储mes业务功能框架以及影响的业务场景说明'
|
||||
},
|
||||
{
|
||||
name: 'MES知识数据库',
|
||||
title: 'MES知识数据库',
|
||||
num: 2,
|
||||
content: '存储mes业务功能框架以及影响的业务场景说明'
|
||||
},
|
||||
{
|
||||
name: 'MES知识数据库',
|
||||
title: 'MES知识数据库',
|
||||
num: 2,
|
||||
content: '存储mes业务功能框架以及影响的业务场景说明'
|
||||
}
|
||||
])
|
||||
const OAtableData = ref([
|
||||
{
|
||||
id: '185663182054991362',
|
||||
problem: 'ceshi',
|
||||
answer: '11',
|
||||
status: '未处理',
|
||||
time: '2024-11-13 09:34:55'
|
||||
},
|
||||
{
|
||||
id: '185634325432591362',
|
||||
problem: '234',
|
||||
answer: '234',
|
||||
status: '未处理',
|
||||
time: '2024-11-13 09:34:55'
|
||||
},
|
||||
{
|
||||
id: '183245235234324324',
|
||||
problem: '234',
|
||||
answer: '234',
|
||||
status: '未处理',
|
||||
time: '2024-11-13 09:34:55'
|
||||
}
|
||||
])
|
||||
const diaForm = ref({
|
||||
title: '新建 文本文档(9)....',
|
||||
radio1: '2',
|
||||
length: 200,
|
||||
checkList: ['3'],
|
||||
text: '大模型训练平台 图生文 支持 9.28-9.30号 文生图支持 具体排期等产品出图 后端和算法已经开始对接预计10.16号完成 文生图 支持 具体排期等产品出图后端和算法已经开始对接 预计10.20号完成 大模型训练平台--键配置Ul需要等功能开发完毕 占用前后端 3-5天时间 当前进度:整体平台开发基础功能完成,管理端待测试 和上正式环境。多模态模型已经部署,差原型图,前后端算法联调。大模型平台迭代 热词处理 9.28-9.30 测试环境部署完 等测试有空再提到正式环境 手机号邮箱特殊识别 9.28-9.30 测试环境部署完 等测试有空再提到正式环境 RAG-迭代 更新向量模型以及rerank模块 10.12号完成RAG 知识检索问答 支持附带图片和视频功能 9.28刚对完方案和功能 具体排期等产品出图 预计10.18号完成 RAG 多轮知识问答 支持离线式对知识库进行处理 来实现机器人多轮知识问答 具体排期等产品出图 预计10.25号完成 RAG web支持爬取政府网站文件 需求未对 排期待定 大模型平台 对话 支持图片对话功能 原型出了 算法开发基本完毕 优先级在后面 预计10.31号完成 大模型平台 对话 支持文档/excel 对话功能 算法开发基本完毕 优先级在后面 排期'
|
||||
})
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('新增知识库')
|
||||
const dialogForm = ref({
|
||||
radio1: 1,
|
||||
length: 200,
|
||||
checkList: '3'
|
||||
})
|
||||
const diaSteps = ref(1)
|
||||
const testForm = ref({
|
||||
text: ''
|
||||
})
|
||||
const testLefeData = ref([
|
||||
{
|
||||
time: '2014-11-14',
|
||||
content: '555',
|
||||
duan: '6'
|
||||
},
|
||||
{
|
||||
time: '2024-11-14',
|
||||
content: '555',
|
||||
duan: '6'
|
||||
}
|
||||
])
|
||||
const boxList = ref([
|
||||
{
|
||||
title: '1',
|
||||
switch: true,
|
||||
num: '202/300',
|
||||
content:
|
||||
'AI数字人培训方案北京捷通华声科技股份有限公司修订历史记录表日期版本说明作者2024-10-30V1.0第一版帅博目录1 培训目的 12 培训方式 13 培训课程说明 1..'
|
||||
},
|
||||
{
|
||||
title: '2',
|
||||
switch: true,
|
||||
num: '202/300',
|
||||
content:
|
||||
'通过培训,最终招标方的技术人员能独立掌握设备的配置,故障诊断,维护管理等技术,使之能适应系统正常运行的需求。培训方式我司免费为招标方提供培训。…'
|
||||
},
|
||||
{
|
||||
title: '3',
|
||||
switch: true,
|
||||
num: '202/300',
|
||||
content:
|
||||
'由我司对招标方技术人员进行基础培训,包括功能原理、平台使用、使用维护以及运行管理等内容。培训课程说明培训主要内容应包括功能原理、软件安装、配置.'
|
||||
},
|
||||
{
|
||||
title: '4',
|
||||
switch: true,
|
||||
num: '202/300',
|
||||
content:
|
||||
'培训时间:产品运行期间,将根据用户时间安排,组织系统培训。培训地点:工程现场。主持人:项目经理,原厂培训讲师。培训对象:领导、最终用户、平台系.'
|
||||
},
|
||||
{
|
||||
title: '5',
|
||||
switch: true,
|
||||
num: '202/300',
|
||||
content:
|
||||
'0.5天平台高级功能配置1天异常情况处理1天建立培训评价体系为确保培训的质量和效果,可以通过培训的评价机制对培训效果进行评价。1.监督指导。培训组织'
|
||||
}
|
||||
])
|
||||
const statusList = ref([
|
||||
{
|
||||
label: '开启',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: '关闭',
|
||||
value: false
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
const handleClick = (e) => {
|
||||
datasetType.value = e
|
||||
}
|
||||
|
||||
const next = () => {
|
||||
diaSteps.value = 2
|
||||
}
|
||||
const goBack = () => {
|
||||
diaSteps.value = 1
|
||||
}
|
||||
// 添加文档提交
|
||||
const trim = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
// 数据预览
|
||||
const dataPreview = (e) => {
|
||||
console.log(e, '数据预览')
|
||||
knowDialogVisible.value = true
|
||||
}
|
||||
// 命中测试
|
||||
|
||||
const addOA = () => {
|
||||
oaTitle.value = '添加OA'
|
||||
oaDialogVisible.value = true
|
||||
}
|
||||
// oa表格修改
|
||||
const oaEdit = (e) => {
|
||||
oaTitle.value = '编辑OA'
|
||||
oaDialogVisible.value = true
|
||||
}
|
||||
// oa表格删除
|
||||
const oaDelete = (e) => {
|
||||
ElMessageBox.confirm('确定要删除当前OA吗?', '', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
})
|
||||
}
|
||||
|
||||
// 监听路由变化,设置正确的 tab 和数据集类型
|
||||
watchEffect(() => {
|
||||
// 设置数据集类型标签页
|
||||
if (route.query?.activeTab) {
|
||||
datasetType.value = parseInt(route.query.activeTab as string) || 1
|
||||
}
|
||||
// 设置数据集父类型选择器(文本/多模态)
|
||||
if (route.query?.parentType) {
|
||||
datasetParentType.value = parseInt(route.query.parentType as string) || 1
|
||||
}
|
||||
})
|
||||
// 在 onMounted 生命周期中初始化
|
||||
onMounted(() => {
|
||||
// 如果 URL 中有 activeTab 参数,使用它来设置当前 tab
|
||||
if (route.query?.activeTab) {
|
||||
datasetType.value = parseInt(route.query.activeTab as string) || 1
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 新增样式 - 匹配图片中的设计 */
|
||||
.dataset-selection-area {
|
||||
background: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.selection-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.selection-label {
|
||||
width: 100px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 原有样式保持不变 */
|
||||
.content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.diaBox {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.secondBox {
|
||||
height: 100%;
|
||||
margin: 20px 10px;
|
||||
}
|
||||
|
||||
.secondBox textarea::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.smallBox {
|
||||
float: left;
|
||||
width: 48%;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
background-color: #f2f3f5;
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
340
src/views/dataService/datasetManage/table.vue
Normal file
340
src/views/dataService/datasetManage/table.vue
Normal file
@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<div class="dataset-table-container">
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="auto"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="数据集名称" prop="datasetName">
|
||||
<el-input
|
||||
v-model="queryParams.datasetName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据集类型" prop="datasetCategory">
|
||||
<el-select
|
||||
v-model="queryParams.datasetCategory" class="!w-240px" clearable
|
||||
placeholder="请选择">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions('llm_dataset_category_'+props.datasetType)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="float: right">
|
||||
<el-button type="primary" @click="handleQuery">搜索</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-divider/>
|
||||
<div class="buttonBox">
|
||||
<el-button type="primary" @click="goDataCreate">创建数据集</el-button>
|
||||
<el-button type="primary" @click="setDataLabel" v-if="props.datasetType===1 && props.datasetParentType !== 2">数据标注</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="list" v-loading="loading" highlight-current-row @current-change="handleCurrentChange" style="width: 100%"
|
||||
show-header=true stripe :header-cell-style="{ background: '#f2f3f5', color: '#6b7785', fontWeight: 'bold' }">
|
||||
<el-table-column width="50" align="center">
|
||||
<template #default="scope">
|
||||
<el-radio-group v-model="currentRow.id">
|
||||
<el-radio :value="scope.row?.id" />
|
||||
</el-radio-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="datasetName" label="数据集名称"/>
|
||||
<el-table-column prop="datasetCategory" label="数据集类型">
|
||||
<template #default="scope">
|
||||
{{ getDictLabel('llm_dataset_category_' + props.datasetType, scope.row.datasetCategory) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="datasetIntro" label="数据集描述">
|
||||
<template #default="scope">
|
||||
<el-popover
|
||||
placement="top"
|
||||
trigger="hover"
|
||||
width="300"
|
||||
:content="scope.row.datasetIntro"
|
||||
>
|
||||
<template #reference>
|
||||
{{ scope.row.datasetIntro && scope.row.datasetIntro.length>18 ? scope.row.datasetIntro.substring(0,18)+'...' : scope.row.datasetIntro }}
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dataLength" label="数据集长度"/>
|
||||
<el-table-column prop="status" label="标注状态" v-if="datasetType == 1">
|
||||
<template #default="scope">
|
||||
<!-- <el-tag >{{ getDictLabel('llm_dataset_mark_status', scope.row.status) }}</el-tag>-->
|
||||
<el-tag v-if="scope.row.markStatus == 0" type="primary">{{
|
||||
getDictLabel('llm_dataset_mark_status', scope.row.markStatus)
|
||||
}}</el-tag>
|
||||
<el-tag v-if="scope.row.markStatus == 1" type="warning">{{
|
||||
getDictLabel('llm_dataset_mark_status', scope.row.markStatus)
|
||||
}}</el-tag>
|
||||
<el-tag v-if="scope.row.markStatus == 2" type="success">{{
|
||||
getDictLabel('llm_dataset_mark_status', scope.row.markStatus)
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="datasetType == 1" prop="annotateProgress" label="标注进度">
|
||||
<template #default="scope">
|
||||
{{ scope.row.annotateProgress }}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
/>
|
||||
<el-table-column :width="300" align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="ruleConfiguration(scope.row)"> 详情</el-button>
|
||||
<el-button link type="primary" @click="goDataCreate(scope.row)" v-if="props.datasetParentType !== 2"> 修改</el-button>
|
||||
<el-button link type="primary" @click="handleExport(scope.row)" v-if="props.datasetParentType !== 2">导出</el-button>
|
||||
<el-button link type="danger" @click="detale(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top: 10px">
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 数据集创建表单弹窗 -->
|
||||
<el-dialog
|
||||
v-model="datasetFormVisible"
|
||||
title="创建数据集"
|
||||
width="800px"
|
||||
:before-close="handleCloseDialog"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<dataset-form
|
||||
ref="datasetFormRef"
|
||||
:query="datasetFormQuery"
|
||||
@create-success="handleDatasetCreateSuccess"
|
||||
@close="handleCloseDialog"
|
||||
/>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, onMounted, nextTick } from 'vue'
|
||||
import {getDictLabel, getIntDictOptions} from "@/utils/dict"
|
||||
import DatasetForm from './form.vue';
|
||||
import {dateFormatter} from "@/utils/formatTime";
|
||||
import * as dataSetApi from '@/api/dataService/dataset'
|
||||
import download from "@/utils/download";
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
datasetType: Number,
|
||||
datasetParentType: Number
|
||||
});
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
datasetType: props.datasetType,
|
||||
datasetParentType: props.datasetParentType,
|
||||
datasetName: undefined,
|
||||
datasetCategory: undefined,
|
||||
type: 0,
|
||||
})
|
||||
|
||||
const {t} = useI18n() // 国际化
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const {path} = useRoute()
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
const currentRow = ref<any>({
|
||||
id: ''
|
||||
})
|
||||
|
||||
const exportLoading = ref(false)
|
||||
const datasetFormRef = ref() // 数据集表单引用
|
||||
const datasetFormVisible = ref(false) // 数据集表单弹窗显示状态
|
||||
const datasetFormQuery = ref({}) // 数据集表单参数
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await dataSetApi.getPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// // 文档表格操作
|
||||
// // 规则配置
|
||||
// const ruleConfiguration = (e) => {
|
||||
//
|
||||
// }
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
|
||||
const goDataCreate = (row?: any) => {
|
||||
// 设置表单参数
|
||||
datasetFormQuery.value = {
|
||||
id: row?.id,
|
||||
datasetType: props.datasetType,
|
||||
datasetParentType: props.datasetParentType,
|
||||
type: queryParams.type
|
||||
}
|
||||
// 显示弹窗
|
||||
datasetFormVisible.value = true
|
||||
}
|
||||
|
||||
const setDataLabel = () => {
|
||||
if (currentRow.value.id) {
|
||||
router.push({
|
||||
path: '/dataService/dataset/mark',
|
||||
query: {
|
||||
id: currentRow.value.id,
|
||||
type: queryParams.type,
|
||||
datasetType: currentRow.value.datasetType,
|
||||
datasetParentType: props.datasetParentType,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
message.warning('请选择数据集!')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 文档表格操作
|
||||
// 规则配置
|
||||
const ruleConfiguration = (row: any) => {
|
||||
router.push({
|
||||
path: '/dataService/dataset/detail',
|
||||
query: {
|
||||
id: row.id,
|
||||
type: queryParams.type,
|
||||
datasetType: props.datasetType,
|
||||
datasetParentType: props.datasetParentType
|
||||
}
|
||||
})
|
||||
}
|
||||
const handleExport = async (row: any) => {
|
||||
try {
|
||||
// 检查标注进度是否达到100%
|
||||
if (row.annotateProgress !== 100) {
|
||||
message.warning('标注进度未达到100%,无法导出数据')
|
||||
return
|
||||
}
|
||||
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await dataSetApi.exportData({
|
||||
datasetId: row.id
|
||||
})
|
||||
download.excel(data, '数据集数据.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
const detale = async (e) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await dataSetApi.deleteDataSet(e.id)
|
||||
message.success(t('common.delSuccess'))
|
||||
//刷新列表
|
||||
await getList()
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
/** 数据集创建成功回调 */
|
||||
const handleDatasetCreateSuccess = () => {
|
||||
message.success('数据集创建成功')
|
||||
datasetFormVisible.value = false
|
||||
// 刷新列表
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 关闭弹窗前的回调 */
|
||||
const handleCloseDialog = () => {
|
||||
datasetFormVisible.value = false
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val: any) => {
|
||||
if (val) {
|
||||
currentRow.value = val
|
||||
} else {
|
||||
currentRow.value = {
|
||||
id: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听datasetParentType变化 */
|
||||
watch(() => props.datasetParentType, (newVal) => {
|
||||
if (newVal !== undefined) {
|
||||
queryParams.datasetParentType = newVal;
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
}
|
||||
}, { immediate: false });
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
let pathList = path.split('-')
|
||||
queryParams.type = Number(pathList[pathList.length - 1]) || 0
|
||||
queryParams.datasetParentType = props.datasetParentType;
|
||||
getList()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
form{
|
||||
margin-top: 20px
|
||||
}
|
||||
.buttonBox{
|
||||
margin-bottom: 20px
|
||||
}
|
||||
</style>
|
@ -144,7 +144,7 @@ const form = reactive<any>({
|
||||
|
||||
// 获取数据集
|
||||
const getDataSet = async () => {
|
||||
let list = await dataSetApi.getAll()
|
||||
let list = await dataSetApi.getAllList()
|
||||
list[0]['datasetName'] = '数据集'
|
||||
list[1]['datasetName'] = '官方数据'
|
||||
dataSetList.value = list
|
||||
|
Loading…
x
Reference in New Issue
Block a user