树状知识图谱
This commit is contained in:
parent
0a6fff3cf2
commit
c58a029c10
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist.zip
|
||||
build
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
10
src/apis/knowledge/chart.ts
Normal file
10
src/apis/knowledge/chart.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { request } from '@/utils/request/index.ts';
|
||||
|
||||
// 列表接口
|
||||
export const dataApi = async (data?: Record<string, any>) => {
|
||||
return await request({
|
||||
url: `/v1.2/graph-connections/1/gremlin-query`,
|
||||
method: 'POST',
|
||||
params: data
|
||||
})
|
||||
};
|
||||
@ -41,14 +41,16 @@ export const useChartHooks = () => {
|
||||
},
|
||||
},
|
||||
animation: false, // 完全关闭动画
|
||||
animationDuration: 0, // 动画时长设为0
|
||||
animationDurationUpdate: 0, // 更新动画时长设为0
|
||||
animationDuration: 10, // 动画时长设为0
|
||||
animationDurationUpdate: 10, // 更新动画时长设为0
|
||||
animationEasingUpdate: "linear", // 使用线性缓动
|
||||
force: {
|
||||
repulsion: 1000,
|
||||
|
||||
repulsion: 800, // 减小排斥力,避免节点分布过散
|
||||
gravity: 0.1, // 添加重力防止节点飞散
|
||||
friction: 0.6, // 添加摩擦力
|
||||
friction: 1, // 添加摩擦力
|
||||
layoutAnimation: false, // 关闭力导向布局动画
|
||||
edgeLength: 100, // 设置边的理想长度,影响关系文本位置
|
||||
},
|
||||
// animationDurationUpdate: 100,
|
||||
// animationEasingUpdate: "quinticInOut",
|
||||
@ -56,7 +58,7 @@ export const useChartHooks = () => {
|
||||
normal: {
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
fontSize: 10, // 减小文本大小,使其与节点大小更匹配
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -69,7 +71,7 @@ export const useChartHooks = () => {
|
||||
{
|
||||
type: "graph",
|
||||
layout: "force",
|
||||
symbolSize: 60,
|
||||
symbolSize: 30, // 增大节点大小,使其与文本大小比例更协调
|
||||
focusNodeAdjacency: true,
|
||||
roam: true,
|
||||
categories: [
|
||||
@ -102,7 +104,7 @@ export const useChartHooks = () => {
|
||||
normal: {
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
fontSize: 10, // 减小文本大小,使其与节点大小更匹配
|
||||
},
|
||||
formatter: (e: Record<string, any>) => {
|
||||
const name = e.data.raw.properties.name
|
||||
@ -111,16 +113,19 @@ export const useChartHooks = () => {
|
||||
},
|
||||
},
|
||||
force: {
|
||||
repulsion: 1000,
|
||||
repulsion: 800, // 减小排斥力,避免节点分布过散
|
||||
edgeLength: 100, // 设置边的理想长度
|
||||
},
|
||||
edgeSymbolSize: [4, 50],
|
||||
edgeSymbolSize: [4, 30], // 减小边的箭头大小,使其更协调
|
||||
edgeLabel: {
|
||||
normal: {
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: 10,
|
||||
fontSize: 9, // 减小关系文本大小
|
||||
},
|
||||
formatter: "{c}",
|
||||
position: 'middle', // 确保文本位于边的中间
|
||||
offset: [0, 5], // 微调文本位置,避免与边重叠
|
||||
},
|
||||
},
|
||||
data: [
|
||||
@ -309,8 +314,10 @@ export const useChartHooks = () => {
|
||||
raw: data,
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
draggable: options?.draggable || false,
|
||||
itemStyle: data?.itemStyle || null
|
||||
draggable: false,
|
||||
itemStyle: data?.itemStyle || null,
|
||||
x: data?.x|| null,
|
||||
y: data?.y || null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
273
src/views/knowledge/hooks/location copy.ts
Normal file
273
src/views/knowledge/hooks/location copy.ts
Normal file
@ -0,0 +1,273 @@
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
/**
|
||||
* 位置管理钩子函数
|
||||
* 用于生成和管理知识图谱节点的树状布局位置
|
||||
*/
|
||||
export const useLocationHooks = () => {
|
||||
// 位置映射表,用于存储节点ID到位置的映射
|
||||
const locationMap = ref(new Map<string, { x: number; y: number }>());
|
||||
|
||||
// 配置参数
|
||||
const config = ref({
|
||||
// 层级之间的垂直间距
|
||||
levelGap: 200, // 纵向间距20
|
||||
// 同一层级内节点的水平间距
|
||||
nodeGap: 100, // 横向间距10
|
||||
// 画布中心位置
|
||||
centerX: 400,
|
||||
centerY: 300,
|
||||
// 初始缩放比例
|
||||
scale: 1
|
||||
});
|
||||
|
||||
/**
|
||||
* 计算树状布局的节点位置
|
||||
* @param nodes 节点列表
|
||||
* @param relationships 关系列表
|
||||
* @param rootIds 根节点ID列表
|
||||
* @returns 更新后的节点列表(包含位置信息)
|
||||
*/
|
||||
const calculateTreeLayout = (nodes: any[], relationships: any[], rootIds: string[]) => {
|
||||
// 清除之前的位置信息
|
||||
clearLocations();
|
||||
|
||||
// 构建节点ID到节点的映射
|
||||
const nodeMap = new Map(nodes.map(node => [node.id, node]));
|
||||
|
||||
// 构建父-子关系树
|
||||
const tree = buildTreeStructure(nodeMap, relationships, rootIds);
|
||||
|
||||
console.log('tree', tree)
|
||||
|
||||
// 计算每个子树的边界
|
||||
rootIds.forEach(rootId => {
|
||||
calculateSubtreeBounds(tree[rootId]);
|
||||
});
|
||||
|
||||
// 计算每个根树的位置偏移
|
||||
let totalWidth = 0;
|
||||
const rootOffsets: number[] = [];
|
||||
|
||||
// 计算总宽度
|
||||
rootIds.forEach(rootId => {
|
||||
const root = tree[rootId];
|
||||
rootOffsets.push(totalWidth);
|
||||
totalWidth += root.width * config.value.nodeGap;
|
||||
});
|
||||
|
||||
// 应用位置计算(从下往上)
|
||||
rootIds.forEach((rootId, index) => {
|
||||
// 计算根节点的水平位置(居中分布)
|
||||
const offsetX = config.value.centerX - totalWidth * config.value.nodeGap * 0.5 + rootOffsets[index] * config.value.nodeGap;
|
||||
|
||||
// 递归计算每个节点的最终位置
|
||||
calculateFinalPosition(tree[rootId], offsetX, config.value.centerY);
|
||||
});
|
||||
|
||||
// 更新节点列表,添加位置信息
|
||||
return nodes.map(node => ({
|
||||
...node,
|
||||
x: locationMap.value.get(node.id)?.x,
|
||||
y: locationMap.value.get(node.id)?.y
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 构建树状数据结构
|
||||
* @param nodeMap 节点映射表
|
||||
* @param relationships 关系列表
|
||||
* @param rootIds 根节点ID列表
|
||||
* @returns 树状结构
|
||||
*/
|
||||
const buildTreeStructure = (nodeMap: Map<string, any>, relationships: any[], rootIds: string[]) => {
|
||||
const tree: Record<string, any> = {};
|
||||
const childrenMap = new Map<string, string[]>();
|
||||
const parentMap = new Map<string, string>();
|
||||
|
||||
// 初始化每个节点的子节点列表
|
||||
nodeMap.forEach((node, id) => {
|
||||
tree[id] = {
|
||||
id,
|
||||
children: [],
|
||||
// 用于布局计算的属性
|
||||
width: 0, // 子树宽度(以节点数为单位)
|
||||
left: 0, // 子树左边界(以节点数为单位)
|
||||
right: 0, // 子树右边界(以节点数为单位)
|
||||
center: 0 // 子树中心位置(以节点数为单位)
|
||||
};
|
||||
childrenMap.set(id, []);
|
||||
});
|
||||
|
||||
// 构建父-子关系
|
||||
relationships.forEach(relation => {
|
||||
const parentId = relation.start_node_id;
|
||||
const childId = relation.end_node_id;
|
||||
if (childrenMap.has(parentId)) {
|
||||
childrenMap.get(parentId)?.push(childId);
|
||||
}
|
||||
parentMap.set(childId, parentId);
|
||||
});
|
||||
|
||||
// 构建完整的树结构
|
||||
childrenMap.forEach((childrenIds, parentId) => {
|
||||
if (tree[parentId]) {
|
||||
tree[parentId].children = childrenIds.map(childId => tree[childId]);
|
||||
}
|
||||
});
|
||||
|
||||
return tree;
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算子树的边界和宽度(后序遍历)
|
||||
* @param node 当前节点
|
||||
*/
|
||||
const calculateSubtreeBounds = (node: any) => {
|
||||
// 如果是叶子节点
|
||||
if (!node.children || node.children.length === 0) {
|
||||
node.width = 1;
|
||||
node.left = 0;
|
||||
node.right = 0;
|
||||
node.center = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// 递归计算所有子节点的边界
|
||||
node.children.forEach((child: any) => {
|
||||
calculateSubtreeBounds(child);
|
||||
});
|
||||
|
||||
// 计算当前节点的子树边界
|
||||
let totalLeft = 0;
|
||||
let totalRight = 0;
|
||||
let totalWidth = 0;
|
||||
|
||||
node.children.forEach((child: any) => {
|
||||
// 更新总宽度
|
||||
totalWidth += child.width;
|
||||
if (node.children.length > 1) {
|
||||
totalWidth += 1; // 添加子节点之间的间距(以节点数为单位)
|
||||
}
|
||||
});
|
||||
|
||||
// 计算每个子节点的相对位置
|
||||
let currentPos = 0;
|
||||
node.children.forEach((child: any, index: number) => {
|
||||
// 记录子节点的相对位置
|
||||
child.relativeX = currentPos - child.center;
|
||||
currentPos += child.width;
|
||||
|
||||
// 添加子节点之间的间距
|
||||
if (index < node.children.length - 1) {
|
||||
currentPos += 1; // 添加一个节点宽度的间距
|
||||
}
|
||||
});
|
||||
|
||||
// 设置当前节点的边界信息
|
||||
node.width = totalWidth;
|
||||
node.left = 0;
|
||||
node.right = totalWidth - 1;
|
||||
node.center = Math.floor(totalWidth / 2);
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算节点的最终位置(前序遍历)
|
||||
* @param node 当前节点
|
||||
* @param offsetX 水平偏移量
|
||||
* @param y Y坐标
|
||||
* @param level 当前层级
|
||||
*/
|
||||
const calculateFinalPosition = (node: any, offsetX: number, y: number, level: number = 0) => {
|
||||
// 计算当前节点的X坐标
|
||||
const nodeX = offsetX + node.center * config.value.nodeGap;
|
||||
|
||||
// 计算当前节点的Y坐标(从上往下排列)
|
||||
const nodeY = y + level * config.value.levelGap;
|
||||
|
||||
// 存储节点位置
|
||||
locationMap.value.set(node.id, {
|
||||
x: nodeX,
|
||||
y: nodeY
|
||||
});
|
||||
|
||||
// 递归计算子节点的位置
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach((child: any) => {
|
||||
const childOffsetX = offsetX + child.relativeX * config.value.nodeGap;
|
||||
calculateFinalPosition(child, childOffsetX, y, level + 1);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据节点ID获取位置
|
||||
* @param id 节点ID
|
||||
* @returns 节点位置
|
||||
*/
|
||||
const getLocationById = (id: string) => {
|
||||
return locationMap.value.get(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量设置节点位置
|
||||
* @param positions 位置映射对象 {nodeId: {x, y}}
|
||||
*/
|
||||
const setLocations = (positions: Record<string, { x: number; y: number }>) => {
|
||||
Object.entries(positions).forEach(([id, position]) => {
|
||||
locationMap.value.set(id, position);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除所有位置信息
|
||||
*/
|
||||
const clearLocations = () => {
|
||||
locationMap.value.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新配置参数
|
||||
* @param newConfig 新的配置参数
|
||||
*/
|
||||
const updateConfig = (newConfig: Partial<typeof config.value>) => {
|
||||
config.value = { ...config.value, ...newConfig };
|
||||
};
|
||||
|
||||
/**
|
||||
* 应用位置信息到ECharts选项配置
|
||||
* @param option ECharts选项配置
|
||||
* @returns 更新后的ECharts选项配置
|
||||
*/
|
||||
const applyLocationsToOption = (option: any) => {
|
||||
if (!option.series || !option.series[0]) return option;
|
||||
|
||||
const updatedSeries = {
|
||||
...option.series[0],
|
||||
// 更新节点数据,添加位置信息
|
||||
data: option.series[0].data.map((node: any) => ({
|
||||
...node,
|
||||
...locationMap.value.get(node.id)
|
||||
}))
|
||||
};
|
||||
|
||||
return {
|
||||
...option,
|
||||
series: [updatedSeries]
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
// 状态
|
||||
locationMap,
|
||||
config,
|
||||
|
||||
// 方法
|
||||
calculateTreeLayout,
|
||||
getLocationById,
|
||||
setLocations,
|
||||
clearLocations,
|
||||
updateConfig,
|
||||
applyLocationsToOption
|
||||
};
|
||||
};
|
||||
266
src/views/knowledge/hooks/location.ts
Normal file
266
src/views/knowledge/hooks/location.ts
Normal file
@ -0,0 +1,266 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* 位置管理钩子函数
|
||||
* 用于生成和管理知识图谱节点的树状布局位置
|
||||
*/
|
||||
export const useLocationHooks = () => {
|
||||
// 位置映射表,用于存储节点ID到位置的映射
|
||||
const locationMap = ref(new Map<string, { x: number; y: number }>());
|
||||
|
||||
// 配置参数
|
||||
const config = ref({
|
||||
// 层级之间的垂直间距
|
||||
levelGap: 120, // 增加垂直间距,避免节点垂直重叠
|
||||
// 同一层级内节点的水平间距
|
||||
nodeGap: 180, // 增加水平间距,避免节点水平重叠
|
||||
// 树之间的间距(增加以避免不同树的叶节点重叠)
|
||||
treeGap: 200,
|
||||
// 画布中心位置
|
||||
centerX: 400,
|
||||
centerY: 300,
|
||||
// 初始缩放比例
|
||||
scale: 1
|
||||
});
|
||||
|
||||
/**
|
||||
* 计算树状布局的节点位置
|
||||
* @param nodes 节点列表
|
||||
* @param relationships 关系列表
|
||||
* @param rootIds 根节点ID列表
|
||||
* @returns 更新后的节点列表(包含位置信息)
|
||||
*/
|
||||
const calculateTreeLayout = (nodes: any[], relationships: any[], rootIds: string[]) => {
|
||||
// 清除旧的位置信息
|
||||
clearLocations();
|
||||
|
||||
// 构建节点ID到节点的映射
|
||||
const nodeMap = new Map(nodes.map(node => [node.id, node]));
|
||||
|
||||
// 构建父-子关系树(倒序)
|
||||
const tree = buildTreeStructure(nodeMap, relationships, rootIds);
|
||||
|
||||
// 后序遍历计算每个子树的边界和中心位置
|
||||
rootIds.slice().reverse().forEach(rootId => {
|
||||
calculateSubtreeBounds(tree[rootId]);
|
||||
});
|
||||
|
||||
// 计算所有根树的总宽度(包括树间距)
|
||||
const totalWidth = rootIds.reduce((sum, rootId) => {
|
||||
return sum + tree[rootId].width + config.value.treeGap;
|
||||
}, 0) - config.value.treeGap; // 减去最后一个树间距
|
||||
|
||||
// 根据总宽度计算起始X位置,使整个布局居中
|
||||
let currentX = config.value.centerX - totalWidth / 2;
|
||||
|
||||
// 前序遍历计算每个节点的最终位置
|
||||
rootIds.slice().reverse().forEach((rootId, index) => {
|
||||
const rootNode = tree[rootId];
|
||||
|
||||
// 计算当前根树的起始位置
|
||||
const rootX = currentX + rootNode.width / 2;
|
||||
|
||||
// 递归计算每个节点的最终位置
|
||||
calculateFinalPosition(rootNode, rootX, config.value.centerY, 0);
|
||||
|
||||
// 更新当前X位置,为下一个根树留出足够空间
|
||||
currentX += rootNode.width + config.value.treeGap;
|
||||
});
|
||||
|
||||
// 更新节点列表,添加位置信息
|
||||
return nodes.map(node => ({
|
||||
...node,
|
||||
x: locationMap.value.get(node.id)?.x,
|
||||
y: locationMap.value.get(node.id)?.y
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 构建树状数据结构(倒序)
|
||||
* @param nodeMap 节点映射表
|
||||
* @param relationships 关系列表
|
||||
* @param rootIds 根节点ID列表
|
||||
* @returns 树状结构
|
||||
*/
|
||||
const buildTreeStructure = (nodeMap: Map<string, any>, relationships: any[], rootIds: string[]) => {
|
||||
const tree: Record<string, any> = {};
|
||||
const childrenMap = new Map<string, string[]>();
|
||||
const parentMap = new Map<string, string>();
|
||||
|
||||
// 初始化每个节点的子节点列表
|
||||
nodeMap.forEach((node, id) => {
|
||||
tree[id] = {
|
||||
id,
|
||||
children: [],
|
||||
// 布局相关属性
|
||||
width: 0, // 子树宽度
|
||||
left: 0, // 子树最左边界
|
||||
right: 0, // 子树最右边界
|
||||
center: 0, // 子树中心位置
|
||||
relativeX: 0 // 相对于父节点的X偏移
|
||||
};
|
||||
childrenMap.set(id, []);
|
||||
});
|
||||
|
||||
// 构建父-子关系
|
||||
relationships.forEach(relation => {
|
||||
const parentId = relation.start_node_id;
|
||||
const childId = relation.end_node_id;
|
||||
if (childrenMap.has(parentId)) {
|
||||
childrenMap.get(parentId)?.push(childId);
|
||||
parentMap.set(childId, parentId);
|
||||
}
|
||||
});
|
||||
|
||||
// 构建完整的树结构(子节点倒序排列)
|
||||
childrenMap.forEach((childrenIds, parentId) => {
|
||||
if (tree[parentId]) {
|
||||
// 倒序添加子节点,确保从右到左布局
|
||||
tree[parentId].children = childrenIds.slice().reverse().map(childId => tree[childId]);
|
||||
}
|
||||
});
|
||||
|
||||
return tree;
|
||||
};
|
||||
|
||||
/**
|
||||
* 后序遍历计算子树边界
|
||||
* @param node 当前节点
|
||||
*/
|
||||
const calculateSubtreeBounds = (node: any) => {
|
||||
if (!node || !node.children || node.children.length === 0) {
|
||||
// 叶子节点
|
||||
node.width = config.value.nodeGap;
|
||||
node.left = -config.value.nodeGap / 2;
|
||||
node.right = config.value.nodeGap / 2;
|
||||
node.center = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// 先计算所有子节点的边界
|
||||
node.children.forEach((child: any) => {
|
||||
calculateSubtreeBounds(child);
|
||||
});
|
||||
|
||||
// 计算当前节点的边界
|
||||
let totalWidth = 0;
|
||||
const childCount = node.children.length;
|
||||
|
||||
// 计算所有子树的总宽度(包括间距)
|
||||
node.children.forEach((child: any) => {
|
||||
totalWidth += child.width + config.value.nodeGap;
|
||||
});
|
||||
|
||||
// 减去最后一个子树后面的间距
|
||||
totalWidth -= config.value.nodeGap;
|
||||
|
||||
// 计算每个子节点的相对位置
|
||||
let currentX = -totalWidth / 2;
|
||||
node.children.forEach((child: any) => {
|
||||
child.relativeX = currentX + child.width / 2;
|
||||
currentX += child.width + config.value.nodeGap;
|
||||
});
|
||||
|
||||
// 更新当前节点的边界信息
|
||||
node.width = totalWidth;
|
||||
node.left = -totalWidth / 2;
|
||||
node.right = totalWidth / 2;
|
||||
node.center = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 前序遍历计算节点的最终位置
|
||||
* @param node 当前节点
|
||||
* @param x 父节点的X坐标
|
||||
* @param y 父节点的Y坐标
|
||||
* @param level 当前层级
|
||||
*/
|
||||
const calculateFinalPosition = (node: any, x: number, y: number, level: number) => {
|
||||
if (!node) return;
|
||||
|
||||
// 计算当前节点的最终位置
|
||||
const nodeX = x + node.relativeX;
|
||||
const nodeY = y + (level * config.value.levelGap);
|
||||
|
||||
// 存储节点位置
|
||||
locationMap.value.set(node.id, { x: nodeX, y: nodeY });
|
||||
|
||||
// 递归计算子节点的位置
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach((child: any) => {
|
||||
calculateFinalPosition(child, nodeX, nodeY, level + 1);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据节点ID获取位置
|
||||
* @param id 节点ID
|
||||
* @returns 节点位置
|
||||
*/
|
||||
const getLocationById = (id: string) => {
|
||||
return locationMap.value.get(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量设置节点位置
|
||||
* @param positions 位置映射对象 {nodeId: {x, y}}
|
||||
*/
|
||||
const setLocations = (positions: Record<string, { x: number; y: number }>) => {
|
||||
Object.entries(positions).forEach(([id, position]) => {
|
||||
locationMap.value.set(id, position);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除所有位置信息
|
||||
*/
|
||||
const clearLocations = () => {
|
||||
locationMap.value.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新配置参数
|
||||
* @param newConfig 新的配置参数
|
||||
*/
|
||||
const updateConfig = (newConfig: Partial<typeof config.value>) => {
|
||||
config.value = { ...config.value, ...newConfig };
|
||||
};
|
||||
|
||||
/**
|
||||
* 应用位置信息到ECharts选项配置
|
||||
* @param option ECharts选项配置
|
||||
* @returns 更新后的ECharts选项配置
|
||||
*/
|
||||
const applyLocationsToOption = (option: any) => {
|
||||
if (!option.series || !option.series[0]) return option;
|
||||
|
||||
const updatedSeries = {
|
||||
...option.series[0],
|
||||
// 更新节点数据,添加位置信息
|
||||
data: option.series[0].data.map((node: any) => ({
|
||||
...node,
|
||||
...locationMap.value.get(node.id)
|
||||
}))
|
||||
};
|
||||
|
||||
return {
|
||||
...option,
|
||||
series: [updatedSeries]
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
// 状态
|
||||
locationMap,
|
||||
config,
|
||||
|
||||
// 方法
|
||||
calculateTreeLayout,
|
||||
getLocationById,
|
||||
setLocations,
|
||||
clearLocations,
|
||||
updateConfig,
|
||||
applyLocationsToOption
|
||||
};
|
||||
};
|
||||
@ -1,8 +1,12 @@
|
||||
export const useUtils = () => {
|
||||
|
||||
export const useUtils = (options: Record<string, any>) => {
|
||||
// 预设的颜色
|
||||
const presetColors = [...(options?.colors || [])]
|
||||
console.log('presetColors', presetColors)
|
||||
// 存储已生成的颜色,确保不重复
|
||||
const usedColors = new Set<string>();
|
||||
|
||||
|
||||
|
||||
// 生成随机颜色
|
||||
const generateColor = (): string => {
|
||||
// 生成更亮的颜色,避免太暗
|
||||
@ -19,7 +23,8 @@ export const useUtils = () => {
|
||||
|
||||
// 尝试生成不重复的颜色,最多尝试100次
|
||||
do {
|
||||
color = generateColor()
|
||||
// 有预设先用预设
|
||||
color = presetColors.shift() || generateColor()
|
||||
attempts++
|
||||
} while (usedColors.has(color) && attempts < 100)
|
||||
|
||||
|
||||
@ -1,24 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, defineComponent, nextTick, computed, onUnmounted, onMounted } from "vue";
|
||||
import Chat from "@/views/knowledge/components/Chat/chat.vue";
|
||||
import { useServices } from "@/views/knowledge/services/index";
|
||||
import dayjs from "dayjs";
|
||||
import { Input, Select, SelectOption } from "ant-design-vue";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
ref,
|
||||
inject,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
computed,
|
||||
onUnmounted,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
import Chat from '@/views/knowledge/components/Chat/chat.vue'
|
||||
import { useServices } from '@/views/knowledge/services/index'
|
||||
import dayjs from 'dayjs'
|
||||
import { Input, Select, SelectOption } from 'ant-design-vue'
|
||||
import _ from 'lodash'
|
||||
|
||||
const services = useServices();
|
||||
const services = useServices()
|
||||
|
||||
const echartRef = ref<any>(null);
|
||||
const echartRef = ref<any>(null)
|
||||
|
||||
const currentNode = ref({});
|
||||
const showRelationData = ref([]);
|
||||
const currentNode = ref({})
|
||||
const showRelationData = ref([])
|
||||
|
||||
const echartInit = (chart: any) => {
|
||||
echartRef.value = chart;
|
||||
echartRef.value.on("click", function (params: any) {
|
||||
console.log("params", params);
|
||||
const nodeId = params.data.id;
|
||||
services.db.api.onPointClick(nodeId);
|
||||
echartRef.value = chart
|
||||
|
||||
echartRef.value.on('click', function (params: any) {
|
||||
console.log('params', params)
|
||||
const nodeId = params.data.id
|
||||
services.db.api.onPointClick(nodeId)
|
||||
// onPointClick(params.data.id);
|
||||
// console.log("params", params);
|
||||
// // console.log("params", params);
|
||||
@ -27,30 +36,30 @@ const echartInit = (chart: any) => {
|
||||
// currentNode.value = params.data.raw;
|
||||
// showRelationData.value = nodes;
|
||||
// console.log("nodes", showRelationData.value);
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
const showData = computed(() => {
|
||||
// console.log('useNode', showOptions)
|
||||
// return {};
|
||||
const curServer = "db";
|
||||
return services[curServer].state.showOptions.value;
|
||||
const curServer = 'db'
|
||||
return services[curServer].state.showOptions.value
|
||||
// return {};
|
||||
});
|
||||
})
|
||||
|
||||
const onChange = _.debounce((id: string) => {
|
||||
console.log(id, "id");
|
||||
services.db.api.onSearch(id);
|
||||
}, 1000);
|
||||
console.log(id, 'id')
|
||||
services.db.api.onSearch(id)
|
||||
}, 1000)
|
||||
|
||||
const prefix = "knowledge-chat-page";
|
||||
const prefix = 'knowledge-chat-page'
|
||||
|
||||
onMounted(() => {
|
||||
services.db.api.getData();
|
||||
services.db.api.getQuery();
|
||||
});
|
||||
services.db.api.getData()
|
||||
services.db.api.getQuery()
|
||||
})
|
||||
|
||||
onUnmounted(() => {});
|
||||
onUnmounted(() => {})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -103,19 +112,31 @@ onUnmounted(() => {});
|
||||
<div class="node-wrapper">
|
||||
<div class="label">切片:</div>
|
||||
<a-tooltip>
|
||||
<template #title>{{ currentNode.properties.origin_text }}</template>
|
||||
<div class="node-desc">{{ currentNode.properties.origin_text }}</div>
|
||||
<template #title>{{
|
||||
currentNode.properties.origin_text
|
||||
}}</template>
|
||||
<div class="node-desc">
|
||||
{{ currentNode.properties.origin_text }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="node-wrapper">
|
||||
<div class="label">创建时间:</div>
|
||||
|
||||
{{ dayjs(currentNode.properties.created_at).format("YYYY-MM-DD hh:mm:ss") }}
|
||||
{{
|
||||
dayjs(currentNode.properties.created_at).format(
|
||||
'YYYY-MM-DD hh:mm:ss'
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<div class="node-wrapper">
|
||||
<div class="label">更新时间:</div>
|
||||
|
||||
{{ dayjs(currentNode.properties.last_updated).format("YYYY-MM-DD hh:mm:ss") }}
|
||||
{{
|
||||
dayjs(currentNode.properties.last_updated).format(
|
||||
'YYYY-MM-DD hh:mm:ss'
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 关联 -->
|
||||
@ -142,18 +163,28 @@ onUnmounted(() => {});
|
||||
<div class="label">切片:</div>
|
||||
<a-tooltip>
|
||||
<template #title>{{ item.node.properties.origin_text }}</template>
|
||||
<div class="node-desc">{{ item.node.properties.origin_text }}</div>
|
||||
<div class="node-desc">
|
||||
{{ item.node.properties.origin_text }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="node-wrapper">
|
||||
<div class="label">创建时间:</div>
|
||||
|
||||
{{ dayjs(item.node.properties.created_at).format("YYYY-MM-DD hh:mm:ss") }}
|
||||
{{
|
||||
dayjs(item.node.properties.created_at).format(
|
||||
'YYYY-MM-DD hh:mm:ss'
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<div class="node-wrapper">
|
||||
<div class="label">更新时间:</div>
|
||||
|
||||
{{ dayjs(item.node.properties.last_updated).format("YYYY-MM-DD hh:mm:ss") }}
|
||||
{{
|
||||
dayjs(item.node.properties.last_updated).format(
|
||||
'YYYY-MM-DD hh:mm:ss'
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -179,7 +210,7 @@ onUnmounted(() => {});
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$prefix: "knowledge-chat-page";
|
||||
$prefix: 'knowledge-chat-page';
|
||||
|
||||
.#{$prefix} {
|
||||
width: 100%;
|
||||
|
||||
@ -1,356 +0,0 @@
|
||||
import { useChartHooks } from '@/views/knowledge/hooks/chart.ts'
|
||||
import { computed, ref } from 'vue'
|
||||
import { router } from "@/router/index.ts";
|
||||
import { createData } from './mockData'
|
||||
|
||||
|
||||
// 存储已生成的颜色,确保不重复
|
||||
const usedColors = new Set<string>()
|
||||
|
||||
/**
|
||||
* 生成随机且不重复的颜色
|
||||
* @returns 十六进制颜色值
|
||||
*/
|
||||
const generateRandomColor = (): string => {
|
||||
// 生成随机颜色
|
||||
const generateColor = (): string => {
|
||||
// 生成更亮的颜色,避免太暗
|
||||
const r = Math.floor(Math.random() * 155) + 100
|
||||
const g = Math.floor(Math.random() * 155) + 100
|
||||
const b = Math.floor(Math.random() * 155) + 100
|
||||
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
let color: string
|
||||
let attempts = 0
|
||||
|
||||
// 尝试生成不重复的颜色,最多尝试100次
|
||||
do {
|
||||
color = generateColor()
|
||||
attempts++
|
||||
} while (usedColors.has(color) && attempts < 100)
|
||||
|
||||
// 如果尝试次数过多,清空已使用的颜色重新开始
|
||||
if (attempts >= 100) {
|
||||
usedColors.clear()
|
||||
color = generateColor()
|
||||
}
|
||||
|
||||
usedColors.add(color)
|
||||
return color
|
||||
}
|
||||
|
||||
/**
|
||||
* 为关系类型生成颜色映射
|
||||
*/
|
||||
const relationColors = ref<Map<string, string>>(new Map())
|
||||
|
||||
/**
|
||||
* 获取关系类型对应的颜色
|
||||
*/
|
||||
const getRelationColor = (type: string): string => {
|
||||
if (!relationColors.value.has(type)) {
|
||||
relationColors.value.set(type, generateRandomColor())
|
||||
}
|
||||
return relationColors.value.get(type) || '#000000'
|
||||
}
|
||||
|
||||
/**
|
||||
* 为节点类型生成颜色映射
|
||||
*/
|
||||
const nodeColors = ref<Map<string, string>>(new Map())
|
||||
|
||||
/**
|
||||
* 获取节点类型对应的颜色
|
||||
*/
|
||||
const getNodeColor = (type: string): string => {
|
||||
if (!nodeColors.value.has(type)) {
|
||||
nodeColors.value.set(type, generateRandomColor())
|
||||
}
|
||||
return nodeColors.value.get(type) || '#000000'
|
||||
}
|
||||
|
||||
|
||||
export const useChartServices = () => {
|
||||
|
||||
const { option, createPointData, createLink} = useChartHooks()
|
||||
|
||||
// 图表原始数据
|
||||
const chatData = ref({})
|
||||
|
||||
// 类型字典集合
|
||||
const typesMap = ref<string[]>([])
|
||||
|
||||
// 节点集合
|
||||
const nodes = ref<Record<string, any>[]>([])
|
||||
|
||||
// 节点字典集合
|
||||
const nodesMap = computed(() => {
|
||||
const dataMap = new Map()
|
||||
nodes.value.forEach((node) => {
|
||||
if (!dataMap.has(node.id)) {
|
||||
dataMap.set(node.id, node)
|
||||
}
|
||||
})
|
||||
return dataMap
|
||||
})
|
||||
|
||||
// 关系字典集合
|
||||
const relationMap = computed(() => {
|
||||
const dataMap = new Map()
|
||||
console.log('relationMap', relations.value)
|
||||
relations.value.forEach((relation) => {
|
||||
if (!dataMap.has(relation.id)) {
|
||||
dataMap.set(relation.raw.id, relation)
|
||||
}
|
||||
})
|
||||
return dataMap
|
||||
})
|
||||
|
||||
// 关系节点集合
|
||||
const relations = ref<Record<string, any>[]>([])
|
||||
|
||||
// 数据规整
|
||||
const chartConfig = {
|
||||
chatData,
|
||||
relationMap: relationMap,
|
||||
typesMap,
|
||||
nodes,
|
||||
relations
|
||||
}
|
||||
|
||||
// 选中的类型
|
||||
const selectTypes = ref<string[]>([])
|
||||
|
||||
// 选中的关系
|
||||
const selectRelations = ref<string[]>([])
|
||||
|
||||
// 切换选中类型
|
||||
const toggleSelectType = (type: string) => {
|
||||
const i = selectTypes.value.indexOf(type)
|
||||
if (i > -1) {
|
||||
selectTypes.value.splice(i, 1)
|
||||
} else {
|
||||
selectTypes.value.push(type)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换选中关系
|
||||
const toggleRelation = (relation: string) => {
|
||||
const i = selectRelations.value.indexOf(relation)
|
||||
if (i > -1) {
|
||||
selectRelations.value.splice(i, 1)
|
||||
} else {
|
||||
selectRelations.value.push(relation)
|
||||
}
|
||||
}
|
||||
|
||||
// 重置数据
|
||||
const resetData = () => {
|
||||
chatData.value = {}
|
||||
relations.value = []
|
||||
typesMap.value = []
|
||||
nodes.value = []
|
||||
// 重置颜色映射,以便下次生成新的颜色
|
||||
relationColors.value.clear()
|
||||
nodeColors.value.clear()
|
||||
}
|
||||
|
||||
// 新增类型,已存在则跳过
|
||||
const addTypeMap = (data: Record<string, any>) => {
|
||||
if (typesMap.value.includes(data.labels)) return
|
||||
typesMap.value.push(data.labels)
|
||||
}
|
||||
|
||||
// 处理数据
|
||||
const handleData = (data: Record<string, any>) => {
|
||||
const { nodes: rawNodes, relationships } = data
|
||||
rawNodes.forEach((node: Record<string, any>) => {
|
||||
addTypeMap(node)
|
||||
// 获取节点类型对应的颜色
|
||||
const nodeColor = getNodeColor(node.labels)
|
||||
const nodeInfo = createPointData({
|
||||
...node,
|
||||
type: node.labels,
|
||||
name: node.id,
|
||||
itemStyle: {
|
||||
color: nodeColor
|
||||
},
|
||||
// name: node.properties.name
|
||||
})
|
||||
nodes.value.push(nodeInfo)
|
||||
})
|
||||
relationships.forEach((relation: Record<string, any>) => {
|
||||
// addRelationMap(relation)
|
||||
// 获取关系类型对应的颜色
|
||||
const relationColor = getRelationColor(relation.type)
|
||||
const link = createLink({
|
||||
...relation,
|
||||
parentIndex: getNodeIndex(relation.start_node_id),
|
||||
index: getNodeIndex(relation.end_node_id),
|
||||
type: relation.type,
|
||||
lineStyle: {
|
||||
color: relationColor,
|
||||
width: 2 // 关系线条宽度
|
||||
},
|
||||
itemStyle: {
|
||||
color: relationColor
|
||||
}
|
||||
})
|
||||
relations.value.push(link)
|
||||
})
|
||||
}
|
||||
|
||||
// 获取节点索引
|
||||
const getNodeIndex = (id: string, source?: Record<string, any>) => {
|
||||
return (source || nodes.value).findIndex((item:Record<string, any>) => item.id === id)
|
||||
}
|
||||
|
||||
// 显示用的数据
|
||||
const showData = computed(() => {
|
||||
// 生成带有颜色的分类配置
|
||||
// const categoriesWithColor = relationsMap.value.map(item => ({
|
||||
// name: item,
|
||||
// itemStyle: {
|
||||
// normal: {
|
||||
// color: getRelationColor(item)
|
||||
// }
|
||||
// }
|
||||
// }))
|
||||
|
||||
// 为每个节点添加category索引,使其与对应关系类型关联
|
||||
const nodesWithCategory = nodes.value.map(node => ({
|
||||
...node,
|
||||
// category: relationsMap.value.indexOf(node.type) !== -1 ?
|
||||
// relationsMap.value.indexOf(node.type) : 0
|
||||
}))
|
||||
|
||||
const useData = {
|
||||
...option.value,
|
||||
series: [
|
||||
{
|
||||
...option.value.series[0],
|
||||
// categories: categoriesWithColor,
|
||||
data: nodesWithCategory,
|
||||
links: relations.value,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
console.log('useData', useData)
|
||||
return useData
|
||||
})
|
||||
|
||||
|
||||
const getDataPath = () => {
|
||||
const { id } = router.currentRoute.value.query;
|
||||
const jsonMap: Record<string, any> = {
|
||||
'1': 'http://222.190.139.186:10001/public/demo/knowledge-chat/neo4j_export2.json',
|
||||
'2': 'http://222.190.139.186:10001/public/demo/knowledge-chat/neo4j_export.json'
|
||||
}
|
||||
return jsonMap[id as string] || jsonMap[2]
|
||||
}
|
||||
|
||||
const getMockData = () => {
|
||||
const data = createData()
|
||||
return data
|
||||
|
||||
}
|
||||
|
||||
|
||||
const getData = async () => {
|
||||
// const path = getDataPath()
|
||||
// const res = await fetch(path)
|
||||
// const mockData = await res.json()
|
||||
const mockData = getMockData()
|
||||
console.log('mockData', mockData)
|
||||
chatData.value = mockData
|
||||
handleData(chatData.value)
|
||||
return mockData
|
||||
}
|
||||
|
||||
// 获取节点关系
|
||||
const getNodeRelationById = (id: string) => {
|
||||
console.log('relations', relationMap.value)
|
||||
const relationNode = []
|
||||
|
||||
relations.value.forEach((item: Record<string, any>) => {
|
||||
if (item.raw.end_node_id == id || item.raw.start_node_id == id) {
|
||||
let targetNodeId = null
|
||||
// 如果是起始点, 相关点是终点
|
||||
if (item.raw.start_node_id === id) {
|
||||
targetNodeId = item.raw.end_node_id
|
||||
} else {
|
||||
// 如果是终点, 相关点是起始点
|
||||
targetNodeId = item.raw.start_node_id
|
||||
}
|
||||
const nodeInfo = getNodeById(targetNodeId)
|
||||
if (!nodeInfo) return
|
||||
const params = {
|
||||
type: item.raw.type,
|
||||
node: nodeInfo.raw,
|
||||
relation: item.raw
|
||||
}
|
||||
relationNode.push(params)
|
||||
}
|
||||
|
||||
// 如果是起始点
|
||||
// 如果是终点
|
||||
// if (item.start_node_id === id) {
|
||||
// targets.push(item)
|
||||
// } else if (item.end_node_id === id) {
|
||||
// ends.push(item)
|
||||
// }
|
||||
})
|
||||
return relationNode
|
||||
|
||||
}
|
||||
|
||||
// 获取节点信息
|
||||
const getNodeById = (id: string) => {
|
||||
if (!id) return
|
||||
return nodesMap.value.get(id)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const searchValue = ref('')
|
||||
|
||||
// 显示节点
|
||||
const onShowNodeEven = () => {
|
||||
|
||||
}
|
||||
// 隐藏节点
|
||||
const onHideNodeEven = () => {}
|
||||
|
||||
|
||||
|
||||
const state = {
|
||||
option,
|
||||
showData,
|
||||
chartConfig,
|
||||
relationColors,
|
||||
nodeColors,
|
||||
searchValue
|
||||
}
|
||||
|
||||
const api = {
|
||||
getData,
|
||||
handleData,
|
||||
// addRelationMap,
|
||||
addTypeMap,
|
||||
resetData,
|
||||
toggleSelectType,
|
||||
toggleRelation,
|
||||
getNodeById,
|
||||
getNodeRelationById,
|
||||
onShowNodeEven,
|
||||
onHideNodeEven
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
api
|
||||
}
|
||||
}
|
||||
390
src/views/knowledge/services/db copy.ts
Normal file
390
src/views/knowledge/services/db copy.ts
Normal file
@ -0,0 +1,390 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { createData } from './mockData'
|
||||
import { useChartHooks } from '@/views/knowledge/hooks/chart.ts'
|
||||
import { useLocationHooks } from '@/views/knowledge/hooks/location.ts'
|
||||
import { router } from "@/router/index.ts"
|
||||
import { useUtils } from '@/views/knowledge/hooks/utils.ts'
|
||||
|
||||
export const useOperateServices = () => {
|
||||
|
||||
const lightColors = ['#D4BDFB', '#9DD0FF', '#57DFB8', '#59E433', '#FFD1A6', '#E1DF62', '#F5B4B4', '#8FE4ED']
|
||||
const colors = ['#997AE7', '#7096E2', '#1FAA86', '#1BBB2B', '#F99D47', '#E4CF67', '#DF8D8D', '#4FC0D2']
|
||||
|
||||
|
||||
const { createColor } = useUtils({
|
||||
colors: colors
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const { option, createPointData, createLink} = useChartHooks()
|
||||
const {
|
||||
calculateTreeLayout,
|
||||
applyLocationsToOption,
|
||||
getLocationById,
|
||||
setLocations,
|
||||
clearLocations
|
||||
} = useLocationHooks()
|
||||
|
||||
|
||||
// 图表数据
|
||||
const graphData = ref<Record<string, any>>({
|
||||
roots: [],
|
||||
nodes: [],
|
||||
relationships: []
|
||||
})
|
||||
|
||||
// 节点映射
|
||||
const nodesMap = new Map()
|
||||
// 关系映射
|
||||
const relationsMap = new Map()
|
||||
// 分类映射
|
||||
const categoriesMap = new Map()
|
||||
|
||||
// 更新位置
|
||||
const updateLocation = (echartRef: Record<string, any>) => {
|
||||
console.log('echartRef', echartRef)
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
const getData = () => {
|
||||
const data = createData()
|
||||
const roots = getMainNodes(data)
|
||||
handleData(data)
|
||||
graphData.value = {
|
||||
roots: roots,
|
||||
nodes: Array.from(nodesMap.values()),
|
||||
relationships: Array.from(relationsMap.values())
|
||||
}
|
||||
console.log('graphData', graphData.value)
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
const updateData = () => {
|
||||
graphData.value = {
|
||||
...graphData.value,
|
||||
nodes: Array.from(nodesMap.values()),
|
||||
relationships: Array.from(relationsMap.values())
|
||||
}
|
||||
}
|
||||
|
||||
// 处理数据
|
||||
const handleData = (data: Record<string, any>) => {
|
||||
const { nodes, relationships } = data
|
||||
createCategoriesMap(nodes)
|
||||
createNodeMap(nodes)
|
||||
createRelationMap(relationships)
|
||||
relationships.forEach((item: Record<string, any>) => {
|
||||
const start = item.start_node_id
|
||||
const end = item.end_node_id
|
||||
if (nodesMap.has(start)) {
|
||||
let target = nodesMap.get(start)
|
||||
if (!target.childrenLinks) target.childrenLinks = []
|
||||
target.childrenLinks.push(relationsMap.get(item.id))
|
||||
}
|
||||
if (nodesMap.has(end)) {
|
||||
let target = nodesMap.get(end)
|
||||
if (!target.parentLinks) target.parentLinks = []
|
||||
target.parentLinks.push(relationsMap.get(item.id))
|
||||
}
|
||||
})
|
||||
return {
|
||||
nodes,
|
||||
relationships
|
||||
}
|
||||
}
|
||||
|
||||
// 可视的节点
|
||||
const showNodes = computed(() => {
|
||||
const { roots, nodes } = graphData.value
|
||||
const useNodes = nodes.filter((item: Record<string, any>) => {
|
||||
const isRoots = roots.find((rootNode: Record<string, any>) => rootNode.id === item.id)
|
||||
if (isRoots) return true
|
||||
else {
|
||||
return item.show
|
||||
}
|
||||
})
|
||||
return useNodes
|
||||
})
|
||||
|
||||
// 可视的关系
|
||||
const showRelations = computed(() => {
|
||||
const useNode = showNodes.value
|
||||
const useRelations: Record<string, any>[] = []
|
||||
useNode?.map((item: Record<string, any>) => {
|
||||
if (item.parentLinks) {
|
||||
useRelations.push(...item.parentLinks)
|
||||
}
|
||||
|
||||
})
|
||||
return useRelations
|
||||
})
|
||||
|
||||
// 可视的echart配置
|
||||
const showOptions = computed(() => {
|
||||
|
||||
const categories = Array.from(categoriesMap, ([key, value]) => {
|
||||
return {
|
||||
name: key,
|
||||
itemStyle: {
|
||||
color: value.color
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getCategoriesIndex = (category: string) => {
|
||||
const i = categories.findIndex((item: Record<string, any>) => {
|
||||
return item.name == category
|
||||
})
|
||||
return i
|
||||
}
|
||||
|
||||
|
||||
console.log('categories', categories)
|
||||
|
||||
// 应用固定布局
|
||||
const nodesWithPositions = calculateTreeLayout(
|
||||
showNodes.value,
|
||||
showRelations.value,
|
||||
graphData.value.roots.map((root: any) => root.id)
|
||||
)
|
||||
|
||||
const useNodes: Record<string, any>[] = []
|
||||
nodesWithPositions.forEach((node: Record<string, any>) => {
|
||||
const color = categoriesMap.get(node.label)?.color
|
||||
const nodeInfo = createPointData({
|
||||
...node,
|
||||
itemStyle: {
|
||||
color: color || '#333'
|
||||
},
|
||||
type: node.labels,
|
||||
name: node.id,
|
||||
})
|
||||
useNodes.push(nodeInfo)
|
||||
}
|
||||
|
||||
|
||||
const useRelations: Record<string, any>[] = []
|
||||
showRelations.value.forEach((relation: Record<string, any>) => {
|
||||
const link = createLink({
|
||||
...relation,
|
||||
parentIndex: getNodeIndex(relation.start_node_id, useNodes),
|
||||
index: getNodeIndex(relation.end_node_id, useNodes),
|
||||
type: relation.type,
|
||||
lineStyle: {
|
||||
width: 2 // 关系线条宽度
|
||||
},
|
||||
|
||||
})
|
||||
useRelations.push(link)
|
||||
})
|
||||
|
||||
const useOptions = {
|
||||
...option.value,
|
||||
series: [
|
||||
{
|
||||
...option.value.series[0],
|
||||
// 使用固定布局替代力导向布局
|
||||
layout: null, // 取消力导向布局
|
||||
roam: true, // 允许缩放和平移
|
||||
data: useNodes,
|
||||
links: useRelations
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 应用位置信息到选项配置
|
||||
return applyLocationsToOption(useOptions)
|
||||
})
|
||||
|
||||
// 获取节点索引
|
||||
const getNodeIndex = (id: string, source: Record<string, any> = showNodes.value) => {
|
||||
return source.findIndex((item: Record<string, any>) => item.id === id)
|
||||
}
|
||||
|
||||
// 找到主节点
|
||||
const getMainNodes = (data: Record<string, any>) => {
|
||||
const { nodes, relationships } = data
|
||||
let roots = JSON.parse(JSON.stringify(nodes))
|
||||
relationships.forEach((item: Record<string, any>) => {
|
||||
const end = item.end_node_id
|
||||
roots = roots.filter((item: Record<string, any>) => {
|
||||
return item.id !== end
|
||||
})
|
||||
})
|
||||
return roots
|
||||
}
|
||||
|
||||
// 构建nodemap
|
||||
const createNodeMap = (arr: Record<string, any>[]) => {
|
||||
nodesMap.clear()
|
||||
arr.forEach((item: Record<string, any>) => {
|
||||
const raw = {
|
||||
...item,
|
||||
show: false
|
||||
}
|
||||
if (nodesMap.has(item.id)) {
|
||||
// nodesMap.get(item.id).push(raw)
|
||||
} else {
|
||||
nodesMap.set(item.id, raw)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 构建分类categoriesMap
|
||||
const createCategoriesMap = (arr: Record<string, any>[]) => {
|
||||
categoriesMap.clear()
|
||||
arr.forEach((item: Record<string, any>) => {
|
||||
if (item.label) {
|
||||
if (!categoriesMap.has(item.label)) {
|
||||
categoriesMap.set(item.label, {
|
||||
color: createColor()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 构建关系map
|
||||
const createRelationMap = (arr: Record<string, any>[]) => {
|
||||
relationsMap.clear()
|
||||
arr.forEach((item: Record<string, any>) => {
|
||||
if (relationsMap.has(item.id)) {
|
||||
// relationsMap.get(item.id).push(item)
|
||||
} else {
|
||||
relationsMap.set(item.id, item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 根据id检索到对应的主节点
|
||||
const getMainNodeById = (id: string) => {
|
||||
const node = nodesMap.get(id)
|
||||
const roots = []
|
||||
const links = [...node.parentLinks || []]
|
||||
if (!links.length) return [node]
|
||||
while(links.length) {
|
||||
const link = links.shift()
|
||||
const start = link.start_node_id
|
||||
const preNode = nodesMap.get(start)
|
||||
console.log('preNode', preNode, nodesMap)
|
||||
if (preNode?.parentLinks?.length) {
|
||||
links.push(...preNode.parentLinks)
|
||||
} else {
|
||||
roots.push(preNode)
|
||||
}
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
// echart关系图节点点击
|
||||
const onPointClick = (id: string) => {
|
||||
const { roots } = graphData.value
|
||||
const isRoots = roots.find((rootNode: Record<string, any>) => rootNode.id === id)
|
||||
if (isRoots) {
|
||||
onToggleMainPoint(id)
|
||||
} else {
|
||||
onTogglePoint(id)
|
||||
}
|
||||
console.log('categoriesMap', categoriesMap)
|
||||
updateData()
|
||||
}
|
||||
|
||||
// 点击节点
|
||||
const onTogglePoint = (id: string) => {
|
||||
const node = nodesMap.get(id)
|
||||
if (node) {
|
||||
// node.show = !node.show
|
||||
const childrenLinks = node?.childrenLinks || []
|
||||
console.log('childrenLinks', childrenLinks)
|
||||
childrenLinks.forEach((item: Record<string, any>) => {
|
||||
const { end_node_id } = item
|
||||
const cnode = nodesMap.get(end_node_id)
|
||||
const type = !cnode.show
|
||||
setNodeTypeInLink(end_node_id, type)
|
||||
})
|
||||
// node.show = !node.show
|
||||
// setNodeTypeInLink(id, !node.show)
|
||||
}
|
||||
}
|
||||
|
||||
// 点击主节点,将当前节点下的所有子节点都显示或者隐藏出来
|
||||
const onToggleMainPoint = (id: string) => {
|
||||
const node = nodesMap.get(id)
|
||||
if (node) {
|
||||
setNodeTypeInLink(id, !node.show)
|
||||
}
|
||||
// 将当前节点下的所有子节点都显示或者隐藏出来
|
||||
|
||||
}
|
||||
|
||||
// 链式更新数据可视信息
|
||||
const setNodeTypeInLink = (id: string, type: boolean) => {
|
||||
const node = nodesMap.get(id)
|
||||
if (node) {
|
||||
// 如果布尔值一致,不处理
|
||||
// if (node.show != type) {
|
||||
node.show = type
|
||||
// }
|
||||
const childrenLinks = node?.childrenLinks || []
|
||||
childrenLinks.forEach((item: Record<string, any>) => {
|
||||
const { end_node_id } = item
|
||||
setNodeTypeInLink(end_node_id, type)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 关键字
|
||||
const searchValue = ref('')
|
||||
|
||||
// 选项过滤
|
||||
const optionsFilter = (input: string, option: any) => {
|
||||
const raw = option['data-raw']
|
||||
const id = raw.id
|
||||
const name = raw.properties.name
|
||||
return id.includes(input) || name.includes(input)
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSearch = (id: string) => {
|
||||
const roots = getMainNodeById(id)
|
||||
console.log('roots', roots)
|
||||
roots.forEach(item => {
|
||||
console.log('item', item)
|
||||
// if (item.show)
|
||||
setNodeTypeInLink(item.id, true)
|
||||
})
|
||||
updateData()
|
||||
}
|
||||
|
||||
const getQuery = () => {
|
||||
const { id } = router.currentRoute.value.query;
|
||||
if (id) {
|
||||
onSearch(id as string)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
state: {
|
||||
graphData,
|
||||
nodesMap,
|
||||
relationsMap,
|
||||
showOptions,
|
||||
searchValue,
|
||||
},
|
||||
api: {
|
||||
getData,
|
||||
onPointClick,
|
||||
optionsFilter,
|
||||
updateLocation,
|
||||
// onMainSelect,
|
||||
// onSelectPoint,
|
||||
onSearch,
|
||||
getQuery
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,16 +1,32 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { createData } from './mockData'
|
||||
import { useChartHooks } from '@/views/knowledge/hooks/chart.ts'
|
||||
import { router } from "@/router/index.ts";
|
||||
import { useLocationHooks } from '@/views/knowledge/hooks/location.ts'
|
||||
import { router } from "@/router/index.ts"
|
||||
import { useUtils } from '@/views/knowledge/hooks/utils.ts'
|
||||
|
||||
export const useOperateServices = () => {
|
||||
|
||||
const { createColor } = useUtils()
|
||||
const lightColors = ['#D4BDFB', '#9DD0FF', '#57DFB8', '#59E433', '#FFD1A6', '#E1DF62', '#F5B4B4', '#8FE4ED']
|
||||
const colors = ['#997AE7', '#7096E2', '#1FAA86', '#1BBB2B', '#F99D47', '#E4CF67', '#DF8D8D', '#4FC0D2']
|
||||
|
||||
|
||||
const { createColor } = useUtils({
|
||||
colors: colors
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const { option, createPointData, createLink} = useChartHooks()
|
||||
|
||||
const colors = ['#5470C6', '#91CC75', '#EE6666', '#73C0DE', '#3BA272', '#FC8452', '#9A60B4', '#EA7CC3']
|
||||
const {
|
||||
calculateTreeLayout,
|
||||
applyLocationsToOption,
|
||||
getLocationById,
|
||||
setLocations,
|
||||
clearLocations
|
||||
} = useLocationHooks()
|
||||
|
||||
|
||||
// 图表数据
|
||||
const graphData = ref<Record<string, any>>({
|
||||
@ -26,6 +42,11 @@ export const useOperateServices = () => {
|
||||
// 分类映射
|
||||
const categoriesMap = new Map()
|
||||
|
||||
// 更新位置
|
||||
const updateLocation = (echartRef: Record<string, any>) => {
|
||||
console.log('echartRef', echartRef)
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
const getData = () => {
|
||||
const data = createData()
|
||||
@ -112,31 +133,44 @@ export const useOperateServices = () => {
|
||||
}
|
||||
})
|
||||
|
||||
const getCategoriesIndex = (category: string) => {
|
||||
const i = categories.findIndex((item: Record<string, any>) => {
|
||||
return item.name == category
|
||||
})
|
||||
return i
|
||||
}
|
||||
|
||||
|
||||
|
||||
console.log('categories', categories)
|
||||
|
||||
// const link = ()
|
||||
// 应用固定布局
|
||||
const nodesWithPositions = calculateTreeLayout(
|
||||
showNodes.value,
|
||||
showRelations.value,
|
||||
graphData.value.roots.map((root: any) => root.id)
|
||||
)
|
||||
|
||||
console.log('nodesWithPositions', nodesWithPositions)
|
||||
|
||||
const useNodes: Record<string, any>[] = []
|
||||
showNodes.value.forEach((node: Record<string, any>) => {
|
||||
nodesWithPositions.forEach((node: Record<string, any>) => {
|
||||
const color = categoriesMap.get(node.label)?.color
|
||||
console.log('color', categoriesMap, color, node.label)
|
||||
const nodeInfo = createPointData({
|
||||
...node,
|
||||
const nodeInfo = {
|
||||
raw: node,
|
||||
id: node.id,
|
||||
name: node.id,
|
||||
itemStyle: {
|
||||
color: color || '#333'
|
||||
},
|
||||
|
||||
type: node.labels,
|
||||
name: node.id,
|
||||
|
||||
})
|
||||
type: node.label,
|
||||
x: node.x,
|
||||
y: node.y
|
||||
}
|
||||
// const nodeInfo = createPointData({
|
||||
// ...node,
|
||||
// itemStyle: {
|
||||
// color: color || '#333'
|
||||
// },
|
||||
// type: node.labels,
|
||||
// name: node.id,
|
||||
// // 直接在创建节点时设置位置信息
|
||||
// x: node.x,
|
||||
// y: node.y
|
||||
// })
|
||||
useNodes.push(nodeInfo)
|
||||
})
|
||||
|
||||
@ -161,12 +195,19 @@ export const useOperateServices = () => {
|
||||
series: [
|
||||
{
|
||||
...option.value.series[0],
|
||||
// categories: categoriesWithColor,
|
||||
// 使用固定布局替代力导向布局
|
||||
layout: null, // 取消力导向布局
|
||||
roam: true, // 允许缩放和平移
|
||||
data: useNodes,
|
||||
links: useRelations
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
console.log('useOptions', useOptions)
|
||||
|
||||
|
||||
// 位置信息已经在创建节点时设置,不需要再次应用
|
||||
return useOptions
|
||||
})
|
||||
|
||||
@ -351,6 +392,7 @@ export const useOperateServices = () => {
|
||||
getData,
|
||||
onPointClick,
|
||||
optionsFilter,
|
||||
updateLocation,
|
||||
// onMainSelect,
|
||||
// onSelectPoint,
|
||||
onSearch,
|
||||
|
||||
@ -1,21 +1,37 @@
|
||||
export const createData = () => {
|
||||
const size = 3 // 每层级节点个数
|
||||
const level = 3 // 层级
|
||||
|
||||
|
||||
const data: Record<string, any> = {
|
||||
nodes: [],
|
||||
relationships: []
|
||||
}
|
||||
|
||||
for(let i = 1; i < size; i++) {
|
||||
data.nodes.push(createNode(`${i}`))
|
||||
for(let j = 0; j< level; j++) {
|
||||
data.nodes.push(createNode(`${i}${j}`))
|
||||
data.relationships.push(createRelations(`${i}${j}`, size))
|
||||
for(let l = 0; l < level; l++) {
|
||||
data.nodes.push(createNode(`${i}${j}${l}`))
|
||||
data.relationships.push(createRelations(`${i}${j}${l}`, size))
|
||||
// 创建根节点
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const rootId = `${i}`;
|
||||
data.nodes.push(createNode(rootId));
|
||||
|
||||
// 为每个根节点创建不同深度和宽度的子树
|
||||
const childCount = Math.floor(Math.random() * 3) + 2; // 2-4个子节点
|
||||
for (let j = 0; j < childCount; j++) {
|
||||
const childId = `${i}${j}`;
|
||||
data.nodes.push(createNode(childId));
|
||||
data.relationships.push(createRelations(childId, rootId));
|
||||
|
||||
// 创建第三层节点(不同数量)
|
||||
const grandchildCount = Math.floor(Math.random() * 4) + 2; // 2-5个孙节点
|
||||
for (let k = 0; k < grandchildCount; k++) {
|
||||
const grandchildId = `${i}${j}${k}`;
|
||||
data.nodes.push(createNode(grandchildId));
|
||||
data.relationships.push(createRelations(grandchildId, childId));
|
||||
|
||||
// 随机为一些孙节点创建第四层节点
|
||||
if (Math.random() > 0.5) {
|
||||
const greatGrandchildCount = Math.floor(Math.random() * 3) + 1; // 1-3个曾孙节点
|
||||
for (let l = 0; l < greatGrandchildCount; l++) {
|
||||
const greatGrandchildId = `${i}${j}${k}${l}`;
|
||||
data.nodes.push(createNode(greatGrandchildId));
|
||||
data.relationships.push(createRelations(greatGrandchildId, grandchildId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -33,12 +49,9 @@ const createNode = (index: number | string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const createRelations = (index: string, max: number) => {
|
||||
const len = index.length
|
||||
const start = index.slice(0, len - 1)
|
||||
const end = index
|
||||
const createRelations = (end: string, start: string) => {
|
||||
const params = {
|
||||
id: `r${index}`,
|
||||
id: `r${end}`,
|
||||
"type": "组成", // 关系类型
|
||||
"start_node_id": start, // 起始节点id
|
||||
"end_node_id": end,
|
||||
|
||||
@ -13,9 +13,8 @@ function pathResolve(dir: string) {
|
||||
return resolve(__dirname, dir)
|
||||
}
|
||||
|
||||
const URL_USE = ''
|
||||
const URL_USE = 'http://ip:8088'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
server: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user