add comment
This commit is contained in:
@@ -12,6 +12,8 @@ import type { Customer } from "@/types/Customer";
|
||||
|
||||
use([GraphChart, TooltipComponent, CanvasRenderer]);
|
||||
|
||||
// ---------- 图表数据类型 ----------
|
||||
|
||||
type RelationNode = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -107,20 +109,45 @@ const props = defineProps<{
|
||||
customers: Customer[];
|
||||
}>();
|
||||
|
||||
// ---------- 图表状态 ----------
|
||||
|
||||
const chartElement = ref<HTMLElement>();
|
||||
const chartOption = computed<EChartsOption>(() => buildChartOption(props.customers));
|
||||
|
||||
// ---------- 图表配置 ----------
|
||||
|
||||
// 节点使用圆角矩形路径,尺寸需要与边界计算中的 nodeSize 保持一致
|
||||
const nodeSymbol = "path://M 6 0 H 50 A 6 6 0 0 1 56 6 V 18 A 6 6 0 0 1 50 24 H 6 A 6 6 0 0 1 0 18 V 6 A 6 6 0 0 1 6 0 Z";
|
||||
|
||||
// 节点尺寸,单位为 ECharts 图表坐标单位
|
||||
const nodeSize: [number, number] = [56, 24];
|
||||
|
||||
// 节点圆角半径,用于计算连线与节点边界的交点
|
||||
const nodeCornerRadius = 6;
|
||||
|
||||
// 箭头占用的节点边缘长度,用于避免连线覆盖目标节点
|
||||
const relationArrowLength = 4;
|
||||
|
||||
// 箭头尖端沿连线方向的额外偏移量,用于修正 ECharts 箭头定位
|
||||
const relationArrowTipOffset = 2;
|
||||
|
||||
// 节点与连线之间的额外间距,单位为 ECharts 图表坐标单位
|
||||
const relationEdgeGap = 0;
|
||||
|
||||
// 节点标签的基础字号,缩放时会在 labelLayout 中按图表缩放比例调整
|
||||
const nodeLabelFontSize = 10;
|
||||
|
||||
// 节点标签可用宽度,超出后由 ECharts 按 truncate 规则截断
|
||||
const nodeLabelWidth = 48;
|
||||
|
||||
// 聚焦节点时的平移动画时长,单位为毫秒
|
||||
const centerTransitionDuration = 300;
|
||||
|
||||
let chart: ECharts | undefined;
|
||||
let centerAnimationFrame: number | undefined;
|
||||
|
||||
// ---------- 图表缩放与关系图内部模型 ----------
|
||||
|
||||
function getChartZoom(): number {
|
||||
const seriesModel = getRelationSeriesModel();
|
||||
const modelZoom = seriesModel?.get("zoom");
|
||||
@@ -143,6 +170,8 @@ function getNodeGraphicScale(node: RelationGraphNode): number {
|
||||
return typeof scale === "number" && 0 < scale ? scale : 1;
|
||||
}
|
||||
|
||||
// ---------- 关系边界与箭头 ----------
|
||||
|
||||
function getRoundedRectBoundary(
|
||||
directionX: number,
|
||||
directionY: number,
|
||||
@@ -276,7 +305,7 @@ function adjustRelationEdges(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
// 保留 ECharts 写入的原始中心点,避免下一次布局继续累积缩短距离。
|
||||
// 保留 ECharts 写入的原始中心点,避免下一次布局继续累积缩短距离
|
||||
sourcePoint[0] = nextSourceX;
|
||||
sourcePoint[1] = nextSourceY;
|
||||
targetPoint[0] = nextTargetX;
|
||||
@@ -290,6 +319,8 @@ function getRelationGraph(): RelationGraph | undefined {
|
||||
return getRelationSeriesModel()?.getGraph();
|
||||
}
|
||||
|
||||
// ---------- 节点交互 ----------
|
||||
|
||||
function focusCustomer(customer: Customer): void {
|
||||
const id = customer.id;
|
||||
if (!id || !chart) {
|
||||
@@ -374,6 +405,8 @@ function clearFocus(): void {
|
||||
chart.dispatchAction({ type: "unfocusNodeAdjacency", seriesIndex: 0 }, { silent: true });
|
||||
}
|
||||
|
||||
// ---------- 图表数据 ----------
|
||||
|
||||
function buildChartOption(list: Customer[]): EChartsOption {
|
||||
const customerMap = new Map<string, Customer>();
|
||||
for (const customer of list) {
|
||||
@@ -403,6 +436,7 @@ function buildChartOption(list: Customer[]): EChartsOption {
|
||||
nodeList.push({
|
||||
id,
|
||||
name: getCustomerName(customer),
|
||||
// 横向按邀请层级排列,纵向按同层节点均匀分布
|
||||
x: depth * 220 + 120,
|
||||
y: (index - levelOffset) * 96 + 1200,
|
||||
draggable: true,
|
||||
@@ -439,6 +473,7 @@ function buildChartOption(list: Customer[]): EChartsOption {
|
||||
}
|
||||
|
||||
return {
|
||||
// 节点和连线的初始动画时长,单位为毫秒
|
||||
animationDuration: 400,
|
||||
tooltip: {
|
||||
formatter: params => {
|
||||
@@ -462,19 +497,24 @@ function buildChartOption(list: Customer[]): EChartsOption {
|
||||
{
|
||||
type: "graph",
|
||||
layout: "force",
|
||||
// 允许拖拽节点和缩放画布,关系较多时仍可查看完整图谱
|
||||
roam: true,
|
||||
roamTrigger: "global",
|
||||
draggable: true,
|
||||
nodeScaleRatio: 0,
|
||||
// 限制画布缩放范围,避免节点过小或超出可视区域
|
||||
scaleLimit: {
|
||||
min: .5,
|
||||
max: 4
|
||||
},
|
||||
focusNodeAdjacency: true,
|
||||
force: {
|
||||
// 使用接口计算出的节点坐标作为初始布局,避免首次渲染随机跳动
|
||||
initLayout: "none",
|
||||
// repulsion 控制节点间排斥力,edgeLength 控制连线期望长度
|
||||
repulsion: 1000,
|
||||
edgeLength: 640,
|
||||
// gravity 控制节点向中心聚拢的力度,layoutAnimation 保持布局变化平滑
|
||||
gravity: .01,
|
||||
layoutAnimation: true
|
||||
},
|
||||
@@ -508,6 +548,7 @@ function buildChartOption(list: Customer[]): EChartsOption {
|
||||
}
|
||||
},
|
||||
stateAnimation: {
|
||||
// 节点状态切换动画时长,单位为毫秒
|
||||
duration: 300,
|
||||
easing: "cubicOut"
|
||||
}
|
||||
@@ -516,6 +557,7 @@ function buildChartOption(list: Customer[]): EChartsOption {
|
||||
};
|
||||
}
|
||||
|
||||
// 根据邀请人链路计算节点层级,层级越深表示被邀请关系越下游
|
||||
function getCustomerDepth(customer: Customer, customerMap: Map<string, Customer>, depthMap: Map<string, number>, visiting: Set<string>): number {
|
||||
const id = customer.id;
|
||||
if (!id) {
|
||||
@@ -546,6 +588,7 @@ function getCustomerName(customer: Customer): string {
|
||||
return customer.name || customer.code || customer.id || "未命名客户";
|
||||
}
|
||||
|
||||
// 邀请人数越多,节点颜色越醒目;阈值需与关系页图例保持一致
|
||||
function getNodeColor(customer: Customer): string {
|
||||
const invitedCount = customer.invitedCount ?? 0;
|
||||
if (10 <= invitedCount) {
|
||||
@@ -563,6 +606,8 @@ function getNodeColor(customer: Customer): string {
|
||||
return "#94a3b8";
|
||||
}
|
||||
|
||||
// ---------- 图表渲染与生命周期 ----------
|
||||
|
||||
function renderChart(): void {
|
||||
if (!chartElement.value || !props.customers.length) {
|
||||
return;
|
||||
@@ -624,9 +669,9 @@ defineExpose({
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
overscroll-behavior: none;
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
|
||||
Reference in New Issue
Block a user