512 lines
15 KiB
Vue
512 lines
15 KiB
Vue
<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>
|