add comment
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user