|
@@ -0,0 +1,287 @@
|
|
|
|
|
+import type { EChartsOption } from 'echarts';
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 节点分类定义 ====================
|
|
|
|
|
+
|
|
|
|
|
+export interface CategoryDef {
|
|
|
|
|
+ name: string; // 分类名称
|
|
|
|
|
+ color: string; // 节点颜色
|
|
|
|
|
+ symbol: string; // ECharts 图形:circle / rect / diamond / roundRect
|
|
|
|
|
+ symbolSize: number; // 节点大小
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 4 类节点:省局、执法处、煤矿、测风点 */
|
|
|
|
|
+export const categories: CategoryDef[] = [
|
|
|
|
|
+ { name: '省局', color: '#0960bd', symbol: 'diamond', symbolSize: 50 },
|
|
|
|
|
+ { name: '执法处', color: '#1890ff', symbol: 'roundRect', symbolSize: 40 },
|
|
|
|
|
+ { name: '煤矿', color: '#52c41a', symbol: 'circle', symbolSize: 35 },
|
|
|
|
|
+ { name: '测风点', color: '#fa8c16', symbol: 'pin', symbolSize: 25 },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 状态颜色映射 ====================
|
|
|
|
|
+
|
|
|
|
|
+export const statusColorMap: Record<string, string> = {
|
|
|
|
|
+ normal: '#52c41a', // 正常
|
|
|
|
|
+ abnormal: '#fa8c16', // 异常
|
|
|
|
|
+ offline: '#999999', // 离线
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 层级布局参数(树形但本质是图) ====================
|
|
|
|
|
+
|
|
|
|
|
+/** 每个层级在画布高度中的百分比 y 坐标(从上到下) */
|
|
|
|
|
+export const levelY = [8, 30, 55, 85]; // 省局、执法处、煤矿、测风点
|
|
|
|
|
+
|
|
|
|
|
+/** 每类节点用于排序的权重字段(值越大越靠左) */
|
|
|
|
|
+export const weightKey: Record<number, string> = {
|
|
|
|
|
+ 0: 'childCount', // 省局
|
|
|
|
|
+ 1: 'childCount', // 执法处
|
|
|
|
|
+ 2: 'childCount', // 煤矿(按管辖测风点数排序)
|
|
|
|
|
+ 3: 'windVolume', // 测风点
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/** 节点横向间距基数(px),实际 = basePadding / 该层节点数 */
|
|
|
|
|
+export const basePadding = 800;
|
|
|
|
|
+
|
|
|
|
|
+/** 画布宽高占位(chart 100% 容器,此处设参考值) */
|
|
|
|
|
+export const LAYOUT_WIDTH = 1200;
|
|
|
|
|
+export const LAYOUT_HEIGHT = 700;
|
|
|
|
|
+
|
|
|
|
|
+// ==================== ECharts graph 基础配置 ====================
|
|
|
|
|
+
|
|
|
|
|
+export function createGraphOption(): EChartsOption {
|
|
|
|
|
+ return {
|
|
|
|
|
+ tooltip: { trigger: 'item', formatter: '{b}' },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'graph',
|
|
|
|
|
+ layout: 'none', // 固定位置,由 applyHierarchicalLayout 分配坐标
|
|
|
|
|
+ roam: true,
|
|
|
|
|
+ draggable: true, // 始终可拖,编辑模式只影响 roam
|
|
|
|
|
+ categories: categories.map((c) => ({ name: c.name, itemStyle: { color: c.color } })),
|
|
|
|
|
+ edgeSymbol: ['none', 'arrow'],
|
|
|
|
|
+ edgeSymbolSize: [0, 10],
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ position: 'bottom',
|
|
|
|
|
+ fontSize: 11,
|
|
|
|
|
+ formatter: (p: any) => p.name || '',
|
|
|
|
|
+ },
|
|
|
|
|
+ lineStyle: { color: 'source', curveness: 0.3, width: 2, opacity: 0.7 },
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ focus: 'adjacency',
|
|
|
|
|
+ lineStyle: { width: 3, opacity: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ nodes: [],
|
|
|
|
|
+ links: [],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 节点详情字段 ====================
|
|
|
|
|
+
|
|
|
|
|
+export interface DetailField {
|
|
|
|
|
+ label: string;
|
|
|
|
|
+ key: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export const nodeDetailFields: Record<string, DetailField[]> = {
|
|
|
|
|
+ 省局: [
|
|
|
|
|
+ { label: '机构名称', key: 'name' },
|
|
|
|
|
+ { label: '管辖执法处数', key: 'childCount' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 执法处: [
|
|
|
|
|
+ { label: '名称', key: 'name' },
|
|
|
|
|
+ { label: '管辖煤矿数', key: 'childCount' },
|
|
|
|
|
+ { label: '在线率', key: 'onlineRate' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 煤矿: [
|
|
|
|
|
+ { label: '煤矿名称', key: 'name' },
|
|
|
|
|
+ { label: '测风点数', key: 'childCount' },
|
|
|
|
|
+ { label: '在线/应接', key: 'onlineRatio' },
|
|
|
|
|
+ { label: '风量异常', key: 'windAbnormal' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 测风点: [
|
|
|
|
|
+ { label: '测风点名称', key: 'name' },
|
|
|
|
|
+ { label: '实时风速', key: 'windSpeed' },
|
|
|
|
|
+ { label: '实时风量', key: 'windVolume' },
|
|
|
|
|
+ { label: '状态', key: 'status' },
|
|
|
|
|
+ ],
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 多对多 + 一对多 关系类型 ====================
|
|
|
|
|
+
|
|
|
|
|
+/** 测风点分类索引(一对多的子端) */
|
|
|
|
|
+export const CHILD_CATEGORY = 3;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 判断目标分类是否为一对多关系的"子端"
|
|
|
|
|
+ * 一对多:煤矿(2)→测风点(3),一个测风点只能属于一个煤矿
|
|
|
|
|
+ * 多对多:执法处(1)↔煤矿(2),一个执法处管多个矿,一个矿可被多个执法处管
|
|
|
|
|
+ */
|
|
|
|
|
+export function isOneToManyChild(category: number): boolean {
|
|
|
|
|
+ return category === CHILD_CATEGORY;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 拖拽绑定时判定"吸附"的距离阈值(像素) */
|
|
|
|
|
+export const BIND_THRESHOLD = 35;
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 模拟拓扑数据 ====================
|
|
|
|
|
+
|
|
|
|
|
+export interface TopoNodeData {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ category: number; // categories 索引
|
|
|
|
|
+ status?: string; // normal / abnormal / offline
|
|
|
|
|
+ windSpeed?: number;
|
|
|
|
|
+ windVolume?: number;
|
|
|
|
|
+ childCount?: number;
|
|
|
|
|
+ onlineRate?: string;
|
|
|
|
|
+ onlineRatio?: string;
|
|
|
|
|
+ windAbnormal?: number;
|
|
|
|
|
+ [key: string]: any;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface TopoLinkData {
|
|
|
|
|
+ source: string;
|
|
|
|
|
+ target: string;
|
|
|
|
|
+ label?: string;
|
|
|
|
|
+ id?: string; // 关联关系数据库主键,用于解绑
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export interface TopologyData {
|
|
|
|
|
+ nodes: TopoNodeData[];
|
|
|
|
|
+ links: TopoLinkData[];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ==================== API 响应类型 ====================
|
|
|
|
|
+
|
|
|
|
|
+/** API -> MineArea 节点 */
|
|
|
|
|
+export interface MineAreaNode {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ mineCode: string;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ level: number; // 1=省局 2=执法处 3=煤矿 4=测风点
|
|
|
|
|
+ airVolume: number;
|
|
|
|
|
+ windrectId: string;
|
|
|
|
|
+ createTime?: string;
|
|
|
|
|
+ updateTime?: string;
|
|
|
|
|
+ [key: string]: any;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** API -> MineArea 关联关系 */
|
|
|
|
|
+export interface MineAreaRelation {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ mineCode: string;
|
|
|
|
|
+ parentId: string;
|
|
|
|
|
+ childId: string;
|
|
|
|
|
+ createTime?: string;
|
|
|
|
|
+ updateTime?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** API -> getMineAreaRelation 返回值 */
|
|
|
|
|
+export interface MineAreaApiResponse {
|
|
|
|
|
+ mineAreaList: MineAreaNode[];
|
|
|
|
|
+ mineAreaRelationList: MineAreaRelation[];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** level(1-4) 转 category(0-3) */
|
|
|
|
|
+const levelToCategory: Record<number, number> = { 1: 0, 2: 1, 3: 2, 4: 3 };
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 将 API 响应转换为 TopologyData
|
|
|
|
|
+ * mineAreaList -> nodes, mineAreaRelationList -> links
|
|
|
|
|
+ */
|
|
|
|
|
+export function transformToTopologyData(apiResp: MineAreaApiResponse): TopologyData {
|
|
|
|
|
+ const { mineAreaList = [], mineAreaRelationList = [] } = apiResp;
|
|
|
|
|
+
|
|
|
|
|
+ // 计算每个节点的子节点数
|
|
|
|
|
+ const childCountMap: Record<string, number> = {};
|
|
|
|
|
+ for (const rel of mineAreaRelationList) {
|
|
|
|
|
+ childCountMap[rel.parentId] = (childCountMap[rel.parentId] || 0) + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const nodes: TopoNodeData[] = mineAreaList.map((item) => ({
|
|
|
|
|
+ id: item.id,
|
|
|
|
|
+ name: item.name,
|
|
|
|
|
+ category: levelToCategory[item.level] ?? 0,
|
|
|
|
|
+ status: 'normal',
|
|
|
|
|
+ windVolume: item.airVolume,
|
|
|
|
|
+ childCount: childCountMap[item.id] || 0,
|
|
|
|
|
+ mineCode: item.mineCode,
|
|
|
|
|
+ windrectId: item.windrectId,
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ const links: TopoLinkData[] = mineAreaRelationList.map((rel) => ({
|
|
|
|
|
+ source: rel.parentId,
|
|
|
|
|
+ target: rel.childId,
|
|
|
|
|
+ id: rel.id,
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ return { nodes, links };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 模拟 API 响应数据(后端未部署时回退) ====================
|
|
|
|
|
+
|
|
|
|
|
+/** 模拟 getMineAreaRelation 的 API 响应 */
|
|
|
|
|
+export const mockMineAreaApiResponse: MineAreaApiResponse = {
|
|
|
|
|
+ mineAreaList: [
|
|
|
|
|
+ // === 省局 level=1 ===
|
|
|
|
|
+ { id: 'province', mineCode: 'M000', name: '陕西省煤矿安全监察局', level: 1, airVolume: 0, windrectId: '' },
|
|
|
|
|
+ // === 执法处 level=2 ===
|
|
|
|
|
+ { id: 'enf-1', mineCode: 'M001', name: '榆林执法处', level: 2, airVolume: 0, windrectId: '' },
|
|
|
|
|
+ { id: 'enf-2', mineCode: 'M002', name: '延安执法处', level: 2, airVolume: 0, windrectId: '' },
|
|
|
|
|
+ { id: 'enf-3', mineCode: 'M003', name: '铜川执法处', level: 2, airVolume: 0, windrectId: '' },
|
|
|
|
|
+ { id: 'enf-4', mineCode: 'M004', name: '渭南执法处', level: 2, airVolume: 0, windrectId: '' },
|
|
|
|
|
+ { id: 'enf-5', mineCode: 'M005', name: '咸阳执法处', level: 2, airVolume: 0, windrectId: '' },
|
|
|
|
|
+ // === 煤矿 level=3 ===
|
|
|
|
|
+ { id: 'mine-1', mineCode: 'M101', name: '大柳塔煤矿', level: 3, airVolume: 0, windrectId: 'W001' },
|
|
|
|
|
+ { id: 'mine-2', mineCode: 'M102', name: '活鸡兔煤矿', level: 3, airVolume: 0, windrectId: 'W002' },
|
|
|
|
|
+ { id: 'mine-3', mineCode: 'M103', name: '哈拉沟煤矿', level: 3, airVolume: 0, windrectId: 'W003' },
|
|
|
|
|
+ { id: 'mine-4', mineCode: 'M104', name: '榆家梁煤矿', level: 3, airVolume: 0, windrectId: 'W004' },
|
|
|
|
|
+ { id: 'mine-5', mineCode: 'M105', name: '三道沟煤矿', level: 3, airVolume: 0, windrectId: 'W005' },
|
|
|
|
|
+ { id: 'mine-6', mineCode: 'M106', name: '黄陵一号矿', level: 3, airVolume: 0, windrectId: 'W006' },
|
|
|
|
|
+ { id: 'mine-7', mineCode: 'M107', name: '红柳林煤矿', level: 3, airVolume: 0, windrectId: 'W007' },
|
|
|
|
|
+ { id: 'mine-8', mineCode: 'M108', name: '凉水井煤矿', level: 3, airVolume: 0, windrectId: 'W008' },
|
|
|
|
|
+ // === 测风点 level=4 ===
|
|
|
|
|
+ { id: 'point-1', mineCode: 'M001', name: '北翼回风巷', level: 4, airVolume: 1800, windrectId: 'W101' },
|
|
|
|
|
+ { id: 'point-2', mineCode: 'M001', name: '南翼进风巷', level: 4, airVolume: 2100, windrectId: 'W102' },
|
|
|
|
|
+ { id: 'point-3', mineCode: 'M001', name: '三采区回风巷', level: 4, airVolume: 950, windrectId: 'W103' },
|
|
|
|
|
+ { id: 'point-4', mineCode: 'M001', name: '主斜井', level: 4, airVolume: 3200, windrectId: 'W104' },
|
|
|
|
|
+ { id: 'point-5', mineCode: 'M002', name: '副立井', level: 4, airVolume: 2800, windrectId: 'W105' },
|
|
|
|
|
+ { id: 'point-6', mineCode: 'M002', name: '二盘区回风巷', level: 4, airVolume: 1350, windrectId: 'W106' },
|
|
|
|
|
+ { id: 'point-7', mineCode: 'M002', name: '101工作面', level: 4, airVolume: 720, windrectId: 'W107' },
|
|
|
|
|
+ { id: 'point-8', mineCode: 'M003', name: '102工作面', level: 4, airVolume: 880, windrectId: 'W108' },
|
|
|
|
|
+ { id: 'point-9', mineCode: 'M003', name: '东翼回风大巷', level: 4, airVolume: 1600, windrectId: 'W109' },
|
|
|
|
|
+ { id: 'point-10', mineCode: 'M004', name: '西翼进风大巷', level: 4, airVolume: 1500, windrectId: 'W110' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ mineAreaRelationList: [
|
|
|
|
|
+ // 省局→执法处
|
|
|
|
|
+ { id: 'rel-1', mineCode: 'M000', parentId: 'province', childId: 'enf-1' },
|
|
|
|
|
+ { id: 'rel-2', mineCode: 'M000', parentId: 'province', childId: 'enf-2' },
|
|
|
|
|
+ { id: 'rel-3', mineCode: 'M000', parentId: 'province', childId: 'enf-3' },
|
|
|
|
|
+ { id: 'rel-4', mineCode: 'M000', parentId: 'province', childId: 'enf-4' },
|
|
|
|
|
+ { id: 'rel-5', mineCode: 'M000', parentId: 'province', childId: 'enf-5' },
|
|
|
|
|
+ // 执法处→煤矿
|
|
|
|
|
+ { id: 'rel-6', mineCode: 'M001', parentId: 'enf-1', childId: 'mine-1' },
|
|
|
|
|
+ { id: 'rel-7', mineCode: 'M001', parentId: 'enf-1', childId: 'mine-2' },
|
|
|
|
|
+ { id: 'rel-8', mineCode: 'M001', parentId: 'enf-1', childId: 'mine-3' },
|
|
|
|
|
+ { id: 'rel-9', mineCode: 'M002', parentId: 'enf-2', childId: 'mine-4' },
|
|
|
|
|
+ { id: 'rel-10', mineCode: 'M002', parentId: 'enf-2', childId: 'mine-5' },
|
|
|
|
|
+ { id: 'rel-11', mineCode: 'M003', parentId: 'enf-3', childId: 'mine-6' },
|
|
|
|
|
+ { id: 'rel-12', mineCode: 'M003', parentId: 'enf-3', childId: 'mine-7' },
|
|
|
|
|
+ { id: 'rel-13', mineCode: 'M004', parentId: 'enf-4', childId: 'mine-1' },
|
|
|
|
|
+ { id: 'rel-14', mineCode: 'M005', parentId: 'enf-5', childId: 'mine-7' },
|
|
|
|
|
+ { id: 'rel-15', mineCode: 'M005', parentId: 'enf-5', childId: 'mine-8' },
|
|
|
|
|
+ // 煤矿→测风点
|
|
|
|
|
+ { id: 'rel-16', mineCode: 'M101', parentId: 'mine-1', childId: 'point-1' },
|
|
|
|
|
+ { id: 'rel-17', mineCode: 'M101', parentId: 'mine-1', childId: 'point-2' },
|
|
|
|
|
+ { id: 'rel-18', mineCode: 'M101', parentId: 'mine-1', childId: 'point-3' },
|
|
|
|
|
+ { id: 'rel-19', mineCode: 'M101', parentId: 'mine-1', childId: 'point-4' },
|
|
|
|
|
+ { id: 'rel-20', mineCode: 'M102', parentId: 'mine-2', childId: 'point-5' },
|
|
|
|
|
+ { id: 'rel-21', mineCode: 'M102', parentId: 'mine-2', childId: 'point-6' },
|
|
|
|
|
+ { id: 'rel-22', mineCode: 'M102', parentId: 'mine-2', childId: 'point-7' },
|
|
|
|
|
+ { id: 'rel-23', mineCode: 'M103', parentId: 'mine-3', childId: 'point-8' },
|
|
|
|
|
+ { id: 'rel-24', mineCode: 'M103', parentId: 'mine-3', childId: 'point-9' },
|
|
|
|
|
+ { id: 'rel-25', mineCode: 'M104', parentId: 'mine-4', childId: 'point-10' },
|
|
|
|
|
+ ],
|
|
|
|
|
+};
|