Selaa lähdekoodia

设备管理提交

luogang 9 kuukautta sitten
vanhempi
commit
e8483fce3e

+ 16 - 2
package.json

@@ -18,6 +18,16 @@
     "url": "https://gitee.com/JavaLionLi/plus-ui.git"
   },
   "dependencies": {
+    "@antv/x6": "^2.18.1",
+    "@antv/x6-plugin-clipboard": "^2.1.6",
+    "@antv/x6-plugin-export": "^2.1.6",
+    "@antv/x6-plugin-history": "^2.2.4",
+    "@antv/x6-plugin-keyboard": "^2.2.3",
+    "@antv/x6-plugin-minimap": "^2.0.7",
+    "@antv/x6-plugin-selection": "^2.2.2",
+    "@antv/x6-plugin-snapline": "^2.1.7",
+    "@antv/x6-plugin-stencil": "^2.1.5",
+    "@antv/x6-plugin-transform": "^2.1.8",
     "@element-plus/icons-vue": "2.3.1",
     "@highlightjs/vue-plugin": "2.1.0",
     "@vueup/vue-quill": "1.2.0",
@@ -30,11 +40,15 @@
     "diagram-js": "12.3.0",
     "didi": "9.0.2",
     "echarts": "5.5.0",
+    "echarts-gl": "^2.0.9",
+    "echarts-liquidfill": "^3.1.0",
     "element-plus": "2.7.8",
+    "element-resize-detector": "^1.2.4",
     "file-saver": "2.0.5",
     "fuse.js": "7.0.0",
     "highlight.js": "11.9.0",
     "image-conversion": "^2.1.1",
+    "insert-css": "^2.0.0",
     "js-cookie": "3.0.5",
     "jsencrypt": "3.3.2",
     "nprogress": "0.2.0",
@@ -66,10 +80,10 @@
     "eslint": "8.57.0",
     "eslint-config-prettier": "9.1.0",
     "eslint-define-config": "2.1.0",
+    "eslint-plugin-import": "2.29.1",
+    "eslint-plugin-node": "11.1.0",
     "eslint-plugin-prettier": "5.1.3",
     "eslint-plugin-promise": "6.1.1",
-    "eslint-plugin-node": "11.1.0",
-    "eslint-plugin-import": "2.29.1",
     "eslint-plugin-vue": "9.23.0",
     "fast-glob": "3.3.2",
     "postcss": "8.4.36",

+ 1 - 0
src/assets/images/camera.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1730172965322" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13414" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M157.44 670.72v-111.36c0-3.84-2.56-6.4-6.4-7.68l-78.08-15.36c-5.12-1.28-8.96 1.28-8.96 6.4v386.56c0 5.12 5.12 8.96 10.24 7.68l78.08-28.16c2.56-1.28 5.12-3.84 5.12-7.68v-128c0-3.84 3.84-7.68 7.68-7.68h139.52c2.56 0 5.12-1.28 6.4-2.56l75.52-106.24c2.56-3.84 1.28-8.96-2.56-10.24l-92.16-51.2c-3.84-2.56-8.96 0-10.24 3.84l-24.32 75.52c-1.28 2.56-3.84 5.12-7.68 5.12h-81.92c-6.4 0-10.24-3.84-10.24-8.96z m798.72-124.16L281.6 88.32c-1.28-1.28-2.56-1.28-3.84-1.28H204.8c-2.56 0-5.12 1.28-6.4 3.84L93.44 261.12c-1.28 1.28-1.28 2.56-1.28 3.84v53.76c0 2.56 1.28 5.12 3.84 6.4l596.48 376.32c1.28 1.28 2.56 1.28 3.84 1.28H742.4c1.28 0 2.56 0 3.84-1.28l209.92-142.08c5.12-3.84 5.12-10.24 0-12.8zM142.08 389.12l-43.52 49.92c-2.56 3.84-2.56 8.96 1.28 11.52l588.8 380.16c1.28 1.28 2.56 1.28 3.84 1.28H742.4c2.56 0 3.84-1.28 5.12-2.56l25.6-26.88c1.28-1.28 1.28-1.28 1.28-2.56l23.04-72.96c1.28-3.84-3.84-7.68-7.68-5.12-17.92 14.08-53.76 40.96-65.28 49.92-2.56 1.28-6.4 1.28-8.96 0-16.64-11.52-34.56-21.76-52.48-30.72h-1.28L152.32 387.84c-3.84-1.28-7.68-1.28-10.24 1.28z" fill="#1296db" p-id="13415"></path></svg>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
src/assets/images/fan.svg


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
src/assets/images/lighting.svg


BIN
src/assets/images/position.png


BIN
src/assets/images/road.png


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
src/assets/images/smoke.svg


+ 83 - 0
src/components/BaseChart/index.vue

@@ -0,0 +1,83 @@
+<template>
+  <div ref="chart" :style="style"></div>
+</template>
+
+<script lang="ts" setup>
+import * as echarts from 'echarts';
+import elementResizeDetectorMaker from 'element-resize-detector';
+import 'echarts-liquidfill';
+import 'echarts-gl';
+const props = defineProps({
+  // 图表的宽度
+  width: {
+    type: String,
+    default: '600px'
+  },
+  // 图表的高度
+  height: {
+    type: String,
+    default: '400px'
+  },
+  // 图表的配置
+  option: {
+    type: Object,
+    required: true
+  }
+});
+
+const chart = shallowRef<any>();
+
+const style = computed(() => ({
+  width: props.width,
+  height: props.height
+}));
+
+// 初始化图表
+const initChart = () => {
+  // setTimeout(() => {
+  if (!chartInstance.value) {
+    chartInstance.value = echarts.init(chart.value);
+  }
+  chartInstance.value.clear();
+  props.option && chartInstance.value.setOption(props.option, true);
+
+  // }, 1000)
+};
+
+// 处理图表容器大小变化
+const handleResize = () => {
+  const erd = elementResizeDetectorMaker();
+  // 监听图表容器的大小变化
+  erd.listenTo(chart.value, () => {
+    // 改变图表尺寸
+    chartInstance.value?.resize();
+  });
+};
+
+const chartInstance = shallowRef<any>();
+
+watch(
+  () => props.option,
+  () => {
+    initChart();
+  },
+  { deep: true }
+);
+
+onMounted(() => {
+  initChart();
+  handleResize();
+});
+
+onBeforeUnmount(() => {
+  if (chartInstance.value) {
+    // 销毁图表实例
+    chartInstance.value.dispose();
+    chartInstance.value = null;
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+/* 你可以在这里定义样式 */
+</style>

+ 28 - 0
src/components/SubTitle/index.vue

@@ -0,0 +1,28 @@
+<template>
+  <div class="sub-title">{{ props.title }}</div>
+</template>
+<script setup lang="ts">
+const props = defineProps({
+  title: {
+    type: String,
+    default: ''
+  }
+});
+</script>
+<style lang="scss" scoped>
+.sub-title {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  font-weight: 500;
+  &::before {
+    content: '';
+    display: inline-block;
+    height: 14px;
+    width: 3px;
+    border-radius: 6px;
+    background: #409eff;
+    margin-right: 5px;
+  }
+}
+</style>

+ 5 - 0
src/views/dashbord.vue

@@ -0,0 +1,5 @@
+<template>
+  <div class=""></div>
+</template>
+<script setup lang="ts"></script>
+<style lang="scss" scoped></style>

+ 380 - 0
src/views/dashbordAdmin.vue

@@ -0,0 +1,380 @@
+<template>
+    <div class="dashbord">
+        <el-card>
+            <SubTitle title="设备运行" />
+            <img class="positionImg" src="@/assets/images/position.png" alt="" />
+        </el-card>
+        <div class="chart-group">
+            <el-card>
+                <SubTitle title="项目情况" />
+                <div class="chart-content">
+                    <BaseChart width="100%" height="100%" :option="option" />
+                </div>
+            </el-card>
+            <el-card>
+                <SubTitle title="设备总览" />
+                <div class="chart-content">
+                    <BaseChart width="100%" height="100%" :option="option" />
+                </div>
+            </el-card>
+            <el-card>
+                <SubTitle title="巡检人员" />
+            </el-card>
+            <el-card>
+                <SubTitle title="设备故障" />
+            </el-card>
+        </div>
+    </div>
+</template>
+<script setup lang="ts">
+function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, height) {
+    // 计算
+    let midRatio = (startRatio + endRatio) / 2;
+
+    let startRadian = startRatio * Math.PI * 2;
+    let endRadian = endRatio * Math.PI * 2;
+    let midRadian = midRatio * Math.PI * 2;
+
+    // 如果只有一个扇形,则不实现选中效果。
+    if (startRatio === 0 && endRatio === 1) {
+        isSelected = false;
+    }
+
+    // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
+    k = typeof k !== 'undefined' ? k : 1 / 3;
+
+    // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
+    let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
+    let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
+
+    // 计算高亮效果的放大比例(未高亮,则比例为 1)
+    let hoverRate = isHovered ? 1.05 : 1;
+
+    // 返回曲面参数方程
+    return {
+        u: {
+            min: -Math.PI,
+            max: Math.PI * 3,
+            step: Math.PI / 32
+        },
+
+        v: {
+            min: 0,
+            max: Math.PI * 2,
+            step: Math.PI / 20
+        },
+
+        x: function (u, v) {
+            if (u < startRadian) {
+                return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
+            }
+            if (u > endRadian) {
+                return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
+            }
+            return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
+        },
+
+        y: function (u, v) {
+            if (u < startRadian) {
+                return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
+            }
+            if (u > endRadian) {
+                return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
+            }
+            return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
+        },
+
+        z: function (u, v) {
+            if (u < -Math.PI * 0.5) {
+                return Math.sin(u);
+            }
+            if (u > Math.PI * 2.5) {
+                return Math.sin(u);
+            }
+            return Math.sin(v) > 0 ? 0.2 * height : -1;
+        }
+    };
+}
+
+// 生成模拟 3D 饼图的配置项
+function getPie3D(pieData, internalDiameterRatio) {
+    let series = [];
+    let sumValue = 0;
+    let startValue = 0;
+    let endValue = 0;
+    let legendData = [];
+    let k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3;
+
+    // 为每一个饼图数据,生成一个 series-surface 配置
+    for (let i = 0; i < pieData.length; i++) {
+        sumValue += pieData[i].value;
+
+        let seriesItem = {
+            name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
+            type: 'surface',
+            parametric: true,
+            wireframe: {
+                show: false
+            },
+            pieData: pieData[i],
+            pieStatus: {
+                selected: false,
+                hovered: false,
+                k: k
+            }
+        };
+
+        if (typeof pieData[i].itemStyle != 'undefined') {
+            let itemStyle = {};
+
+            typeof pieData[i].itemStyle.color != 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
+            typeof pieData[i].itemStyle.opacity != 'undefined' ? (itemStyle.opacity = pieData[i].itemStyle.opacity) : null;
+
+            seriesItem.itemStyle = itemStyle;
+        }
+        series.push(seriesItem);
+    }
+
+    // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
+    // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
+    for (let i = 0; i < series.length; i++) {
+        endValue = startValue + series[i].pieData.value;
+        console.log(series[i]);
+        series[i].pieData.startRatio = startValue / sumValue;
+        series[i].pieData.endRatio = endValue / sumValue;
+        series[i].parametricEquation = getParametricEquation(
+            series[i].pieData.startRatio,
+            series[i].pieData.endRatio,
+            false,
+            false,
+            k,
+            series[i].pieData.value
+        );
+
+        startValue = endValue;
+
+        legendData.push(series[i].name);
+    }
+
+    // // 补充一个透明的圆环,用于支撑高亮功能的近似实现。
+    series.push({
+        name: 'mouseoutSeries',
+        type: 'surface',
+        parametric: true,
+        wireframe: {
+            show: false
+        },
+        itemStyle: {
+            opacity: 0.1,
+            color: '#8997DE'
+        },
+        parametricEquation: {
+            u: {
+                min: 0,
+                max: Math.PI * 2,
+                step: Math.PI / 20
+            },
+            v: {
+                min: 0,
+                max: Math.PI,
+                step: Math.PI / 20
+            },
+            x: function (u, v) {
+                return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2;
+            },
+            y: function (u, v) {
+                return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2;
+            },
+            z: function (u, v) {
+                return Math.cos(v) > 0 ? -0.5 : -5;
+            }
+        }
+    });
+
+    // // 补充一个透明的圆环,用于支撑高亮功能的近似实现。
+    series.push({
+        name: 'mouseoutSeries',
+        type: 'surface',
+        parametric: true,
+        wireframe: {
+            show: false
+        },
+        itemStyle: {
+            opacity: 0.1,
+            color: '#8997DE'
+        },
+        parametricEquation: {
+            u: {
+                min: 0,
+                max: Math.PI * 2,
+                step: Math.PI / 20
+            },
+            v: {
+                min: 0,
+                max: Math.PI,
+                step: Math.PI / 20
+            },
+            x: function (u, v) {
+                return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2;
+            },
+            y: function (u, v) {
+                return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2;
+            },
+            z: function (u, v) {
+                return Math.cos(v) > 0 ? -5 : -7;
+            }
+        }
+    });
+    series.push({
+        name: 'mouseoutSeries',
+        type: 'surface',
+        parametric: true,
+        wireframe: {
+            show: false
+        },
+        itemStyle: {
+            opacity: 0.1,
+            color: '#8997DE'
+        },
+
+        parametricEquation: {
+            u: {
+                min: 0,
+                max: Math.PI * 2,
+                step: Math.PI / 20
+            },
+            v: {
+                min: 0,
+                max: Math.PI,
+                step: Math.PI / 20
+            },
+            x: function (u, v) {
+                return ((Math.sin(v) * Math.sin(u) + Math.sin(u)) / Math.PI) * 2.2;
+            },
+            y: function (u, v) {
+                return ((Math.sin(v) * Math.cos(u) + Math.cos(u)) / Math.PI) * 2.2;
+            },
+            z: function (u, v) {
+                return Math.cos(v) > 0 ? -7 : -7;
+            }
+        }
+    });
+    return series;
+}
+
+let colors = ['#085AC7', '#24525E', '#C3972E'];
+let xData = ['运营', '施工', '结束'];
+let yData = [568, 175, 396];
+// 传入数据生成 option
+let optionsData = [];
+let total = 0;
+yData.forEach((v) => {
+    total += v;
+});
+for (let i = 0; i < xData.length; i++) {
+    optionsData.push({
+        name: xData[i],
+        value: yData[i],
+        itemStyle: {
+            color: colors[i],
+            opacity: 0.7
+        }
+    });
+}
+
+const series = getPie3D(optionsData, 0.8);
+const option = computed(() => {
+    return {
+        grid: {
+            top: '0%',
+            containLabel: true
+        },
+        legend: {
+            tooltip: {
+                show: true
+            },
+            data: xData,
+            orient: 'vertial',
+            bottom: '10%',
+            left: 'center',
+            itemGap: 14,
+            itemHeight: 10,
+            itemWidth: 15,
+            formatter: (name) => {
+                const res = optionsData.filter((n) => {
+                    return n.name === name;
+                });
+                if (!res.length) return;
+                return `${name}  ${res[0].value}个  ${res[0].value ? ((res[0].value / total) * 100).toFixed(2) : 0}%`;
+            }
+        },
+        animation: true,
+        tooltip: {
+            formatter: (params) => {
+                if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
+                    return `${params.seriesName}<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>${option.value.series[params.seriesIndex].pieData.value}个`;
+                }
+            },
+            textStyle: {
+                fontSize: 12
+            }
+        },
+        xAxis3D: {
+            min: -1,
+            max: 1
+        },
+        yAxis3D: {
+            min: -1,
+            max: 1
+        },
+        zAxis3D: {
+            min: -1,
+            max: 1
+        },
+        grid3D: {
+            show: false,
+            boxHeight: 0.6,
+            bottom: '20%',
+            viewControl: {
+                distance: 280,
+                alpha: 25,
+                beta: 60,
+                autoRotate: true // 自动旋转
+            }
+        },
+
+        series: series
+    };
+});
+</script>
+<style lang="scss" scoped>
+.positionImg {
+    width: 100%;
+    height: 250px;
+}
+
+.chart-group {
+    display: flex;
+    margin-top: 5px;
+
+    :deep(.el-card__body) {
+        height: 100%;
+    }
+
+    .el-card {
+        flex: 1;
+        height: 350px;
+        background: #fff;
+
+        &:not(:first-child) {
+            margin-left: 5px;
+        }
+    }
+
+    .chart-content {
+        height: 100%;
+        margin-top: 5px;
+        border-top: 1px solid #eaebee;
+    }
+}
+</style>

+ 485 - 3
src/views/deviceManage/modeling/index.vue

@@ -1,5 +1,487 @@
 <template>
-  <div class=""></div>
+  <div class="content-main">
+    <div class="tool-container">
+      <el-button type="primary" text @click="copy">复制</el-button>
+      <el-divider direction="vertical" />
+      <el-button type="primary" text @click="paste">粘贴</el-button>
+      <el-divider direction="vertical" />
+      <el-button type="primary" text @click="del">删除</el-button>
+      <el-divider direction="vertical" />
+      <el-button type="primary" text @click="save">保存</el-button>
+      <!-- <el-divider direction="vertical" />
+      <el-button type="primary" text @click="exportPng">导出PNG</el-button> -->
+    </div>
+    <div id="" class="content-container">
+      <div class="content">
+        <div ref="stencilContainer" class="stencil"></div>
+        <div id="graphContainer" ref="graphContainer" class="graph-content"></div>
+        <!-- <img src="@/assets/images/road.png" class="roadback" alt="" /> -->
+      </div>
+    </div>
+  </div>
 </template>
-<script setup lang="ts"></script>
-<style lang="scss" scoped></style>
+
+<script setup lang="ts">
+import { Graph, Path, Edge, StringExt, Node, Cell, Model, DataUri } from '@antv/x6';
+import { Transform } from '@antv/x6-plugin-transform';
+import { Selection } from '@antv/x6-plugin-selection';
+import { Snapline } from '@antv/x6-plugin-snapline';
+import { Keyboard } from '@antv/x6-plugin-keyboard';
+import { Clipboard } from '@antv/x6-plugin-clipboard';
+import { Stencil } from '@antv/x6-plugin-stencil';
+import { Export } from '@antv/x6-plugin-export';
+
+const stencilContainer = ref();
+const graphContainer = ref();
+
+let graph: any = null;
+
+const state = reactive({
+  data: {
+    nodes: [
+      {
+        id: 'ac51fb2f-2753-4852-8239-53672a29bb14',
+        position: {
+          x: 160,
+          y: 250
+        },
+        data: {
+          name: '烟感系统'
+        }
+      },
+      {
+        id: '81004c2f-0413-4cc6-8622-127004b3befa',
+        position: {
+          x: 220,
+          y: 110
+        },
+        data: {
+          name: '烟感系统'
+        }
+      },
+      {
+        id: 'f2db8c64-6ffe-4c9b-83a2-037c9154e599',
+        position: {
+          x: 50,
+          y: 120
+        },
+        data: {
+          name: '摄像头'
+        }
+      }
+    ]
+  }
+});
+const imageShapes = [
+  {
+    label: '摄像头',
+    image: new URL('@/assets/images/camera.svg', import.meta.url).href
+  },
+  {
+    label: '风机',
+    image: new URL('@/assets/images/fan.svg', import.meta.url).href
+  },
+  {
+    label: '照明设施',
+    image: new URL('@/assets/images/lighting.svg', import.meta.url).href
+  },
+  {
+    label: '烟感系统',
+    image: new URL('@/assets/images/smoke.svg', import.meta.url).href
+  }
+];
+const getNodeAttrs = (label) => {
+  const [{ image }] = imageShapes.filter((item) => item.label === label);
+  return {
+    image: {
+      'xlink:href': image
+    }
+  };
+};
+const init = () => {
+  graph = new Graph({
+    container: graphContainer.value,
+    grid: true,
+    mousewheel: {
+      enabled: true,
+      zoomAtMousePosition: true,
+      modifiers: 'ctrl',
+      minScale: 0.5,
+      maxScale: 3
+    },
+    highlighting: {
+      magnetAdsorbed: {
+        name: 'stroke',
+        args: {
+          attrs: {
+            fill: '#fff',
+            stroke: '#31d0c6',
+            strokeWidth: 4
+          }
+        }
+      }
+    }
+  });
+  // graph.centerContent();
+
+  // #region 使用插件
+  graph
+    .use(
+      new Transform({
+        resizing: true,
+        rotating: true
+      })
+    )
+    .use(
+      new Selection({
+        rubberband: true,
+        showNodeSelectionBox: true
+      })
+    )
+    .use(new Snapline())
+    .use(new Keyboard())
+    .use(new Clipboard())
+    .use(new Export());
+  Graph.registerNode(
+    'custom-image',
+    {
+      inherit: 'rect',
+      width: 52,
+      height: 52,
+      markup: [
+        {
+          tagName: 'rect',
+          selector: 'body'
+        },
+        {
+          tagName: 'image'
+        },
+        {
+          tagName: 'text',
+          selector: 'label'
+        }
+      ],
+      attrs: {
+        body: {
+          stroke: '#5F95FF',
+          fill: 'transparent',
+          strokeWidth: 0
+        },
+        image: {
+          width: 45,
+          height: 45,
+          refX: 5,
+          refY: 5
+        },
+        label: {
+          refX: 3,
+          refY: 2,
+          textAnchor: 'left',
+          textVerticalAnchor: 'top',
+          fontSize: 12,
+          fill: 'transparent'
+        }
+      }
+    },
+    true
+  );
+  const stencil = new Stencil({
+    title: '道路元器',
+    target: graph,
+    stencilGraphWidth: 200,
+    stencilGraphHeight: 180,
+    collapsable: true,
+    groups: [
+      {
+        title: '基础元器',
+        name: 'processLibrary',
+        graphHeight: 250,
+        layoutOptions: {
+          rowHeight: 70
+        }
+      }
+    ],
+    layoutOptions: {
+      columns: 2,
+      columnWidth: 80,
+      rowHeight: 55
+    }
+  });
+  stencilContainer.value.appendChild(stencil.container);
+  graph.drawBackground({
+    color: '#ccc',
+    position: 'center',
+    size: {
+      width: '100%'
+      // height: 100
+    },
+    image: new URL('@/assets/images/road.png', import.meta.url).href // 设置背景图片
+  });
+  // #region 快捷键与事件
+  graph.bindKey(['meta+c', 'ctrl+c'], () => {
+    copy();
+  });
+  graph.bindKey(['meta+x', 'ctrl+x'], () => {
+    const cells = graph.getSelectedCells();
+    if (cells.length) {
+      graph.cut(cells);
+    }
+    return false;
+  });
+  graph.bindKey(['meta+v', 'ctrl+v'], () => {
+    paste();
+  });
+  // select all
+  graph.bindKey(['meta+a', 'ctrl+a'], () => {
+    const nodes = graph.getNodes();
+    if (nodes) {
+      graph.select(nodes);
+    }
+  });
+
+  // delete
+  graph.bindKey('backspace', () => {
+    del();
+  });
+
+  // zoom
+  graph.bindKey(['ctrl+1', 'meta+1'], () => {
+    const zoom = graph.zoom();
+    if (zoom < 1.5) {
+      graph.zoom(0.1);
+    }
+  });
+  graph.bindKey(['ctrl+2', 'meta+2'], () => {
+    const zoom = graph.zoom();
+    if (zoom > 0.5) {
+      graph.zoom(-0.1);
+    }
+  });
+
+  //节点被取消选中时触发。
+  graph.on('node:unselected', (args: { cell: Cell; node: Node; options: Model.SetOptions }) => {
+    args.node.removeTools();
+  });
+
+  //边选中事件
+  graph.on('edge:selected', (args: { cell: Cell; edge: Edge; options: Model.SetOptions }) => {
+    args.edge.attr('line/strokeWidth', 3);
+  });
+
+  //边被取消选中时触发。
+  graph.on('edge:unselected', (args: { cell: Cell; edge: Edge; options: Model.SetOptions }) => {
+    args.edge.attr('line/strokeWidth', 1);
+  });
+
+  const nodes = imageShapes.map((item) => {
+    const node = {
+      shape: 'custom-image',
+      label: item.label,
+      attrs: getNodeAttrs(item.label)
+    };
+    const newNode = graph.addNode(node);
+    return newNode;
+  });
+  stencil.load(nodes, 'processLibrary');
+};
+
+//保存
+function save() {
+  console.log('save');
+  const graphData = graph.toJSON();
+  console.log(graphData);
+}
+//复制
+function copy() {
+  const cells = graph.getSelectedCells();
+  if (cells.length) {
+    graph.copy(cells);
+  }
+  return false;
+}
+//粘贴
+function paste() {
+  if (!graph.isClipboardEmpty()) {
+    const cells = graph.paste({ offset: 32 });
+    graph.cleanSelection();
+    graph.select(cells);
+  }
+  return false;
+}
+//删除
+function del() {
+  const cells = graph.getSelectedCells();
+  if (cells.length) {
+    graph.removeCells(cells);
+  }
+}
+
+//导出PNG
+function exportPng() {
+  graph.toPNG(
+    (dataUri: string) => {
+      // 下载
+      DataUri.downloadDataUri(dataUri, 'chart.png');
+    },
+    {
+      padding: {
+        top: 20,
+        right: 20,
+        bottom: 20,
+        left: 20
+      }
+    }
+  );
+}
+
+//加载初始节点
+function getData() {
+  let cells = [] as any;
+  const location = state.data;
+  location.nodes.map((node) => {
+    cells.push(
+      graph.addNode({
+        id: node.id,
+        x: node.position.x,
+        y: node.position.y,
+        shape: 'custom-image',
+        attrs: getNodeAttrs(node.data.name),
+        label: node.data.name,
+        data: node.data
+      })
+    );
+  });
+  graph.resetCells(cells);
+}
+
+onMounted(() => {
+  init();
+  getData();
+});
+
+onUnmounted(() => {
+  graph.dispose();
+});
+</script>
+
+<style lang="scss" scoped>
+.content-main {
+  display: flex;
+  width: 100%;
+  flex-direction: column;
+  height: calc(100vh - 85px - 40px);
+  background-color: #ffffff;
+  position: relative;
+
+  .tool-container {
+    padding: 8px;
+    display: flex;
+    align-items: center;
+    color: rgba(0, 0, 0, 0.45);
+  }
+}
+.content-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  .content {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    min-width: 400px;
+    min-height: 600px;
+    display: flex;
+    border: 1px solid #dfe3e8;
+    flex-direction: row;
+    flex: 1;
+    .stencil {
+      width: 200px;
+      height: 100%;
+      border-right: 1px solid #dfe3e8;
+      position: relative;
+
+      :deep(.x6-widget-stencil) {
+        background-color: #fff;
+      }
+      :deep(.x6-widget-stencil-title) {
+        background-color: #fff;
+        display: none;
+      }
+      :deep(.x6-widget-stencil-content) {
+        top: 0;
+      }
+      :deep(.x6-widget-stencil-group-title) {
+        background-color: #fff !important;
+      }
+    }
+    .graph-content {
+      width: calc(100% - 180px);
+      height: 100%;
+    }
+
+    .editor-sidebar {
+      display: flex;
+      flex-direction: column;
+      border-left: 1px solid #e6f7ff;
+      background: #fafafa;
+      z-index: 9;
+
+      .el-card {
+        border: none;
+      }
+      .edit-panel {
+        flex: 1 1;
+        background-color: #fff;
+      }
+
+      :deep(.x6-widget-minimap-viewport) {
+        border: 1px solid #8f8f8f;
+      }
+
+      :deep(.x6-widget-minimap-viewport-zoom) {
+        border: 1px solid #8f8f8f;
+      }
+    }
+  }
+}
+
+:deep(.x6-widget-transform) {
+  margin: -1px 0 0 -1px;
+  padding: 0px;
+  border: 1px solid #239edd;
+}
+:deep(.x6-widget-transform > div) {
+  border: 1px solid #239edd;
+}
+:deep(.x6-widget-transform > div:hover) {
+  background-color: #3dafe4;
+}
+:deep(.x6-widget-transform-active-handle) {
+  background-color: #3dafe4;
+}
+:deep(.x6-widget-transform-resize) {
+  border-radius: 0;
+}
+:deep(.x6-widget-selection-inner) {
+  border: 1px solid #239edd;
+}
+:deep(.x6-widget-selection-box) {
+  opacity: 0;
+}
+
+.topic-image {
+  visibility: hidden;
+  cursor: pointer;
+}
+.x6-node:hover .topic-image {
+  visibility: visible;
+}
+.x6-node-selected rect {
+  stroke-width: 2px;
+}
+.roadback {
+  position: absolute;
+  bottom: 0;
+  left: 200px;
+}
+</style>

+ 10 - 65
src/views/index.vue

@@ -1,76 +1,21 @@
 <template>
-  <div class="app-container home">
-    首页
-    <el-divider />
+  <div class="home">
+    <component :is="comp"></component>
   </div>
 </template>
 
 <script setup name="Index" lang="ts">
-const goTarget = (url: string) => {
-  window.open(url, '__blank');
-};
+import useUserStore from '@/store/modules/user';
+import DashbordAdmin from './dashbordAdmin.vue';
+import Dashbord from './dashbord.vue';
+console.log(useUserStore().roles);
+const comp = useUserStore().roles.includes('superadmin') ? DashbordAdmin : Dashbord;
 </script>
 
 <style scoped lang="scss">
 .home {
-  blockquote {
-    padding: 10px 20px;
-    margin: 0 0 20px;
-    font-size: 17.5px;
-    border-left: 5px solid #eee;
-  }
-  hr {
-    margin-top: 20px;
-    margin-bottom: 20px;
-    border: 0;
-    border-top: 1px solid #eee;
-  }
-  .col-item {
-    margin-bottom: 20px;
-  }
-
-  ul {
-    padding: 0;
-    margin: 0;
-  }
-
-  font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
-  font-size: 13px;
-  color: #676a6c;
-  overflow-x: hidden;
-
-  ul {
-    list-style-type: none;
-  }
-
-  h4 {
-    margin-top: 0px;
-  }
-
-  h2 {
-    margin-top: 10px;
-    font-size: 26px;
-    font-weight: 100;
-  }
-
-  p {
-    margin-top: 10px;
-
-    b {
-      font-weight: 700;
-    }
-  }
-
-  .update-log {
-    ol {
-      display: block;
-      list-style-type: decimal;
-      margin-block-start: 1em;
-      margin-block-end: 1em;
-      margin-inline-start: 0;
-      margin-inline-end: 0;
-      padding-inline-start: 40px;
-    }
-  }
+  height: 100%;
+  padding: 10px 20px;
+  background: #fcfcfc;
 }
 </style>

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä