add comment

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