SourceTermAnalysisSystem_vue/src/pages/sjtb/configTask/dataTable/index.vue
2026-07-15 19:06:41 +08:00

512 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ElMessage, ElSelect } from 'element-plus';
import { v4 as uuidv4 } from 'uuid';
import { useI18n } from 'vue-i18n';
import { createTargetTables, getFileId, getMindMap, getTable, getUser, targetUser } from '@/utils/axios/sync/configTask/index';
import { X6Graph } from './antvX6';
// 接收一个名为 data 的对象 props类型为 DataProps
const props = defineProps<{
data: DataProps; // 这里的 data 就是父组件传递的整个对象
}>();
const emit = defineEmits(['cancel']);
// 定义一个接口描述对象结构
interface DataProps {
taskId: string;
sourceId: string;
sourceName: string;
}
const { t } = useI18n();
// 模式列表
const modeList = ref<any[]>([]);
// 数据表列表
const dataList = ref<any[]>([]);
// 字段列表
const fieldList = ref<any[]>([]);
// 目标用户名
const targetUserNameList = ref<any[]>([]);
const graph = ref<X6Graph | null>(null);
const level3 = ref<InstanceType<typeof ElSelect> | null>(null);
const level4 = ref<InstanceType<typeof ElSelect> | null>(null);
// 手动输入模式名
const manualModeInput = ref('');
// 表单数据
const formData = reactive({
taskId: '',
sourceId: '', // 默认选中“mySql”
sourceName: '',
mode: [],
dataValue: [],
field: '',
targetUserName: '',
});
// 当前配置项Id
const configData = ref<any>([]);
// 记录上一次的选中值(初始为空)
const prevModeValues = ref<string[]>([]);
// 记录上一次的选中值(数据表列表)
const prevDataValues = ref<string[]>([]);
// 记录上一次的选中值(字段列表)
const prevFieldValues = ref<string>('');
// 配置的数据表集合
const configDataValues = ref<string[]>([]);
// 初始化思维导图画布
function initGraph() {
// 初始化画布容器ID为 graph-container
graph.value = new X6Graph('graphContainer').init({ grid: false, zoom: true });
// 1. 创建根节点
const root = graph.value.addRootNode({
id: formData.sourceId,
label: formData.sourceName,
});
console.log(root);
}
// 模式选择变化(正确的事件参数:仅新值)
function handleModeChange(newValues: string[]) {
if (!graph.value)
return;
// 旧值从 prevModeValues 中获取
const oldValues = prevModeValues.value;
// 1. 找出新增的选项(需要新增节点)
const added = newValues.filter(v => !oldValues.includes(v));
// 2. 找出移除的选项(需要删除节点)
const removed = oldValues.filter(v => !newValues.includes(v));
// 新增节点
added.forEach((value) => {
const item = modeList.value.find((item: any) => item.value === value);
if (item) {
graph.value?.addChildNode(formData.sourceId, {
id: value,
label: item.label,
level: 'level2',
});
}
});
// 删除节点
removed.forEach((value) => {
graph.value?.removeNode(value);
});
// 更新旧值记录(下次触发时使用)
prevModeValues.value = [...newValues];
}
// 数据选择变化(模仿 handleModeChange 逻辑)
function handleDataListChange(newValues: string[]) {
if (!graph.value)
return;
// 旧值从 prevDataValues 中获取
const oldValues = prevDataValues.value;
// 1. 找出新增的选项(需要新增节点)
const added = newValues.filter(v => !oldValues.includes(v));
// 2. 找出移除的选项(需要删除节点)
const removed = oldValues.filter(v => !newValues.includes(v));
// 新增节点(假设添加到 level3 层级,父节点为某个 level2 节点,可按需修改)
added.forEach((value) => {
// 从数据列表中找到对应项的 label
const item = dataList.value.find(item => item.value === value);
if (item) {
// 假设父节点 ID 为某个 level2 节点(例如:取第一个 level2 节点的 ID或通过参数传入
// 实际场景中需根据业务确定父节点 ID
const parentNodeId = configData.value[1].id; // 可替换为动态获取的父节点
graph.value?.addChildNode(parentNodeId, {
id: `${value}`, // 避免与其他节点 ID 冲突,加前缀区分
label: item.label,
level: 'level3', // 假设数据节点为 level3 层级
});
}
});
// 删除节点(注意:节点 ID 需与新增时一致,这里对应上面的 `data-${value}`
removed.forEach((value) => {
const nodeId = `${value}`; // 与新增时的 ID 保持一致
if (!configDataValues.value.includes(value)) {
graph.value?.removeNode(nodeId);
}
});
// 更新旧值记录(下次触发时使用)
prevDataValues.value = [...newValues];
}
// 数据选择变化(单选逻辑)
function handleFieldListChange(newValue: string) { // 注意:参数从 string[] 改为 string
console.log('当前选中值:', newValue);
if (!graph.value)
return;
// 1. 旧值从 prevFieldValues 中获取(同样存单个值,非数组)
const oldValue = prevFieldValues.value;
// 2. 核心判断:只有新值≠旧值时,才执行新增/删除(单选无需找新增/删除数组)
if (newValue !== oldValue) {
// 第一步:先删除旧值对应的节点(如果旧值存在)
if (oldValue) {
const oldNodeId = `${oldValue}`; // 与新增时的ID规则一致
// 只有旧值不在配置保留列表中,才删除节点
if (!configDataValues.value.includes(oldValue)) {
graph.value.removeNode(oldNodeId);
}
}
// 第二步:再新增新值对应的节点(如果新值存在)
if (newValue) {
// 从数据列表找到新值对应的label
const item = fieldList.value.find(item => item.value === newValue);
if (item) {
// 动态获取父节点ID这里逻辑保持你原有可根据业务调整
const parentNodeId = configData.value[2].id;
// 新增level4节点ID规则与删除时一致加前缀避免冲突
graph.value.addChildNode(parentNodeId, {
id: newValue,
label: item.label,
level: 'level4', // 单选节点层级为level4
});
}
}
}
// 3. 更新旧值记录(存单个值,供下次对比)
prevFieldValues.value = newValue;
}
// -------------------------- 8. 初始化逻辑(保留) --------------------------
onMounted(() => {
// 源数据库id和name从props传入
formData.taskId = props.data.taskId;
formData.sourceId = props.data.sourceId;
formData.sourceName = props.data.sourceName;
// 根据源数据库获取(模式/用户名)列表
nextTick(() => {
getTargetUserName(formData.taskId);
getMindMap({
taskId: formData.taskId,
}, (res) => {
initGraph();
getModelList((value: any) => {
console.log(value);
if (res.result.userInfos.length) {
console.log(res);
graph.value?.importData(res.result.userInfos, formData.sourceId);
res.result.userInfos.forEach((item: any) => {
modeList.value.forEach((mode: any) => {
if (mode.label === item.userName) {
formData.mode.push(mode.value);
}
});
});
}
});
// mindMap.value = res.result;
});
});
// 注册antvX6Event回调事件
EventBusTool.getEventBus().subscribe('antvX6Event', (res: any) => {
// 如果进入全屏全屏icon没有高亮的话需要高亮
if (res.key === 'removeNode') {
console.log(res.id);
if (res.level === 'level2') {
// 3. 从下拉选中值中移除(关键:同步下拉选择器的选中状态)
formData.mode = formData.mode.filter(v => v !== res.id);
prevModeValues.value = prevModeValues.value.filter(v => v !== res.id);
formData.dataValue = [];
formData.field = '';
}
else if (res.level === 'level3') {
// 3. 从下拉选中值中移除(关键:同步下拉选择器的选中状态)
formData.dataValue = formData.dataValue.filter(v => v !== res.id);
prevDataValues.value = prevDataValues.value.filter(v => v !== res.id);
formData.field = '';
}
else if (res.level === 'level4') {
formData.field = '';
prevFieldValues.value = '';
}
}
if (res.key === 'configNode') {
configData.value = res.allParents;
configDataValues.value = [...formData.dataValue, formData.field];
// 请求
if (res.level === 'level2') {
getDataListById(res.label);
}
if (res.level === 'level3') {
getFieldById(res.label);
}
}
});
});
// 根据taskId获取目标用户名
function getTargetUserName(taskId: any) {
targetUser({ taskId }, (res) => {
console.log(res);
targetUserNameList.value = res.result.map((item: any) => {
return {
value: item,
label: item,
};
});
});
}
// 根据源数据库id获取用户
function getModelList(callback?: (data: any) => void) {
getUser({ sourceId: formData.sourceId }, (res) => {
modeList.value = res.result.map((item: any) => {
return {
value: uuidv4(),
label: item,
};
});
callback?.(res);
});
}
// 根据源数据库id以及用户名称获取所有表名称
function getDataListById(name: any) {
getTable({ sourceId: formData.sourceId, username: name }, (res: any) => {
dataList.value = res.result.map((item: any) => {
return {
value: uuidv4(),
label: item,
};
});
level3.value?.toggleMenu();
formData.dataValue = [];
});
}
function getFieldById(name: any) {
console.log(configData.value[1].label, name);
getFileId({ sourceId: formData.sourceId, username: configData.value[1].label, tableName: name }, (res) => {
fieldList.value = res.result.map((item: any) => {
return {
value: uuidv4(),
label: item,
};
});
level4.value?.toggleMenu();
formData.field = '';
});
}
// 手动添加模式
function handleAddMode() {
const inputVal = manualModeInput.value.trim();
if (!inputVal) {
ElMessage({ type: 'warning', message: '请输入模式名称' });
return;
}
// 检查是否已存在相同 label 的模式
const exists = modeList.value.find((item: any) => item.label === inputVal);
if (exists) {
// 如果已存在但未选中,直接选中
if (!formData.mode.includes(exists.value)) {
formData.mode.push(exists.value);
handleModeChange([...formData.mode]);
}
else {
ElMessage({ type: 'warning', message: '该模式已添加' });
}
}
else {
// 新建项并选中
const newItem = { value: uuidv4(), label: inputVal };
modeList.value.push(newItem);
formData.mode.push(newItem.value);
handleModeChange([...formData.mode]);
}
manualModeInput.value = '';
}
// 保存事件
function handleSave() {
if (!formData.targetUserName) {
ElMessage({
type: 'error',
message: `请选择目标用户名`,
});
return;
}
const data = graph.value?.getTargetTableData(formData.taskId, formData.targetUserName);
console.log(data);
if (data) {
createTargetTables(data, (res) => {
console.log(res);
if (res === true) {
ElMessage({
type: 'success',
message: `配置数据表成功`,
});
emit('cancel');
}
});
}
else {
ElMessage({
type: 'error',
message: `配置有误,请检查`,
});
}
}
// 取消事件
function handleCancel() {
emit('cancel');
}
</script>
<template>
<div class="dataTable">
<ElForm :inline="true" :model="formData" class="custom-form" style="margin-bottom: 20px;">
<ElFormItem :label="t('database')">
<ElInput v-model="formData.sourceName" disabled style="width: 180px" />
</ElFormItem>
<ElFormItem :label="t('mode')">
<ElSelect v-model="formData.mode" style="width: 220px" multiple :max-collapse-tags="0" collapse-tags
collapse-tags-tooltip filterable :placeholder="t('pleaseSelect')" class="custom-select"
@change="handleModeChange">
<ElOption v-for="(item, index) in modeList" :key="index" :label="item.label" :value="item.value" />
</ElSelect>
</ElFormItem>
<ElFormItem label="手动输入模式" v-show="!formData.mode || !formData.mode.length">
<div style="display: flex; align-items: center;">
<ElInput v-model="manualModeInput" style="width: 160px" placeholder="输入模式名" @keyup.enter="handleAddMode" />
<ElButton type="primary" style="margin-left: 4px; height: 34px; border-radius: 4px;" @click="handleAddMode">
添加
</ElButton>
</div>
</ElFormItem>
<ElFormItem :label="t('dataTable')">
<ElSelect ref="level3" v-model="formData.dataValue" style="width: 220px" multiple :max-collapse-tags="0"
collapse-tags collapse-tags-tooltip filterable :placeholder="t('pleaseSelect')" class="custom-select"
@change="handleDataListChange">
<ElOption v-for="(item, index) in dataList" :key="index" :label="item.label" :value="item.value" />
</ElSelect>
</ElFormItem>
<ElFormItem :label="t('field')">
<ElSelect ref="level4" v-model="formData.field" style="width: 220px" filterable :placeholder="t('pleaseSelect')"
class="custom-select" @change="handleFieldListChange">
<ElOption v-for="(item, index) in fieldList" :key="index" :label="item.label" :value="item.value"
:disabled="formData.dataValue.length === 0" />
</ElSelect>
</ElFormItem>
<ElFormItem :label="t('targetUserName')">
<ElSelect v-model="formData.targetUserName" style="width: 220px" filterable :placeholder="t('pleaseSelect')"
class="custom-select">
<ElOption v-for="(item, index) in targetUserNameList" :key="index" :label="item.label" :value="item.value" />
</ElSelect>
</ElFormItem>
</ElForm>
<div class="container_warapper">
<div id="graphContainer" class="flow-chart-container" />
</div>
<div class="btns">
<div class="save" @click="handleSave">
保存
</div>
<div class="close" @click="handleCancel">
取消
</div>
</div>
</div>
</template>
<style scoped lang="less">
.dataTable {
width: 100%;
padding: 20px;
box-sizing: border-box;
.custom-form {
margin-bottom: 20px;
.custom-select {
margin-right: 10px;
}
}
.container_warapper {
width: 100%;
padding: 40px 0;
box-sizing: border-box;
border: 1px solid #416f7f;
.flow-chart-container {
width: 100%;
height: 600px;
overflow: auto;
}
}
.x6-node:hover rect {
stroke: #2196f3;
strokewidth: 2;
}
.x6-node-selected rect {
stroke: #ff9800;
strokewidth: 2;
}
// 按钮hover优化
.x6-node .btn:hover circle {
fill: rgba(173, 230, 238, 0.2);
}
}
.btns {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
div {
margin-top: 5px;
width: 150px;
height: 40px;
border-radius: 2px;
background-color: rgb(10, 74, 92);
color: #fff;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-size: 16px;
}
.save {
margin-right: 5px;
}
}
</style>