679 lines
19 KiB
Vue
679 lines
19 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 pztbslFlag = ref(false);
|
||
const pztbslValue = ref("");
|
||
|
||
// 当前配置同步数量的节点id
|
||
const nowPzTbslId = ref(null);
|
||
|
||
// 配置的数据表集合
|
||
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) {
|
||
graph.value?.importData(res.result.userInfos, formData.sourceId);
|
||
|
||
res.result.userInfos.forEach((item: any) => {
|
||
const graphSl = graph.value?.getGraph();
|
||
|
||
// 获取全部节点实例
|
||
const allNodes = graphSl.getNodes();
|
||
console.log(allNodes, "allNodes");
|
||
// 过滤:根据你存名称的字段,这里假设字段是 label;如果你的字段是 name,就改成 item.name
|
||
const matchNodesData = allNodes
|
||
.map((node) => {
|
||
return {
|
||
label: node.label,
|
||
id: node.id,
|
||
};
|
||
})
|
||
.filter((item1) => {
|
||
return item1.label == item.userName;
|
||
});
|
||
const exists = modeList.value.find(
|
||
(item1: any) => item1.label === item.userName
|
||
);
|
||
|
||
if (!exists) {
|
||
const newItem = {
|
||
value: matchNodesData[0].id,
|
||
label: item.userName,
|
||
};
|
||
modeList.value.push(newItem);
|
||
formData.mode.push(newItem.value);
|
||
} else {
|
||
formData.mode.push(exists.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];
|
||
|
||
console.log(
|
||
configDataValues.value,
|
||
configData.value,
|
||
"2configData.value"
|
||
);
|
||
// 请求
|
||
if (res.level === "level2") {
|
||
getDataListById(res.label);
|
||
}
|
||
if (res.level === "level3") {
|
||
if (res.isTbsl) {
|
||
const graphSl = graph.value?.getGraph();
|
||
const jdItem = graphSl.getCellById(res.id);
|
||
nowPzTbslId.value = res.id;
|
||
|
||
const oldSyncCount = jdItem.getData().syncCount;
|
||
pztbslFlag.value = true;
|
||
if (oldSyncCount) {
|
||
pztbslValue.value = oldSyncCount;
|
||
} else {
|
||
pztbslValue.value = "";
|
||
}
|
||
} else {
|
||
getFieldById(res.label);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
function paTbslMethod() {
|
||
if (!pztbslValue.value) {
|
||
ElMessage({
|
||
type: "error",
|
||
message: `请配置同步数量`,
|
||
});
|
||
return;
|
||
}
|
||
|
||
const graphSl = graph.value?.getGraph();
|
||
const jdItem = graphSl.getCellById(nowPzTbslId.value);
|
||
jdItem.setData({ ...jdItem.getData(), syncCount:pztbslValue.value })
|
||
pztbslFlag.value = false;
|
||
pztbslValue.value = "";
|
||
nowPzTbslId.value = null;
|
||
}
|
||
|
||
// 根据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,
|
||
};
|
||
});
|
||
|
||
console.log(formData, "2141");
|
||
|
||
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");
|
||
}
|
||
},
|
||
30 * 60 * 1000
|
||
);
|
||
} 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"
|
||
allow-create default-first-option :reserve-keyword="false"
|
||
@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('mode')">
|
||
<ElInput
|
||
v-model="manualModeInput"
|
||
style="width: 160px"
|
||
placeholder="输入模式名"
|
||
@keyup.enter="handleAddMode"
|
||
/>
|
||
</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>
|
||
|
||
<Dialog
|
||
v-model="pztbslFlag"
|
||
title="配置同步数量"
|
||
width="20vw"
|
||
class="config-dialog"
|
||
>
|
||
<el-input
|
||
v-model="pztbslValue"
|
||
style="width: 240px"
|
||
type="number"
|
||
placeholder="请配置同步数量"
|
||
/>
|
||
<template #footer>
|
||
<ElButton
|
||
type="text"
|
||
style="color: #fff; cursor: no-drop"
|
||
@click="paTbslMethod"
|
||
>
|
||
配置
|
||
</ElButton>
|
||
</template>
|
||
</Dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped lang="less">
|
||
.dataTable {
|
||
width: 100%;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
|
||
::v-deep .config-dialog {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
}
|
||
|
||
.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>
|