From ad6ec45e1d516002672220676294339b5ea6fc2c Mon Sep 17 00:00:00 2001 From: wujingjing <gersonwu@qq.com> Date: 星期三, 30 十月 2024 16:37:15 +0800 Subject: [PATCH] 指标图谱 --- customer_list/common/static/fonts/ywiconfont/iconfont.ttf | 0 src/directive/customDirective.ts | 1 src/views/project/yw/systemManage/agentGraph/AgentGraph.vue | 312 ++++++++++++++++++++++++++++ src/api/login/UserMenuData.ts | 17 + src/views/project/yw/systemManage/metricAgentMgr/MetricAgentMgr.vue | 37 +- src/views/project/yw/systemManage/agentGraph/shape.ts | 58 ++++ customer_list/common/static/fonts/ywiconfont/iconfont.woff2 | 0 customer_list/common/static/fonts/ywiconfont/iconfont.woff | 0 src/views/project/yw/systemManage/agentGraph/routeMap.ts | 0 /dev/null | 168 --------------- customer_list/yw/static/config/route.js | 7 src/views/project/yw/systemManage/agentGraph/testData.ts | 0 customer_list/common/static/fonts/ywiconfont/iconfont.css | 18 + 13 files changed, 420 insertions(+), 198 deletions(-) diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.css b/customer_list/common/static/fonts/ywiconfont/iconfont.css index 1954a75..05712ae 100644 --- a/customer_list/common/static/fonts/ywiconfont/iconfont.css +++ b/customer_list/common/static/fonts/ywiconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "ywifont"; /* Project id 4655417 */ - src: url('iconfont.woff2?t=1730254934187') format('woff2'), - url('iconfont.woff?t=1730254934187') format('woff'), - url('iconfont.ttf?t=1730254934187') format('truetype'); + src: url('iconfont.woff2?t=1730272121109') format('woff2'), + url('iconfont.woff?t=1730272121109') format('woff'), + url('iconfont.ttf?t=1730272121109') format('truetype'); } .ywifont { @@ -13,6 +13,18 @@ -moz-osx-font-smoothing: grayscale; } +.ywicon-fangda:before { + content: "\e81d"; +} + +.ywicon-suoxiao:before { + content: "\e628"; +} + +.ywicon-sousuo:before { + content: "\e60a"; +} + .ywicon-zishiying:before { content: "\e657"; } diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.ttf b/customer_list/common/static/fonts/ywiconfont/iconfont.ttf index 058657a..88b5f80 100644 --- a/customer_list/common/static/fonts/ywiconfont/iconfont.ttf +++ b/customer_list/common/static/fonts/ywiconfont/iconfont.ttf Binary files differ diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.woff b/customer_list/common/static/fonts/ywiconfont/iconfont.woff index e0a50c7..d052d59 100644 --- a/customer_list/common/static/fonts/ywiconfont/iconfont.woff +++ b/customer_list/common/static/fonts/ywiconfont/iconfont.woff Binary files differ diff --git a/customer_list/common/static/fonts/ywiconfont/iconfont.woff2 b/customer_list/common/static/fonts/ywiconfont/iconfont.woff2 index 4d4569f..05990de 100644 --- a/customer_list/common/static/fonts/ywiconfont/iconfont.woff2 +++ b/customer_list/common/static/fonts/ywiconfont/iconfont.woff2 Binary files differ diff --git a/customer_list/yw/static/config/route.js b/customer_list/yw/static/config/route.js index 27aa503..8670816 100644 --- a/customer_list/yw/static/config/route.js +++ b/customer_list/yw/static/config/route.js @@ -1,5 +1,12 @@ window.route = [ { + name: 'AgentGraph', + isKeepAlive: true, + isAffix: false, + path: '/agent/graph', + component: '/project/yw/systemManage/agentGraph/AgentGraph.vue', + }, + { name: 'ChatLog', isKeepAlive: true, isAffix: false, diff --git a/src/api/login/UserMenuData.ts b/src/api/login/UserMenuData.ts index 63018fa..b037bdc 100644 --- a/src/api/login/UserMenuData.ts +++ b/src/api/login/UserMenuData.ts @@ -30,7 +30,22 @@ SortCode: 2, Description: '', }, - + { + Children: [], + ID: '1', + ParentID: '3', + Type: 2, + Name: '鎸囨爣鍥捐氨', + Path: '/agent/graph', + Permission: '', + Icon: 'ywifont ywicon-zhibiao', + IsIframe: false, + OutLink: '', + IsHide: true, + Weight: 0, + SortCode: 2, + Description: '', + }, { Children: [], ID: '1', diff --git a/src/directive/customDirective.ts b/src/directive/customDirective.ts index ca1f987..525793c 100644 --- a/src/directive/customDirective.ts +++ b/src/directive/customDirective.ts @@ -210,5 +210,6 @@ mounted: (el) => { el.querySelector('input.el-input__inner')?.focus(); }, + }); }; \ No newline at end of file diff --git a/src/views/project/yw/systemManage/agentGraph/AgentGraph.vue b/src/views/project/yw/systemManage/agentGraph/AgentGraph.vue new file mode 100644 index 0000000..d407b53 --- /dev/null +++ b/src/views/project/yw/systemManage/agentGraph/AgentGraph.vue @@ -0,0 +1,312 @@ +<template> + <div v-loading="firstLoading" class="h-full bg-white relative"> + <!-- <div class="absolute right-4 top-4 z-10"> + + + <span class="ywifont ywicon-zishiying cursor-pointer" title="鑷�傚簲" @click="selfAdaptClick"></span> + </div> --> + <div class="h-full" ref="graphRef"></div> + + <div + class="absolute left-4 top-4 z-10 w-16 divide-y-[1.5px] divide-solid divide-gray-100 rounded-lg" + style="box-shadow: 0 0 15px #dbdee6" + > + <div + class="flex-column content-center items-center border-x-0 py-2 hover:bg-gray-200 active:bg-gray-300 cursor-pointer" + @click="toolBarItemClick(item)" + v-for="item in toolBarFunList" + > + <span class="ywifont !text-[20px] mb-1 p-1.5" :class="[`ywicon-${item.icon}`]"></span> + <span>{{ item.label }}</span> + </div> + </div> + <div class="absolute right-2 top-4 py-7 px-5 rounded-lg w-96" style="box-shadow: 0 0 15px #dbdee6" v-if="searchIsShow"> + <span + class="absolute ywifont ywicon-guanbi right-4 top-4 cursor-pointer hover:bg-gray-200 active:bg-gray-300" + @click="closeSearch" + ></span> + <div class="flex-column"> + <span class="mb-5">鏌ユ壘</span> + <el-input v-model="searchValue" @input="debounceSearch" placeholder="杈撳叆鏌ユ壘鍐呭" v-elInputFocus></el-input> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { DagreLayout } from '@antv/layout'; +import { Graph } from '@antv/x6'; +import { onMounted, ref } from 'vue'; +import { routeMap } from './routeMap'; +import { circleShape, edgeShape, ellipseShape, rectShape } from './shape'; +import { getMetricAgentListByPost, getMetricNameListByPost } from '/@/api/metrics'; +import router from '/@/router'; +import { travelTree } from '/@/utils/util'; +const graphRef = ref<HTMLDivElement>(null); + +let graph: Graph; +const firstLoading = ref(false); + +const agentId = router.currentRoute.value.query.id as string; + +const initGraph = () => { + // 鍒濆鍖栫敾甯� + graph = new Graph({ + container: graphRef.value, + async: true, + interacting: false, + connecting: { + anchor: 'orth', + connector: 'rounded', + connectionPoint: 'boundary', + router: { + name: 'er', + args: { + offset: 24, + direction: 'H', + }, + }, + }, + mousewheel: { + enabled: true, + }, + panning: { + enabled: true, + }, + }); +}; + +const getLeaf = (item) => { + if (item.Children && item.Children.length > 0) { + const first = item.Children[0]; + return getLeaf(first); + } else { + return item; + } +}; + +const initEvent = () => { + graph.on('node:click', ({ cell, e, node, view }) => { + return; + const target = e.target; + const targetParent = target.parentElement; + if (!targetParent.className?.baseVal?.includes('name')) return; + + const data = node.getData(); + const leafData = getLeaf(data); + const routeName = routeMap.get(leafData.Code); + router.push({ + name: routeName, + }); + }); +}; + +const loadData = (treeData: any[]) => { + const data: any = { + nodes: [], + edges: [], + }; + travelTree( + treeData, + (item) => { + let shapeAttrs = null; + switch (item.type) { + case 'agent': + shapeAttrs = rectShape(item.treeId, item.label); + break; + case 'metrics': + shapeAttrs = ellipseShape(item.treeId, item.label); + break; + case 'dimension': + shapeAttrs = circleShape(item.treeId, item.label); + break; + default: + break; + } + data.nodes.push({ + id: item.treeId, + + ...shapeAttrs, + children: item.children?.map((item) => item.treeId), + data: item, + }); + if (item.children) { + for (const child of item?.children) { + const sourceId = `${item.treeId}-b`; + const targetId = `${child.treeId}-t`; + + data.edges.push({ + source: { cell: item.treeId, port: sourceId }, + target: { cell: child.treeId, port: targetId }, + ...edgeShape, + }); + } + } + }, + undefined, + undefined, + 'children' + ); + const dagreLayout = new DagreLayout({ + type: 'dagre', + rankdir: 'TB', + // align: 'UL', + // ranksep: 30, + nodesep: 30, + + controlPoints: false, + }); + let newData = dagreLayout.layout(data); + + graph.fromJSON(newData); + selfAdapt(); +}; + +const getFirstOrgTreeList = async () => { + // const res = await GetSMCenterFirstOrgTreeList(); + + const allAgentRes = getMetricAgentListByPost(); + const metricsRes = getMetricNameListByPost({ + agent_id: agentId, + }); + const [allAgentResult, metricsResult] = await Promise.all([allAgentRes, metricsRes]); + + const allAgentList = allAgentResult?.values ?? []; + const metricsList = metricsResult?.values ?? []; + // const foundAgent = allAgentList.find(item=>item.) + + const foundAgent = allAgentList.find((item) => item.id === agentId); + if (!foundAgent) return []; + + const agentTreeId = `agent-${foundAgent.id}`; + let logicTree = [ + { + treeId: agentTreeId, + logicId: foundAgent.id, + model: foundAgent, + type: 'agent', + get label() { + return this.model.title; + }, + children: metricsList.map((curVal) => { + const metricsTreeId = `${agentTreeId}-metrics-${curVal.id}`; + const metrics = { + treeId:metricsTreeId, + logicId: curVal.id, + type: 'metrics', + + model: curVal, + get label() { + return this.model.title; + }, + children: (curVal.dimensions ?? []).map((item) => { + const dimensionTreeId = `${metricsTreeId}-dimension-${item.id}` + return { + treeId: dimensionTreeId, + logicId: item.id, + type: 'dimension', + model: item, + get label() { + return this.model.title; + }, + }; + }), + }; + return metrics; + }, []), + }, + ]; + console.log('馃殌 ~ logicTree:', logicTree); + const resData = logicTree; + return resData; +}; +const selfAdapt = () => { + graph.zoomToFit(); + graph.zoom(-0.05); +}; + +//#region ====================== 宸ュ叿鏍� ====================== +const enum ToolBarType { + Search, + ZoomIn, + ZoomOut, + Reset, +} +type ToolBarItem = { + icon: string; + label: string; + type: ToolBarType; +}; +const toolBarFunList: ToolBarItem[] = [ + { + icon: 'sousuo', + label: '鎼滅储', + type: ToolBarType.Search, + }, + { + icon: 'fangda', + label: '鏀惧ぇ', + type: ToolBarType.ZoomIn, + }, + { + icon: 'suoxiao', + label: '缂╁皬', + type: ToolBarType.ZoomOut, + }, + { + icon: 'zishiying', + label: '閲嶇疆', + type: ToolBarType.Reset, + }, +]; + +const searchIsShow = ref(false); +const searchValue = ref(''); +const ZOOM_OFFSET = 0.05; +const toolBarItemClick = (item: ToolBarItem) => { + switch (item.type) { + case ToolBarType.Search: + searchIsShow.value = true; + break; + case ToolBarType.ZoomIn: + graph.zoom(ZOOM_OFFSET); + break; + + case ToolBarType.ZoomOut: + graph.zoom(-ZOOM_OFFSET); + + break; + + case ToolBarType.Reset: + selfAdapt(); + default: + break; + } +}; + +const debounceSearch = () => {}; +const closeSearch = () => { + searchIsShow.value = false; +}; +//#endregion +onMounted(async () => { + if (!agentId) return; + firstLoading.value = true; + + const orgTreeList = await getFirstOrgTreeList().catch(() => { + firstLoading.value = false; + }); + setTimeout(() => { + initGraph(); + initEvent(); + loadData(orgTreeList as any); + firstLoading.value = false; + }, 100); +}); +</script> +<style scoped lang="scss"> +:deep(g[data-shape='rect'].x6-node > text:hover) { + // text-decoration: underline; + cursor: pointer; +} +</style> diff --git a/src/views/project/yw/systemManage/metricAgentMgr/graph/routeMap.ts b/src/views/project/yw/systemManage/agentGraph/routeMap.ts similarity index 100% rename from src/views/project/yw/systemManage/metricAgentMgr/graph/routeMap.ts rename to src/views/project/yw/systemManage/agentGraph/routeMap.ts diff --git a/src/views/project/yw/systemManage/metricAgentMgr/graph/shape.ts b/src/views/project/yw/systemManage/agentGraph/shape.ts similarity index 77% rename from src/views/project/yw/systemManage/metricAgentMgr/graph/shape.ts rename to src/views/project/yw/systemManage/agentGraph/shape.ts index 26589ff..0c9a3fb 100644 --- a/src/views/project/yw/systemManage/metricAgentMgr/graph/shape.ts +++ b/src/views/project/yw/systemManage/agentGraph/shape.ts @@ -66,6 +66,7 @@ }; export const rectShape = (id, name) => ({ + shape:'rect', width: 80, height: 42, attrs: { @@ -80,10 +81,9 @@ fill: '#080808', class: 'name', - refX: '50%', - refY: '100%', - refY2: NODE_TEXT_SPACE, - yAlign: 'top', + refY: '0', + refY2: -NODE_TEXT_SPACE, + yAlign: 'bottom', }, }, markup: [ @@ -99,6 +99,42 @@ ports: { ...directionPorts(id) }, }); + + + +export const ellipseShape = (id, name) => ({ + shape:'rect', + width: 80, + height: 42, + attrs: { + body: { + stroke: useCssVar('--el-color-warning').value, + strokeWidth: 1, + fill: useCssVar('--el-color-warning-light-7').value, + }, + text: { + text: name, + fontSize: BASE_FONT_SIZE, + fill: '#080808', + class: 'name', + + refX: '100%', + refX2: NODE_TEXT_SPACE, + xAlign: 'left', + }, + }, + markup: [ + { + tagName: 'rect', + selector: 'body', + }, + { + tagName: 'text', + selector: 'text', + }, + ], + ports: { ...directionPorts(id) }, +}); export const verticalRectShape = (id, name) => ({ width: 30, height: 100, @@ -147,10 +183,16 @@ fill: '#080808', // writingMode: 'tb-rl', class: 'name', - refX: '100%', - refX2: NODE_TEXT_SPACE, - refY: '50%', - xAlign: 'left', + // refX: '100%', + // refX2: NODE_TEXT_SPACE, + // refY: '50%', + // xAlign: 'left', + + + refX: '50%', + refY: '100%', + refY2: NODE_TEXT_SPACE, + yAlign: 'top', }, }, }; diff --git a/src/views/project/yw/systemManage/metricAgentMgr/graph/testData.ts b/src/views/project/yw/systemManage/agentGraph/testData.ts similarity index 100% rename from src/views/project/yw/systemManage/metricAgentMgr/graph/testData.ts rename to src/views/project/yw/systemManage/agentGraph/testData.ts diff --git a/src/views/project/yw/systemManage/metricAgentMgr/MetricAgentMgr.vue b/src/views/project/yw/systemManage/metricAgentMgr/MetricAgentMgr.vue index 367c59f..04038e9 100644 --- a/src/views/project/yw/systemManage/metricAgentMgr/MetricAgentMgr.vue +++ b/src/views/project/yw/systemManage/metricAgentMgr/MetricAgentMgr.vue @@ -36,17 +36,13 @@ <el-form-item> <el-button icon="ele-Refresh" @click="resetQuery">閲嶇疆 </el-button> </el-form-item> - <div class="absolute top-0 right-0 w-[112px] h-[24[x]] bg-white z-50 leading-3 pt-2"> - <el-radio-group v-model="canvasRadio" size="small"> - <el-radio-button label="鍒楄〃" :value="RadioType.List" /> - <el-radio-button label="鐢诲竷" :value="RadioType.Canvas" /> - </el-radio-group> - </div> + </el-form> </template> <template #main> <div class="w100 h100"> - <div class="h-full" v-show="canvasRadio === RadioType.List"> + <div class="h-full" > + <el-table v-loading="tableLoading" ref="draggableTableRef" @@ -80,7 +76,12 @@ @click="openMetricNameDlg(scope.row)" ></i> </el-tooltip> - + <el-tooltip effect="dark" content="鎸囨爣鍥捐氨" placement="top"> + <i + class="ywifont ywicon-duoweidu !text-[17px] text-blue-400 cursor-pointer" + @click="gotoAgentGraph(scope.row)" + ></i> + </el-tooltip> <el-tooltip effect="dark" content="瀵硅瘽娴嬭瘯" placement="top"> <i class="ywifont ywicon-ceshi !text-[20px] text-blue-400 cursor-pointer" @click="openChatTest(scope.row)"></i> </el-tooltip> @@ -106,7 +107,6 @@ <Chat ref="chatRef" class="flex-auto px-2" :questionApi="questionAi"> </Chat> </div> </div> - <AgentGraph v-if="canvasRadio ===RadioType.Canvas" /> </div> </template> <!-- <OptDlg v-model="optDlgIsShow" :item="optDlgMapRow" @insert="insertOpt" @update="updateOpt" :groupId="currentListID"></OptDlg> --> @@ -132,7 +132,7 @@ import { useCompRef } from '/@/utils/types'; import { convertListToTree, debounce } from '/@/utils/util'; import { OptClassificationMap, classificationEnum } from '/@/views/types/metrics'; -import AgentGraph from './graph/AgentGraph.vue'; +import { gotoRoute } from '/@/utils/route'; //#region ====================== 宸︿晶鏍戞暟鎹紝tree init ====================== const leftTreeRef = useCompRef(LeftTreeByMgr); const treeLoading = ref(false); @@ -212,15 +212,7 @@ showQuotaList.value = tableData.value; }); //#endregion -//#region ====================== 妯″瀷绠$悊(娌℃湁鐖惰妭鐐�) ====================== - const enum RadioType { - List = 'list', - Canvas='canvas' -} -const canvasRadio = ref(RadioType.List); -const getNodeTableData = () => {}; -//#endregion //#region ====================== Chat 娴嬭瘯 ====================== const chatRef = useCompRef(Chat); const chatTestMapRow = ref(null); @@ -278,6 +270,15 @@ metricNameMapRow.value = row; metricNameIsShow.value = true; }; + +const gotoAgentGraph = (row) => { + gotoRoute({ + name: 'AgentGraph', + query: { + id: row.id, + }, + }); +}; //#endregion onMounted(() => { diff --git a/src/views/project/yw/systemManage/metricAgentMgr/graph/AgentGraph.vue b/src/views/project/yw/systemManage/metricAgentMgr/graph/AgentGraph.vue deleted file mode 100644 index 20c1279..0000000 --- a/src/views/project/yw/systemManage/metricAgentMgr/graph/AgentGraph.vue +++ /dev/null @@ -1,168 +0,0 @@ -<template> - <div v-loading="firstLoading" class="h-full bg-white relative"> - <!-- <div class="absolute right-4 top-4 z-10"> - - - <span class="ywifont ywicon-zishiying cursor-pointer" title="鑷�傚簲" @click="selfAdaptClick"></span> - </div> --> - <div class="h-full" ref="graphRef"></div> - </div> -</template> - -<script setup lang="ts"> -import { DagreLayout } from '@antv/layout'; -import { Graph } from '@antv/x6'; -import { onMounted, ref } from 'vue'; -import { routeMap } from './routeMap'; -import { circleShape, edgeShape, rectShape, verticalRectShape } from './shape'; -import router from '/@/router'; -import { travelTree } from '/@/utils/util'; -import { NODE_DATA, SIMPLE_LAYOUT_DATA } from './testData'; -const graphRef = ref<HTMLDivElement>(null); - -let graph: Graph; -const firstLoading = ref(false); -const initGraph = () => { - // 鍒濆鍖栫敾甯� - graph = new Graph({ - container: graphRef.value, - async: true, - interacting: false, - connecting: { - anchor: 'orth', - connector: 'rounded', - connectionPoint: 'boundary', - router: { - name: 'er', - args: { - offset: 24, - direction: 'H', - }, - }, - }, - mousewheel: { - enabled: true, - }, - panning: { - enabled: true, - }, - }); -}; - -const getLeaf = (item) => { - if (item.Children && item.Children.length > 0) { - const first = item.Children[0]; - return getLeaf(first); - } else { - return item; - } -}; - -const initEvent = () => { - graph.on('node:click', ({ cell, e, node, view }) => { - return; - const target = e.target; - const targetParent = target.parentElement; - if (!targetParent.className?.baseVal?.includes('name')) return; - - const data = node.getData(); - const leafData = getLeaf(data); - const routeName = routeMap.get(leafData.Code); - router.push({ - name: routeName, - }); - }); -}; - -const loadData = (treeData: any[]) => { - const data: any = { - nodes: [], - edges: [], - }; - travelTree( - treeData, - (item) => { - const hasChildren = item.Children && item.Children.length > 0; - // const hasChildren = true; - const shapeAttrs = hasChildren ? rectShape(item.Id, item.Name) : circleShape(item.Id, item.Name); - if (hasChildren) { - data.nodes.push({ - id: item.Id, - shape: 'rect', - - ...shapeAttrs, - children: item.Children?.map((item) => item.Id), - data: item, - }); - } else { - console.log('馃殌 ~ circle',); - data.nodes.push({ - id: item.Id, - - ...shapeAttrs, - - children: item.Children?.map((item) => item.Id), - data: item, - }); - } - - for (const child of item.Children) { - const sourceId = `${item.Id}-r`; - const targetId = `${child.Id}-l`; - - data.edges.push({ - source: { cell: item.Id, port: sourceId }, - target: { cell: child.Id, port: targetId }, - ...edgeShape, - }); - } - }, - undefined, - undefined, - 'Children' - ); - const dagreLayout = new DagreLayout({ - type: 'dagre', - rankdir: 'LR', - // align: 'UL', - // ranksep: 30, - nodesep: 30, - - controlPoints: false, - }); - let newData = dagreLayout.layout(data); - - graph.fromJSON(newData); - selfAdapt(); -}; - -const getFirstOrgTreeList = async () => { - // const res = await GetSMCenterFirstOrgTreeList(); - - const resData = NODE_DATA; - return resData; -}; -const selfAdapt = () => { - graph.zoomToFit(); - graph.zoom(-0.20); -}; -onMounted(async () => { - firstLoading.value = true; - - const orgTreeList = await getFirstOrgTreeList().catch(() => { - firstLoading.value = false; - }); - setTimeout(() => { - initGraph(); - initEvent(); - loadData(orgTreeList as any); - firstLoading.value = false; - }, 100); -}); -</script> -<style scoped lang="scss"> -:deep(g[data-shape='rect'].x6-node > text:hover) { - // text-decoration: underline; - cursor: pointer; -} -</style> -- Gitblit v1.9.3