From 726a9218e0144da36f433a4438c40aac8a0b419b Mon Sep 17 00:00:00 2001 From: Timi Date: Fri, 11 Sep 2026 11:19:28 +0800 Subject: [PATCH] add comment --- .../component/CustomerInviteRelationChart.vue | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/pages/customer/component/CustomerInviteRelationChart.vue b/src/pages/customer/component/CustomerInviteRelationChart.vue index 61343b3..bac24d9 100644 --- a/src/pages/customer/component/CustomerInviteRelationChart.vue +++ b/src/pages/customer/component/CustomerInviteRelationChart.vue @@ -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(); const chartOption = computed(() => 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(); 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, depthMap: Map, visiting: Set): 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;