Bläddra i källkod

ETA 图库完成

jwyu 11 månader sedan
förälder
incheckning
35b8e4e287

+ 1 - 1
.env.development

@@ -1,5 +1,5 @@
 # 接口地址
-VITE_APP_API_URL="http://8.136.199.33:8610/v1"
+VITE_APP_API_URL="/v1"
 # 路由根地址
 VITE_APP_BASE_URL="/"
 # 打包输入文件名

+ 1 - 1
.env.production

@@ -1,5 +1,5 @@
 # 接口地址
-VITE_APP_API_URL="http://8.136.199.33:8610/v1"
+VITE_APP_API_URL="/v1"
 # 路由根地址
 VITE_APP_BASE_URL="/"
 # 打包输入文件名

+ 1 - 1
.env.test

@@ -1,5 +1,5 @@
 # 接口地址
-VITE_APP_API_URL="http://8.136.199.33:8610/v1"
+VITE_APP_API_URL="/v1"
 # 路由根地址
 VITE_APP_BASE_URL="/"
 # 打包输入文件名

+ 3 - 1
README.md

@@ -1,2 +1,4 @@
 # ETA社区管理后台项目
-1. 测试访问地址:http://8.136.199.33:8900/
+1. 测试访问地址:http://8.136.199.33:8900/
+2. 项目引入了两个UI库 Tdesign 和 ElementPlus,主要使用Tdesign因为UI是按照这个组件库标准设计的,
+    使用ElementPlus是因为用了其中的Tree组件

+ 4 - 0
package.json

@@ -10,8 +10,12 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@antv/x6": "^2.18.1",
+    "@antv/x6-vue-shape": "^2.1.2",
     "@vueuse/core": "^10.9.0",
     "axios": "^1.6.8",
+    "dagre": "^0.8.5",
+    "element-plus": "^2.7.1",
     "highcharts": "^11.4.1",
     "lodash": "^4.17.21",
     "moment": "^2.30.1",

+ 190 - 0
pnpm-lock.yaml

@@ -1,12 +1,24 @@
 lockfileVersion: '6.0'
 
 dependencies:
+  '@antv/x6':
+    specifier: ^2.18.1
+    version: 2.18.1
+  '@antv/x6-vue-shape':
+    specifier: ^2.1.2
+    version: 2.1.2(@antv/x6@2.18.1)(vue@3.4.21)
   '@vueuse/core':
     specifier: ^10.9.0
     version: 10.9.0(vue@3.4.21)
   axios:
     specifier: ^1.6.8
     version: 1.6.8
+  dagre:
+    specifier: ^0.8.5
+    version: 0.8.5
+  element-plus:
+    specifier: ^2.7.1
+    version: 2.7.1(vue@3.4.21)
   highcharts:
     specifier: ^11.4.1
     version: 11.4.1
@@ -58,6 +70,40 @@ packages:
     resolution: {integrity: sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==}
     dev: true
 
+  /@antv/x6-common@2.0.17:
+    resolution: {integrity: sha512-37g7vmRkNdYzZPdwjaMSZEGv/MMH0S4r70/Jwoab1mioycmuIBN73iyziX8m56BvJSDucZ3J/6DU07otWqzS6A==}
+    dependencies:
+      lodash-es: 4.17.21
+      utility-types: 3.11.0
+    dev: false
+
+  /@antv/x6-geometry@2.0.5:
+    resolution: {integrity: sha512-MId6riEQkxphBpVeTcL4ZNXL4lScyvDEPLyIafvWMcWNTGK0jgkK7N20XSzqt8ltJb0mGUso5s56mrk8ysHu2A==}
+    dev: false
+
+  /@antv/x6-vue-shape@2.1.2(@antv/x6@2.18.1)(vue@3.4.21):
+    resolution: {integrity: sha512-lfLNJ2ztK8NP2JBAWTD6m5Wol0u6tOqj2KdOhWZoT8EtEw9rMmAdxsr8uTi9MRJO9pDMM0nbsR3cidnMh7VeDQ==}
+    peerDependencies:
+      '@antv/x6': ^2.x
+      '@vue/composition-api': ^1.0.0-rc.1
+      vue: ^2.0.0 || >=3.0.0
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+    dependencies:
+      '@antv/x6': 2.18.1
+      vue: 3.4.21
+      vue-demi: 0.14.7(vue@3.4.21)
+    dev: false
+
+  /@antv/x6@2.18.1:
+    resolution: {integrity: sha512-FkWdbLOpN9J7dfJ+kiBxzowSx2N6syBily13NMVdMs+wqC6Eo5sLXWCZjQHateTFWgFw7ZGi2y9o3Pmdov1sXw==}
+    dependencies:
+      '@antv/x6-common': 2.0.17
+      '@antv/x6-geometry': 2.0.5
+      utility-types: 3.11.0
+    dev: false
+
   /@babel/helper-string-parser@7.24.1:
     resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==}
     engines: {node: '>=6.9.0'}
@@ -88,6 +134,19 @@ packages:
       '@babel/helper-validator-identifier': 7.22.20
       to-fast-properties: 2.0.0
 
+  /@ctrl/tinycolor@3.6.1:
+    resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
+    engines: {node: '>=10'}
+    dev: false
+
+  /@element-plus/icons-vue@2.3.1(vue@3.4.21):
+    resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
+    peerDependencies:
+      vue: ^3.2.0
+    dependencies:
+      vue: 3.4.21
+    dev: false
+
   /@esbuild/aix-ppc64@0.20.2:
     resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
     engines: {node: '>=12'}
@@ -295,6 +354,23 @@ packages:
     dev: true
     optional: true
 
+  /@floating-ui/core@1.6.0:
+    resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==}
+    dependencies:
+      '@floating-ui/utils': 0.2.1
+    dev: false
+
+  /@floating-ui/dom@1.6.3:
+    resolution: {integrity: sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==}
+    dependencies:
+      '@floating-ui/core': 1.6.0
+      '@floating-ui/utils': 0.2.1
+    dev: false
+
+  /@floating-ui/utils@0.2.1:
+    resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
+    dev: false
+
   /@jridgewell/sourcemap-codec@1.4.15:
     resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
 
@@ -474,6 +550,10 @@ packages:
     dev: true
     optional: true
 
+  /@sxzz/popperjs-es@2.11.7:
+    resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
+    dev: false
+
   /@trysound/sax@0.2.0:
     resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
     engines: {node: '>=10.13.0'}
@@ -483,6 +563,12 @@ packages:
     resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
     dev: true
 
+  /@types/lodash-es@4.17.12:
+    resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
+    dependencies:
+      '@types/lodash': 4.14.182
+    dev: false
+
   /@types/lodash@4.14.182:
     resolution: {integrity: sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==}
     dev: false
@@ -511,6 +597,10 @@ packages:
     resolution: {integrity: sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==}
     dev: false
 
+  /@types/web-bluetooth@0.0.16:
+    resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
+    dev: false
+
   /@types/web-bluetooth@0.0.20:
     resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
 
@@ -604,9 +694,25 @@ packages:
       - '@vue/composition-api'
       - vue
 
+  /@vueuse/core@9.13.0(vue@3.4.21):
+    resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
+    dependencies:
+      '@types/web-bluetooth': 0.0.16
+      '@vueuse/metadata': 9.13.0
+      '@vueuse/shared': 9.13.0(vue@3.4.21)
+      vue-demi: 0.14.7(vue@3.4.21)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+    dev: false
+
   /@vueuse/metadata@10.9.0:
     resolution: {integrity: sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==}
 
+  /@vueuse/metadata@9.13.0:
+    resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
+    dev: false
+
   /@vueuse/shared@10.9.0(vue@3.4.21):
     resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==}
     dependencies:
@@ -615,6 +721,15 @@ packages:
       - '@vue/composition-api'
       - vue
 
+  /@vueuse/shared@9.13.0(vue@3.4.21):
+    resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
+    dependencies:
+      vue-demi: 0.14.7(vue@3.4.21)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+    dev: false
+
   /acorn@8.11.3:
     resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
     engines: {node: '>=0.4.0'}
@@ -686,6 +801,10 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /async-validator@4.2.5:
+    resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
+    dev: false
+
   /asynckit@0.4.0:
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
     dev: false
@@ -919,6 +1038,13 @@ packages:
   /csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
+  /dagre@0.8.5:
+    resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==}
+    dependencies:
+      graphlib: 2.1.8
+      lodash: 4.17.21
+    dev: false
+
   /data-view-buffer@1.0.1:
     resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==}
     engines: {node: '>= 0.4'}
@@ -1074,6 +1200,31 @@ packages:
       domhandler: 4.3.1
     dev: true
 
+  /element-plus@2.7.1(vue@3.4.21):
+    resolution: {integrity: sha512-yk/vXFwJp0flMrd2kfcR0XlumhwtPjB19HJvwcf0n3DvRE7UK8LeSK14LVghSzk0TzPsFFElweMnZEEv7+MYuQ==}
+    peerDependencies:
+      vue: ^3.2.0
+    dependencies:
+      '@ctrl/tinycolor': 3.6.1
+      '@element-plus/icons-vue': 2.3.1(vue@3.4.21)
+      '@floating-ui/dom': 1.6.3
+      '@popperjs/core': /@sxzz/popperjs-es@2.11.7
+      '@types/lodash': 4.14.182
+      '@types/lodash-es': 4.17.12
+      '@vueuse/core': 9.13.0(vue@3.4.21)
+      async-validator: 4.2.5
+      dayjs: 1.11.10
+      escape-html: 1.0.3
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21)
+      memoize-one: 6.0.0
+      normalize-wheel-es: 1.2.0
+      vue: 3.4.21
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+    dev: false
+
   /emojis-list@3.0.0:
     resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
     engines: {node: '>= 4'}
@@ -1211,6 +1362,10 @@ packages:
       '@esbuild/win32-x64': 0.20.2
     dev: true
 
+  /escape-html@1.0.3:
+    resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+    dev: false
+
   /escape-string-regexp@1.0.5:
     resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
     engines: {node: '>=0.8.0'}
@@ -1436,6 +1591,12 @@ packages:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
     dev: true
 
+  /graphlib@2.1.8:
+    resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==}
+    dependencies:
+      lodash: 4.17.21
+    dev: false
+
   /has-ansi@2.0.0:
     resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==}
     engines: {node: '>=0.10.0'}
@@ -1836,6 +1997,22 @@ packages:
       pkg-types: 1.1.0
     dev: true
 
+  /lodash-es@4.17.21:
+    resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+    dev: false
+
+  /lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
+    resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
+    peerDependencies:
+      '@types/lodash-es': '*'
+      lodash: '*'
+      lodash-es: '*'
+    dependencies:
+      '@types/lodash-es': 4.17.12
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+    dev: false
+
   /lodash@4.17.21:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
     dev: false
@@ -1861,6 +2038,10 @@ packages:
     resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
     dev: true
 
+  /memoize-one@6.0.0:
+    resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
+    dev: false
+
   /merge-options@1.0.1:
     resolution: {integrity: sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==}
     engines: {node: '>=4'}
@@ -1987,6 +2168,10 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /normalize-wheel-es@1.2.0:
+    resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
+    dev: false
+
   /normalize.css@8.0.1:
     resolution: {integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==}
     dev: false
@@ -2814,6 +2999,11 @@ packages:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
     dev: true
 
+  /utility-types@3.11.0:
+    resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
+    engines: {node: '>= 4'}
+    dev: false
+
   /validator@13.11.0:
     resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==}
     engines: {node: '>= 0.10'}

+ 45 - 0
src/api/etaChart/etaChart.js

@@ -0,0 +1,45 @@
+import {get,post} from '@/api/index'
+
+export default{
+    /* 图表分类
+     * ParentId:0 表示查询未分类目录下的内容,-1表示查询所有一级目录
+    */
+    classifyList:params=>{
+        return get('/chart/classify/simple',params)
+    },
+    //新增分类 一级分类传0
+    classifyAdd:params=>{
+        return post('/chart/classify/add',params)
+    },
+    //编辑分类
+    classifyEdit:params=>{
+        return post('/chart/classify/edit',params)
+    },
+    //删除分类
+    classifyDelete:params=>{
+        return post('/chart/classify/delete',params)
+    },
+    // 分类或者图表的移动排序
+    classifySort:params=>{
+        return post('/chart/classify/move',params)
+    },
+
+    // 图列表
+    chartList:params=>{
+        return get('/chart/classify/chart_list',params)
+    },
+    // 图搜索
+    chartSearch:params=>{
+        return get('/chart/search_by_es',params)
+    },
+    // 图详情
+    chartDetail:params=>{
+        return get('/chart/detail',params)
+    },
+    // 指标溯源
+    getEdbSource:params=>{
+        return get('/edb/trace',params)
+    }
+
+
+}

+ 5 - 0
src/api/etaChart/index.js

@@ -0,0 +1,5 @@
+import apiETAChart from './etaChart'
+
+export {
+    apiETAChart,
+}

+ 3 - 2
src/api/index.js

@@ -14,6 +14,7 @@ const LOADINGWHITELIST=[]
 // 请求数
 let LOADINGCOUNT = 0;
 let LOADING;
+// 这里搞这个hook是为了如果要全局设置接口loading 就用这个
 const {isLoading,startLoading,closeLoading}=useRequestLoading()
 
 let config = {
@@ -34,7 +35,7 @@ _axios.interceptors.request.use(
       LOADINGCOUNT++;
     }
     
-    config.headers.Authorization=localStorage.getItem('token')||''
+    config.headers.Authorization=sessionStorage.getItem('token')||''
     return config;
   },
   function (error) {
@@ -71,7 +72,7 @@ _axios.interceptors.response.use(
     }
     if(data.Ret===408){//token失效
       MessagePlugin.warning(data.Msg)
-      router.replace('/login')
+      
     }
     if(data.Ret===403){
       setTimeout(() => {

+ 8 - 0
src/api/system/common.js

@@ -0,0 +1,8 @@
+import {get,post} from '@/api/index'
+
+export default{
+    //机构用户
+    companyUserList:()=>{
+        return get('/admin/business/admin',{})
+    }
+}

+ 8 - 0
src/api/system/index.js

@@ -0,0 +1,8 @@
+import apiSystemSet from './set'
+import apiSystemCommon from './common'
+
+export {
+    apiSystemSet,
+    apiSystemCommon
+}
+

+ 8 - 0
src/api/system/set.js

@@ -0,0 +1,8 @@
+import {get,post} from '@/api/index'
+
+export default{
+    //登录(根据code)
+    loginWithCode:params=>{
+        return post('/login/auth_code_login',params)
+    }
+}

BIN
src/assets/imgs/nodata.png


BIN
src/assets/imgs/tree_closed.png


BIN
src/assets/imgs/tree_opend.png


+ 3 - 0
src/assets/svg/edb_source.svg

@@ -0,0 +1,3 @@
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9.79165 3.33337C13.3586 3.33337 16.25 6.22472 16.25 9.7917C16.25 10.2693 16.198 10.7403 16.0963 11.1986L16.0556 11.3699L15.0918 11.1281C15.2011 10.6949 15.2564 10.2471 15.2564 9.7917C15.2564 6.77351 12.8098 4.32696 9.79165 4.32696C6.77345 4.32696 4.3269 6.77351 4.3269 9.7917C4.3269 12.8099 6.77345 15.2564 9.79165 15.2564C11.0708 15.2585 12.3098 14.8099 13.2911 13.9893L13.3268 13.9585L9.44422 10.1464L10.1391 9.43666L14.1528 13.3772L14.7477 13.9625L14.3999 14.3168C13.7994 14.9299 13.0823 15.4167 12.291 15.7487C11.4996 16.0807 10.6498 16.2512 9.79165 16.25C6.22466 16.25 3.33331 13.3587 3.33331 9.7917C3.33331 6.22472 6.22466 3.33337 9.79165 3.33337ZM9.79165 6.64534C10.2582 6.64525 10.719 6.74894 11.1406 6.94889C11.5621 7.14884 11.934 7.44004 12.2291 7.80141C12.5242 8.16279 12.7353 8.58528 12.8471 9.03828C12.9588 9.49129 12.9684 9.96348 12.8751 10.4206L12.8476 10.5432L11.8825 10.3067C11.9938 9.85448 11.9555 9.37834 11.7732 8.94974C11.5909 8.52115 11.2746 8.16322 10.8717 7.92966C10.4687 7.69611 10.0009 7.59952 9.53843 7.65441C9.07593 7.70929 8.64371 7.91267 8.30662 8.23405C7.96953 8.55542 7.74575 8.97744 7.66887 9.43678C7.59198 9.89613 7.66613 10.368 7.88019 10.7816C8.09426 11.1953 8.43668 11.5283 8.85609 11.7308C9.2755 11.9333 9.74927 11.9943 10.2063 11.9047L10.3073 11.8825L10.5441 12.8473C10.112 12.9534 9.66223 12.9667 9.2246 12.8861C8.78696 12.8056 8.37137 12.6332 8.00528 12.3802C7.6392 12.1273 7.33093 11.7995 7.10083 11.4186C6.87074 11.0378 6.72405 10.6124 6.67046 10.1707C6.61686 9.72892 6.65757 9.28084 6.78991 8.85598C6.92225 8.43113 7.1432 8.03919 7.43817 7.70602C7.73314 7.37285 8.09543 7.10604 8.50112 6.9232C8.90681 6.74037 9.34666 6.64566 9.79165 6.64534Z" fill="#0052D9"/>
+</svg>

+ 0 - 4
src/assets/svg/tree_close.svg

@@ -1,4 +0,0 @@
-<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.23642 8.48046H7.51642V10.7605C7.51642 10.8265 7.57042 10.8805 7.63642 10.8805H8.35642C8.42242 10.8805 8.47642 10.8265 8.47642 10.7605V8.48046H10.7564C10.8224 8.48046 10.8764 8.42646 10.8764 8.36046V7.64046C10.8764 7.57446 10.8224 7.52046 10.7564 7.52046H8.47642V5.24046C8.47642 5.17446 8.42242 5.12046 8.35642 5.12046H7.63642C7.57042 5.12046 7.51642 5.17446 7.51642 5.24046V7.52046H5.23642C5.17042 7.52046 5.11642 7.57446 5.11642 7.64046V8.36046C5.11642 8.42646 5.17042 8.48046 5.23642 8.48046Z" fill="#333333"/>
-<path d="M13.5168 2H2.47677C2.21127 2 1.99677 2.2145 1.99677 2.48V13.52C1.99677 13.7855 2.21127 14 2.47677 14H13.5168C13.7823 14 13.9968 13.7855 13.9968 13.52V2.48C13.9968 2.2145 13.7823 2 13.5168 2ZM12.9168 12.92H3.07677V3.08H12.9168V12.92Z" fill="#333333"/>
-</svg>

+ 0 - 4
src/assets/svg/tree_opend.svg

@@ -1,4 +0,0 @@
-<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 8.5H11V7.5H5V8.5Z" fill="#333333"/>
-<path d="M3 14C2.44772 14 2 13.5523 2 13V3C2 2.44771 2.44772 2 3 2H13C13.5523 2 14 2.44772 14 3L14 13C14 13.5523 13.5523 14 13 14L3 14ZM3 13L13 13L13 3L3 3L3 13Z" fill="#333333"/>
-</svg>

+ 150 - 0
src/components/EdbSourceToolTip.vue

@@ -0,0 +1,150 @@
+<script setup>
+import { inject, nextTick, onMounted, ref } from 'vue'
+import { Node } from '@antv/x6'
+
+const getNode = inject('getNode')
+const getGraph = inject('getGraph')
+
+const data = ref({
+  fold: false,
+  routeQuery: {}
+})
+const show = ref(false)
+let node = {}, graph = {};
+
+function initNode() {
+  node = getNode();
+  graph = getGraph()
+  node.data = node.data ? { ...data.value, ...node.data } : data.value
+  data.value = node.data
+
+  let { style } = data.value
+  initStyle(style)
+}
+
+const container = ref(null)
+const antvTooltip = ref(null)
+const containerText = ref(null)
+function initStyle(style) {
+  // !important 是有的节点 :hover不变色
+  if (style) {
+    // 背景色
+    if (style.backgroundColor && style.backgroundColor.indexOf("!important") != -1) {
+      let value = style.backgroundColor.split("!")[0]
+      containerText.value.style.setProperty('background-color', value, 'important')
+    } else {
+      container.value.style.backgroundColor = style.backgroundColor ? style.backgroundColor :
+        data.value.isRoot ? '#0052d9' : "#f2f6fa"
+    }
+    // 字体颜色
+    if (style.color && style.color.indexOf("!important") != -1) {
+      let value = style.color.split("!")[0]
+      containerText.value.style.setProperty('color', value, 'important')
+    } else {
+      container.value.style.color = style.color ? style.color :
+        data.value.isRoot ? '#ffffff' : "#000000"
+    }
+    nextTick(()=>{
+        containerText.value.style.color = style.color
+    })
+    
+  }
+}
+
+function flodApi(event) {
+  const succ = graph.getSuccessors(node)
+  if (succ) {
+    succ.forEach((node, index) => {
+      node.setVisible(data.value.fold)
+      if (data.value.fold) {
+        node.toBack()
+        requestAnimationFrame(() => {
+          let edge = graph.getIncomingEdges(node) || []
+          edge[0].toBack()
+        })
+      }
+      node.data.fold = !data.value.fold
+    })
+  }
+  data.value.fold = !data.value.fold
+}
+
+function nodeMouseEnter() {
+  show.value = true
+}
+function nodeMouseOut() {
+  show.value = false
+}
+
+onMounted(() => {
+  initNode()
+})
+
+
+
+
+</script>
+
+<template>
+  <div
+    class="container"
+    ref="container"
+    @mouseover="nodeMouseEnter"
+    @mouseout="nodeMouseOut"
+  >
+    <t-tooltip
+      :content="data.RuleTitle"
+      class="container-input"
+      ref="antvTooltip"
+    >
+      <span ref="containerText">{{ data.EdbName }}</span>
+    </t-tooltip>
+    <t-icon
+      :name="data.fold ? 'add-circle' : 'minus-circle'"
+      v-if="!data.isLeaf"
+      @click="flodApi"
+      v-show="show"
+      class="fold-icon"
+    />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  height: 100%;
+  width: 100%;
+  box-sizing: border-box;
+  background-color: #0052d9;
+  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
+  display: flex;
+  cursor: pointer;
+  position: relative;
+  border-radius: 4px;
+  .container-input {
+    text-align: center;
+    font-size: 16px;
+    color: #ffffff;
+    font-weight: 400;
+    word-break: break-all;
+  }
+  //   &:hover {
+  //     background-color: #ecf5ff;
+  //     .container-input {
+  //       color: #0052d9 !important;
+  //       text-decoration: underline;
+  //     }
+  //   }
+  .fold-icon {
+    position: absolute;
+    font-size: 24px;
+    color: #666666;
+    bottom: -24px;
+    left: 50%;
+    transform: translateX(-50%);
+  }
+}
+</style>

+ 21 - 0
src/components/Empty.vue

@@ -0,0 +1,21 @@
+<script setup>
+
+</script>
+
+<template>
+    <div class="empty-wrap">
+        <img src="@/assets/imgs/nodata.png" alt="">
+        <p>暂无数据</p>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.empty-wrap{
+    text-align: center;
+    color: #666;
+    img{
+        width: 200px;
+        margin-bottom: 20px;
+    }
+}
+</style>

+ 3 - 2
src/main.js

@@ -12,11 +12,12 @@ import svgIcon from "@/components/SvgIcon.vue";
 //引入注册脚本
 import 'virtual:svg-icons-register'
 
+import EmptyWrap from '@/components/Empty.vue'
 
-const app= createApp(App)
 
-// reportErr(app)//设置全局错误上报
+const app= createApp(App)
 
 app.component('svg-icon', svgIcon)
+app.component('empty-wrap', EmptyWrap)
 app.use(router)
 app.mount('#app')

+ 18 - 2
src/router/index.js

@@ -2,10 +2,18 @@ import { createRouter, createWebHistory } from "vue-router";
 import LayoutIndex from "@/layout/Index.vue";
 
 const routes = [
+  {
+    path:'/autoLogin',
+    name:'AutoLogin',
+    component:()=>import('@/views/AutoLogin.vue'),
+    meta:{
+      title:'ETA社区'
+    },
+  },
   {
     path:'/',
     name:'LayoutIndex',
-    redirect: '/etaChart/index',
+    redirect: '/autoLogin',
     component:LayoutIndex,
     meta:{
       title:'ETA社区'
@@ -18,7 +26,15 @@ const routes = [
         meta:{
           title:'ETA图库'
         },
-      }
+      },
+      {
+        path:'EDBSource',
+        name:'EDBSource',
+        component:()=>import('@/views/EDBSource.vue'),
+        meta:{
+          title:'指标溯源'
+        },
+      },
     ]
   },
   {

+ 39 - 0
src/views/AutoLogin.vue

@@ -0,0 +1,39 @@
+<script setup>
+import {apiSystemSet} from '@/api/system'
+import { useRoute, useRouter } from 'vue-router'
+
+const route=useRoute()
+const router=useRouter()
+
+function init(){
+    const code=route.query.code
+    if(!code) return
+    apiSystemSet.loginWithCode({
+        AuthCode:code
+    }).then(res=>{
+        if(res.Ret===200){
+            sessionStorage.setItem('token',res.Data.Authorization)
+            sessionStorage.setItem('userInfo',JSON.stringify(res.Data))
+            router.replace('/etaChart/index')
+        }
+    })
+}
+init()
+
+</script>
+
+<template>
+    <div class="auto-login-page">
+        登录中...
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.auto-login-page{
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+</style>

+ 376 - 0
src/views/EDBSource.vue

@@ -0,0 +1,376 @@
+<script setup>
+import { nextTick, ref } from 'vue'
+import { apiETAChart } from '@/api/etaChart'
+import { useRoute, useRouter } from 'vue-router'
+import { Graph } from '@antv/x6';
+import { register, getTeleport } from '@antv/x6-vue-shape'
+import dagre from "dagre"
+import EdbSourceToolTip from '@/components/EdbSourceToolTip.vue';
+
+
+const route = useRoute()
+const router = useRouter()
+
+const treeData = ref({})
+async function getDetail() {
+  if (!route.query.code) return
+  const res = await apiETAChart.getEdbSource({
+    UniqueCode: route.query.code
+  })
+  if (res.Ret === 200) {
+    treeData.value = res.Data;
+    nextTick(() => {
+      init()
+    })
+  }
+}
+getDetail()
+
+let graph = null, mould = ref(null), mouldText = ref(null);
+let params = {
+  stopNodeClick: false
+}
+
+
+const TeleportContainer = getTeleport()
+
+function init() {
+  graph = new Graph({
+    container: document.getElementById('sand-chart-body'),
+    autoResize: false,
+    async: true,//不异步时,显示隐藏会卡死
+    panning: true,
+    background: {
+      color: '#fff',
+    },
+    scroller: {
+      enabled: true,
+      pannable: true,
+      minVisibleWidth: 50,
+      minVisibleHeight: 50,
+    },
+    interacting: {
+      nodeMovable: false,
+      magnetConnectable: false,
+      edgeMovable: false,
+      edgeLabelMovable: false,
+      arrowheadMovable: false,
+      vertexMovable: false,
+      vertexMovable: false,
+      vertexDeletable: false
+    },
+    mousewheel: {
+      enabled: true,
+      modifiers: ['ctrl', 'meta']
+    }, //滚轮缩放
+    scaling: {
+      min: 0.5,
+      max: 2
+    },
+    //小地图
+    // minimap: {
+    //   enabled: true,
+    //   container: document.getElementById("minimap"),
+    // }
+  })
+
+  graph.on('node:mousemove', () => {
+    // 拖动画布的时候 阻止节点的点击操作
+    params.stopNodeClick = true
+  })
+
+  graph.on('node:mouseup', () => {
+    // 延迟释放
+    requestAnimationFrame(() => {
+      params.stopNodeClick = false
+    })
+  })
+
+  register({
+    shape: 'vue-shape',
+    component: EdbSourceToolTip,
+  })
+
+
+  Graph.registerEdge(
+    'org-edge',
+    {
+      attrs: {
+        line: {
+          strokeWidth: 1,
+          stroke: '#dddddd',
+          sourceMarker: null,
+          targetMarker: null,
+        },
+      },
+      zIndex: 0
+    },
+    true,
+  )
+
+
+  mouldText.value = document.getElementById('mould-text')
+  let edbName = treeData.value.EdbName + (treeData.value.IsStop ? '(暂停更新)' : '')
+  mouldText.value.innerText = edbName
+  mould.value = document.getElementById('mould')
+
+  let node = graph.createNode({
+    shape: 'vue-shape',
+    component: 'custom-rect',
+    width: mould.value.offsetWidth + 1,
+    height: mould.value.offsetHeight,
+    attrs: {
+      body: {
+        rx: 4,
+        ry: 4,
+        strokeWidth: 1,
+        class: 'body-class'
+      },
+    },
+    data: {
+      EdbName: edbName,
+      RuleTitle: treeData.value.RuleTitle,
+      routeQuery: {
+        ClassifyId: treeData.value.ClassifyId,
+        UniqueCode: treeData.value.UniqueCode,
+        EdbInfoId: treeData.value.EdbInfoId,
+        EdbInfoType: treeData.value.EdbInfoType,
+        HaveOperaAuth: treeData.value.HaveOperaAuth
+      },
+      isRoot: true,
+      isLeaf: (treeData.value.Child && treeData.value.Child.length > 0) ? false : true,
+      style: {
+        color: treeData.value.IsStop ? "red!important" : '#ffffff!important',
+      },
+    }
+  })
+  let cells = [node, ...createCells(treeData.value.Child, node)]
+  graph.resetCells(cells)
+  layout()
+  graph.positionCell(node, 'top', { padding: { top: 20 } })
+}
+
+function createCells(list, parentNode) {
+  if (!(list && list.length > 0)) {
+    return []
+  }
+  let dataList = []
+  list.forEach(element => {
+    let edbName = element.EdbName + (element.IsStop ? '(暂停更新)' : '')
+
+    mouldText.value.innerText = edbName
+    let node = graph.createNode({
+      shape: 'vue-shape',
+      component: 'custom-rect',
+      width: mould.value.offsetWidth + 1,
+      height: mould.value.offsetHeight,
+      attrs: {
+        body: {
+          rx: 4,
+          ry: 4,
+          strokeWidth: 1,
+          class: 'body-class'
+        },
+      },
+      data: {
+        style: {
+          backgroundColor: '#f2f6fa',
+          color: element.IsStop ? "red!important" : '#000000',
+        },
+        EdbName: edbName,
+        RuleTitle: element.RuleTitle,
+        routeQuery: {
+          ClassifyId: element.ClassifyId,
+          UniqueCode: element.UniqueCode,
+          EdbInfoId: element.EdbInfoId,
+          EdbInfoType: element.EdbInfoType,
+          HaveOperaAuth: treeData.value.HaveOperaAuth
+        },
+        isRoot: false,
+        isLeaf: (element.Child && element.Child.length > 0) ? false : true
+      }
+    })
+    let side = graph.createEdge({
+      shape: 'org-edge',
+      source: { cell: parentNode.id },
+      target: { cell: node.id },
+    })
+    dataList.push(node)
+    dataList.push(side)
+    dataList = [...dataList, ...createCells(element.Child, node)]
+  });
+  return dataList
+}
+
+function layout() {
+  const dir = 'TB'
+  const nodes = graph.getNodes()
+  const edges = graph.getEdges()
+  const g = new dagre.graphlib.Graph()
+
+  g.setDefaultEdgeLabel(() => ({}))
+  nodes.forEach((node) => {
+    g.setNode(node.id, { width: node.size().width, height: node.size().height })
+  })
+
+  let heights = nodes.map(item => {
+    // 为了防止重叠,不知道他内部怎么排的,先调整这样。
+    if (item.data.isLeaf) {
+      return item.size().height * 1.5 - 100
+    } else {
+      return item.size().height / 2
+    }
+  })
+  let maxHeight = Math.max(...heights)
+  // console.log(maxHeight,'maxHeight');
+  g.setGraph({ rankdir: dir, nodesep: 50, ranksep: maxHeight })
+  edges.forEach((edge) => {
+    const source = edge.getSource()
+    const target = edge.getTarget()
+    g.setEdge(source.cell, target.cell)
+  })
+
+  dagre.layout(g)
+
+  g.nodes().forEach((id) => {
+    const node = graph.getCellById(id)
+    if (node) {
+      const pos = g.node(id)
+      node.position(pos.x, pos.y)
+    }
+  })
+  edges.forEach((edge) => {
+    const source = edge.getSourceNode()
+    const target = edge.getTargetNode()
+    const sourceBBox = source.getBBox()
+    const targetBBox = target.getBBox()
+
+    if ((dir === 'LR' || dir === 'RL') && sourceBBox.y !== targetBBox.y) {
+      const gap =
+        dir === 'LR'
+          ? targetBBox.x - sourceBBox.x - sourceBBox.width
+          : -sourceBBox.x + targetBBox.x + targetBBox.width
+      const fix = dir === 'LR' ? sourceBBox.width : 0
+      const x = sourceBBox.x + fix + gap / 2
+      edge.setVertices([
+        { x, y: sourceBBox.center.y },
+        { x, y: targetBBox.center.y },
+      ])
+    } else if (
+      (dir === 'TB' || dir === 'BT')) {
+      const gap =
+        dir === 'TB'
+          ? targetBBox.y - sourceBBox.y - sourceBBox.height
+          : -sourceBBox.y + targetBBox.y + targetBBox.height
+      const fix = dir === 'TB' ? sourceBBox.height : 0
+      const y = sourceBBox.y + fix + gap / 2
+
+      edge.setVertices([
+        { x: sourceBBox.center.x, y },
+        { x: targetBBox.center.x, y },
+      ])
+    } else {
+      edge.setVertices([])
+    }
+  })
+}
+
+
+</script>
+
+<template>
+  <div class="edb-source-page">
+    <div class="edb-title">{{ treeData.EdbName || "" }}</div>
+    <div class="edb-source-wrap">
+      <div class="sandbox-body">
+        <div class="sand-chart-body" id="sand-chart-body"></div>
+        <TeleportContainer />
+        <div id="minimap" class="minimap"></div>
+      </div>
+    </div>
+    <!-- 确定每个Node节点大小用 -->
+    <div id="mould"><span id="mould-text"></span></div>
+  </div>
+</template>
+
+
+<style lang="scss">
+.sandbox-body {
+  .x6-graph-scroller {
+    flex: 1;
+  }
+
+  .x6-port-body {
+    display: none;
+  }
+
+  /* reseize 框样式 */
+  .x6-widget-transform {
+    .x6-widget-transform-resize {
+      border-radius: 0;
+    }
+  }
+  .x6-widget-minimap-viewport {
+    border-color: red;
+    .x6-widget-minimap-viewport-zoom {
+      border-color: red;
+    }
+  }
+  .x6-widget-minimap {
+    width: auto !important;
+    height: auto !important;
+  }
+}
+</style>
+<style scoped lang="scss">
+.edb-source-page {
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 120px);
+  overflow: hidden;
+  background-color: #fff;
+  border-radius: 4px;
+  border: 1px solid #c8cdd9;
+  box-sizing: border-box;
+  // padding:30px;
+  .edb-title {
+    margin: 0 -30px;
+    text-align: center;
+    padding: 30px;
+    border-bottom: 1px solid #c8cdd9;
+    font-size: 16px;
+  }
+  .edb-source-wrap {
+    text-align: center;
+    flex: 1;
+    overflow: auto;
+    .sandbox-body {
+      height: 100%;
+      display: flex;
+      position: relative;
+      .minimap {
+        position: absolute;
+        right: 6px;
+        bottom: 6px;
+        box-sizing: border-box;
+      }
+      #sand-chart-body {
+        flex: 1;
+      }
+    }
+  }
+  #mould {
+    position: absolute;
+    max-width: 100px;
+    padding: 20px;
+    background-color: red;
+    font-size: 16px;
+    text-align: center;
+    border-radius: 4px;
+    top: -10000px;
+    opacity: 0;
+    word-break: break-all;
+    box-sizing: content-box;
+  }
+}
+</style>

+ 57 - 30
src/views/etaChart/Index.vue

@@ -1,10 +1,15 @@
 <script setup>
 import { ref } from 'vue'
 import ClassifyWrapVue from "./components/ClassifyWrap.vue";
+import ChartWrap from './components/ChartWrap.vue'
+import { apiETAChart } from '@/api/etaChart'
+import {useClassify} from './hooks/useClassify'
+
+const {classifyActived,userVal} =useClassify()
 
 const tableColOpts = [
   {
-    colKey: 'ChartName',
+    colKey: 'ChartClassifyName',
     title: '图表名称',
     width: '450px'
   },
@@ -14,45 +19,63 @@ const tableColOpts = [
     width: '100px'
   },
   {
-    colKey: 'AdminName',
+    colKey: 'SysUserRealName',
     title: '创建人',
     width: '80px'
   }
 ]
+let page = 1
+const pageSize = 20
+let finished = false
 const tableData = ref([])
 const tableLoading = ref(false)
-function getTableData() {
+async function getTableData(data) {
   tableLoading.value = true
-  setTimeout(() => {
-    for (let i = 0; i < 30; i++) {
-      tableData.value.push({
-        index: i + 1,
-        ChartName: '图表名称很长的附近的拉进来放到手机里尽快费德勒就看撒就进口法',
-        Company: '弘则研究研究',
-        AdminName: '管理眼研',
-      });
-    }
-    tableLoading.value = false
-  }, 1500);
+  const res = await apiETAChart.chartList({
+    PageSize: pageSize,
+    CurrentIndex: page,
+    ChartClassifyId:classifyActived.value||0,
+    SysUserIds:userVal.value?.join(',')||''
+  })
+  tableLoading.value = false
+  if (res.Ret === 200) {
+    const arr = res.Data.AllNodes || []
+    tableData.value = [...tableData.value, ...arr]
+    finished = res.Data.Paging.IsEnd
+  }
 }
 getTableData()
 function tableScroll(opt) {
-  if (tableLoading.value) return
+  if (tableLoading.value || finished) return
   if (opt.scrollBottom < 100) {
+    page++
     getTableData()
   }
 }
 
+function handleFilterList() {
+  activeChartId.value=''
+  tableData.value=[]
+  finished=false
+  page=1
+  getTableData()
+  // 取消掉右侧图表
+  activeChartId.value=''
+}
+
+
+const activeChartId = ref('')
+function handleSelectChart(item) {
+  activeChartId.value = item.ChartInfoId
+}
+
 </script>
 
 <template>
   <div class="eta-chart-page">
-    <ClassifyWrapVue />
-    <div class="table-wrap" v-loading='tableLoading'>
-      <t-list
-        style="height: 100%"
-        :onScroll="tableScroll"
-      >
+    <ClassifyWrapVue @filter="handleFilterList" @change="handleSelectChart" />
+    <div class="table-wrap" v-loading="tableLoading">
+      <t-list style="height: 100%" :onScroll="tableScroll">
         <table class="table-box" cellpadding="0" cellspacing="0">
           <thead class="table-head thead-sticky">
             <tr>
@@ -66,7 +89,12 @@ function tableScroll(opt) {
             </tr>
           </thead>
           <tbody>
-            <tr v-for="row in tableData" :key="row.index">
+            <tr
+              :class="row.ChartInfoId === activeChartId ? 'active_row' : ''"
+              v-for="row in tableData"
+              :key="row.index"
+              @click="handleSelectChart(row)"
+            >
               <td
                 v-for="opt in tableColOpts"
                 :key="opt.colKey"
@@ -77,9 +105,10 @@ function tableScroll(opt) {
             </tr>
           </tbody>
         </table>
+        <empty-wrap v-if="tableData.length===0"/>
       </t-list>
     </div>
-    <div class="bg-white chart-wrap"></div>
+    <ChartWrap :chartInfoId="activeChartId"/>
   </div>
 </template>
 
@@ -111,18 +140,16 @@ function tableScroll(opt) {
         top: 0;
       }
       .table-head {
-        background-color: #EBEEF5;
+        background-color: #ebeef5;
         font-weight: bold;
       }
-      tbody{
+      tbody {
         color: #666;
+        .active_row{
+          background-color: #ECF5FF;
+        }
       }
     }
   }
-  .chart-wrap{
-    flex: 1;
-    min-width: 600px;
-    border: 1px solid #DCDFE6;
-  }
 }
 </style>

+ 179 - 0
src/views/etaChart/components/ChartWrap.vue

@@ -0,0 +1,179 @@
+<script setup>
+import { watch, nextTick, ref } from "vue"
+import { apiETAChart } from '@/api/etaChart'
+import { useChartRender } from '@/hooks/chart/render'
+import { useRouter } from "vue-router"
+
+const router = useRouter()
+
+const { options, axisLimitState, chartRender, setLimitData, isUseSelfLimit } = useChartRender()
+
+const props = defineProps({
+  chartInfoId: {
+    type: [Number, String],
+    default: ''
+  }
+})
+
+watch(
+  () => props.chartInfoId,
+  (n) => {
+    if(!n){
+      tableData.value=[]
+      intro.value=''
+      chartInfo.value=null
+      return
+    }
+    getChartDetail()
+  }
+)
+
+const columns = [
+  {
+    colKey: 'EdbName',
+    title: '指标名称',
+    align: 'center'
+  },
+  {
+    colKey: 'Frequency',
+    title: '更新频度',
+    width: '100',
+    align: 'center'
+  },
+  {
+    colKey: 'LatestDate',
+    title: '最新日期',
+    width: '120',
+    align: 'center'
+  },
+  {
+    colKey: 'LatestValue',
+    title: '最新值',
+    width: '100',
+    align: 'center'
+  },
+  {
+    colKey: 'SourceName',
+    title: '输据来源',
+    width: '150',
+    align: 'center'
+  }
+]
+const tableData = ref([])
+const intro = ref('')
+const chartInfo = ref(null)
+async function getChartDetail() {
+
+  const res = await apiETAChart.chartDetail({
+    ChartInfoId: props.chartInfoId
+  })
+  if (res.Ret === 200) {
+    tableData.value = res.Data.EdbInfoList || []
+    intro.value = res.Data.ChartInfo.Description
+    chartInfo.value = res.Data.ChartInfo
+    nextTick(() => {
+      chartRender({
+        data: {
+          ...res.Data,
+          ChartInfo: {
+            ...res.Data.ChartInfo,
+            Calendar: '公历'
+          },
+        },
+        renderId: 'chart-box',
+        // lang:currentLang.value,
+        changeLangIsCheck: false,
+        showChartTitle: true
+      })
+    })
+  }
+}
+
+
+// 跳转指标溯源
+function handleGoEdbSource(data) {
+  const href = router.resolve({
+    path: '/EDBSource',
+    query: {
+      code: data.UniqueCode
+    }
+  }).href
+  window.open(href, "_blank")
+}
+
+
+
+</script>
+
+<template>
+  <div class="bg-white chart-wrap">
+    <template v-if="props.chartInfoId">
+      <div class="chart-render-wrap">
+        <div class="chart-box" id="chart-box"></div>
+        <div class="chart-source" v-if="chartInfo">
+          <span
+            v-if="
+              chartInfo.SourcesFrom && JSON.parse(chartInfo.SourcesFrom).isShow
+            "
+            :style="`color: ${
+              JSON.parse(chartInfo.SourcesFrom).color
+            };fontSize: ${JSON.parse(chartInfo.SourcesFrom).fontSize}px;
+                          `"
+            >来源:{{ JSON.parse(chartInfo.SourcesFrom).text }}</span
+          >
+        </div>
+      </div>
+      <div class="table-wrap">
+        <t-table
+          row-key="index"
+          :data="tableData"
+          :columns="columns"
+          bordered
+          hover
+          max-height="300"
+          cell-empty-content="-"
+          resizable
+        >
+          <template #SourceName="{ row }">
+            <span>{{ row.SourceName }}</span>
+            <!-- 指标溯源 -->
+            <svg-icon
+              v-if="row.EdbType === 2"
+              style="
+                font-size: 20px;
+                cursor: pointer;
+                position: relative;
+                top: 5px;
+              "
+              name="edb_source"
+              @click="handleGoEdbSource(row)"
+            ></svg-icon>
+          </template>
+        </t-table>
+      </div>
+      <div class="instructions-wrap" v-if="intro">
+        <p>逻辑简述:</p>
+        <p>{{ intro }}</p>
+      </div>
+    </template>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.chart-wrap {
+  flex: 1;
+  min-width: 600px;
+  border: 1px solid #dcdfe6;
+  padding: 30px;
+  .chart-render-wrap {
+    margin-bottom: 20px;
+    .chart-box {
+      height: 600px;
+    }
+  }
+  .instructions-wrap {
+    margin-top: 20px;
+    line-height: 1.7;
+  }
+}
+</style>

+ 508 - 74
src/views/etaChart/components/ClassifyWrap.vue

@@ -1,106 +1,502 @@
 <script setup>
-import { ref } from 'vue'
-import { SearchIcon,Icon  } from 'tdesign-icons-vue-next';
+import { reactive, ref, watch } from 'vue'
+import { SearchIcon, Icon } from 'tdesign-icons-vue-next';
+import { apiETAChart } from '@/api/etaChart'
+import {apiSystemCommon} from '@/api/system'
+import { ElTree } from 'element-plus'
+import 'element-plus/es/components/tree/style/css'
+import {useClassify} from '../hooks/useClassify'
 
-const companyOpts = [{ label: '弘则研究', value: 1 }]
-const companyVal = ref('')
+const emits = defineEmits(['change','filter'])
 
-const userOpts = [{ label: '弘则研究', value: 1 }]
-const userVal = ref('')
+const userProps={
+  label:'RealName',
+  value:'AdminId',
+  children:'ChildrenList'
+}
+const userOpts = ref([])
+const {userVal}=useClassify()
+async function getCompanyUserData(){
+  const res=await apiSystemCommon.companyUserList()
+  if(res.Ret===200){
+    userOpts.value=res.Data.List||[]
+  }
+}
+getCompanyUserData()
 
+const searchSelectKeys={
+  value:'ChartInfoId',
+  label:'ChartName'
+}
 const searchVal = ref('')
+const searchOpts=ref([])
+const searchLoading=ref(false)
+let searchPage=1
+const searchPageSize=20
+let finished=false
+function handleSearchChart(){
+  finished=false
+  searchPage=1
+  searchOpts.value=[]
+  handleGetSearchChartList()
+}
+async function handleGetSearchChartList(){
+  searchLoading.value=true
+  const res=await apiETAChart.chartSearch({
+    PageSize:searchPageSize,
+    CurrentIndex:searchPage,
+    SysUserIds:'',
+    Keyword:searchVal.value
+  })
+  searchLoading.value=false
+  if(res.Ret===200){
+    const arr=res.Data.List||[]
+    searchOpts.value=[...searchOpts.value,...arr]
+    finished=res.Data.Paging.IsEnd
+  }
+}
+async function handleLoadMoreChart(){
+  if(finished||searchLoading.value) return
+  handleGetSearchChartList()
+}
+function handleSelectChart(value,context){
+  if(value){
+    emits('change',context.option)
+  }
+}
+
+function handleUserChange(){
+  emits('filter')
+  getClassify()
+}
+
 
 
-const classifyList = ref([{
-  label: '第一段',
-  children: [
-    {
-      label: '第二段',
-    },
-    {
-      label: '第二段',
-    },
-  ],
-},
-{
-  label: '第一段',
-  children: [
-    {
-      label: '第二段',
-    },
-    {
-      label: '第二段',
-    },
-  ],
-},
-{
-  label: '第一段',
-  children: [
-    {
-      label: '第二段',
-    },
-    {
-      label: '第二段',
-    },
-  ],
-},
-{
-  label: '第一段',
-  children: [
-    {
-      label: '第二段',
-    },
-    {
-      label: '第二段',
-    },
-  ],
-},])
+//分类列表
+const classifyTreeKeys = {
+  label: 'ChartClassifyName',
+  children: 'Children',
+  isLeaf: 'isLeaf'
+}
+const {classifyActived}=useClassify()//当前选中的分类
+const classifyList = ref([])
+async function getClassify() {
+  const res = await apiETAChart.classifyList({
+    ParentId: -1,
+    SysUserIds:userVal.value?.join(',')
+  })
+  if (res.Ret === 200) {
+    const arr = res.Data.AllNodes || []
+    classifyList.value = arr.map(item => {
+      return {
+        ...item,
+        Children: null
+      }
+    })
+  }
+}
+getClassify()
+// 懒加载分类
+async function classifyLoad(node, resolve) {
+  if (node.level === 0) {
+    resolve(classifyList.value)
+  } else {
+    let nodes = []
+    const res = await apiETAChart.classifyList({
+      ParentId: node.data.ChartClassifyId,
+      SysUserIds:userVal.value?.join(',')
+    })
+    if (res.Ret === 200) {
+      const arr = res.Data.AllNodes || []
+      nodes = arr.map(item => {
+        return {
+          ...item,
+          isLeaf: item.ChartInfoId !== 0 ? true : false,
+          ChartClassifyId: item.ChartInfoId ? item.UniqueCode : item.ChartClassifyId,//如果是指标则将分类id设置为图表id
+        }
+      })
+    }
+    resolve(nodes);
+  }
+}
+function handleClassifyActiveChange(data, node) {
+  classifyActived.value = data.ChartClassifyId
+  if(data.ChartInfoId){//选择的是图表
+    emits('change',data)
+  }else{
+    emits('filter')
+  }
+}
+// 控制分类操作按钮显示
+function showClassifyOpt(node, data) {
+  return (classifyActived.value === data.ChartClassifyId) && data.ChartClassifyId !== 0 && data.ChartInfoId === 0
+}
+
+// 分类编辑
+const formEl = ref(null)
+const FORM_RULES = { name: [{ required: true, message: '名称不能为空' }] };
+const showEditClassify = ref(false)
+const classifyEditState = reactive({
+  id: 0,
+  name: '',
+  parent: [],
+  parentId: 0
+})
+watch(
+  () => showEditClassify.value,
+  (n) => {
+    if (!n) {
+      classifyEditState.id = 0
+      classifyEditState.name = ''
+      classifyEditState.parent = []
+      classifyEditState.parentId = 0
+    }
+  }
+)
+async function handleConfirmClassify() {
+  const valid = await formEl.value.validate()
+  if (!valid) return
+  const res = classifyEditState.id ? await apiETAChart.classifyEdit({
+    ChartClassifyName: classifyEditState.name,
+    ChartClassifyId: classifyEditState.id
+  }) : await apiETAChart.classifyAdd({
+    ChartClassifyName: classifyEditState.name,
+    ParentId: classifyEditState.parentId
+  })
+  if (res.Ret === 200) {
+    MessagePlugin.success('操作成功')
+    showEditClassify.value = false
+    getClassify()
+  }
+
+
+}
+// 递归获取父节点
+function getAllParent(data, arr) {
+  if (data.level === 0) return
+  arr.push({ ChartClassifyName: data.data.ChartClassifyName, ChartClassifyId: data.data.ChartClassifyId })
+  getAllParent(data.parent, arr)
+  return arr
+}
+function handleClassifyOpt(node, data, type) {
+  if (type === 'edit') {
+    classifyEditState.id = data.ChartClassifyId
+    classifyEditState.name = data.ChartClassifyName
+    let parr = getAllParent(node, [])
+    parr.shift()
+    classifyEditState.parent = parr.reverse()
+    classifyEditState.parentId = node.parent.id || 0
+    showEditClassify.value = true
+  }
+  if (type === 'add') {
+    let parr = getAllParent(node, [])
+    parr.shift()
+    classifyEditState.parent = parr.reverse()
+    classifyEditState.parentId = data.ChartClassifyId
+    showEditClassify.value = true
+  }
+
+  if (type === 'del') {
+    apiETAChart.classifyDelete({
+      ChartClassifyId: data.ChartClassifyId
+    }).then(res => {
+      if (res.Ret === 200) {
+        MessagePlugin.success('操作成功')
+        getClassify()
+      }
+    })
+  }
+}
 
 
+function allowDrag({ data }) {
+  if (data.ChartClassifyId === 0) return false //未分类不允许拖动
+  return true
+}
+function allowDrop(draggingNode, dropNode, type) {
+  let canDrop = false
+  // 如果拖动的是指标
+  if (draggingNode.data.ChartInfoId) {
+    if (!(dropNode.level === 1 && type !== 'inner')) {
+      canDrop = true
+    }
+  } else {//拖动的是目录
+    // console.log(dropNode.level,draggingNode.level);
+    //目录层级不能改变
+    if ((dropNode.level + 1 == draggingNode.level && type === 'inner' && !dropNode.data.ChartInfoId) || (dropNode.level === draggingNode.level && type !== 'inner')) {
+      canDrop = true
+    }
+  }
+  return canDrop
+}
+function handleDropOver(b, a, i, e) {
+  // 被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置
+  console.log(b, a, i);
+  const isEDB = b.data.ChartInfoId ? true : false
+  let list = a.parent.childNodes;
+  let targetIndex = 0, PrevClassifyId = 0, NextClassifyId = 0, ParentClassifyId = 0;
+  let ClassifyId = 0, ChartInfoId = 0, PrevChartInfoId = 0, NextChartInfoId = 0;
+
+  ClassifyId = isEDB ? 0 : b.data.ChartClassifyId
+  ChartInfoId = isEDB ? b.data.ChartInfoId : 0
+
+
+  if (i !== 'inner') {
+    ParentClassifyId = a.parent.data.ChartClassifyId || 0
+    list.forEach((item, index) => {
+      if (isEDB) {
+        if (item.data.ChartInfoId === b.data.ChartInfoId) {
+          targetIndex = index
+        }
+      } else {
+        if (item.data.ChartClassifyId === b.data.ChartClassifyId) {
+          targetIndex = index
+        }
+      }
+
+    })
+
+    console.log(targetIndex);
+
+
+    if (targetIndex === 0) {
+      const data = list[targetIndex + 1].data
+      NextClassifyId = data.ChartInfoId ? 0 : data.ChartClassifyId
+      NextChartInfoId = data.ChartInfoId ? data.ChartInfoId : 0
+    } else if (targetIndex === list.length - 1) {
+      const data = list[targetIndex - 1].data
+      PrevClassifyId = data.ChartInfoId ? 0 : data.ChartClassifyId
+      PrevChartInfoId = data.ChartInfoId ? data.ChartInfoId : 0
+    } else {
+      const pData = list[targetIndex - 1].data
+      PrevClassifyId = pData.ChartInfoId ? 0 : pData.ChartClassifyId
+
+      PrevChartInfoId = pData.ChartInfoId ? pData.ChartInfoId : 0
+
+      const nData = list[targetIndex + 1].data
+      NextClassifyId = nData.ChartInfoId ? 0 : nData.ChartClassifyId
+      NextChartInfoId = nData.ChartInfoId ? nData.ChartInfoId : 0
+    }
+  } else {
+    ParentClassifyId = a.data.ChartClassifyId || 0
+  }
+
+  const params = {
+    ClassifyId,
+    ParentClassifyId,
+    ChartInfoId,
+    PrevClassifyId,
+    NextClassifyId,
+    PrevChartInfoId,
+    NextChartInfoId
+  }
+  console.log(params);
+  apiETAChart.classifySort(params).then(res => {
+    if (res.Ret === 200) {
+      MessagePlugin.success('移动成功!')
+      getClassify()
+    }
+  })
+}
+
 </script>
 
 <template>
   <div class="bg-white classify-wrap">
     <div class="select-wrap">
-      <t-select placeholder="机构" v-model="companyVal">
-        <t-option
-          v-for="opt in companyOpts"
-          :key="opt.value"
-          :label="opt.label"
-          :value="opt.value"
-        ></t-option>
-      </t-select>
-      <t-select placeholder="创建人" v-model="userVal">
-        <t-option
-          v-for="opt in userOpts"
-          :key="opt.value"
-          :label="opt.label"
-          :value="opt.value"
-        ></t-option>
-      </t-select>
+      <t-cascader
+        v-model="userVal"
+        :options="userOpts"
+        :keys="userProps"
+        multiple
+        :minCollapsedNum="1"
+        clearable
+        filterable
+        :showAllLevels='false'
+        placeholder="创建人"
+        @change="handleUserChange"
+      />
     </div>
-    <t-input v-model="searchVal" clearable placeholder="请输入图表名称">
+    <t-select
+      v-model="searchVal"
+      placeholder="请输入图表名称"
+      clearable
+      filterable
+      :keys="searchSelectKeys"
+      :options="searchOpts"
+      :loading="searchLoading"
+      @search="handleSearchChart"
+      @change="handleSelectChart"
+      :popup-props="{ 'on-scroll-to-bottom': handleLoadMoreChart }"
+    >
       <template #prefixIcon>
         <search-icon />
       </template>
-    </t-input>
+    </t-select>
     <div class="classify-list-box">
-      <t-tree :data="classifyList" activable transition>
+      <el-tree
+        :data="classifyList"
+        :props="classifyTreeKeys"
+        :current-node-key="classifyActived"
+        draggable
+        check-on-click-node
+        node-key="ChartClassifyId"
+        check-strictly
+        highlight-current
+        empty-text="暂无目录"
+        lazy
+        icon="span"
+        @current-change="handleClassifyActiveChange"
+        :load="classifyLoad"
+        :allow-drop="allowDrop"
+        :allow-drag="allowDrag"
+        @node-drop="handleDropOver"
+      >
+        <template #default="{ node, data }">
+          <div class="classify-item-box">
+            <div class="label">{{ node.label }}</div>
+            <div class="opt-box" v-show="showClassifyOpt(node, data)">
+              <span>
+                <t-icon name="drag-move" />
+              </span>
+              <span @click.stop="handleClassifyOpt(node, data, 'add')">
+                <t-icon name="add" />
+              </span>
+              <span @click.stop="handleClassifyOpt(node, data, 'edit')">
+                <t-icon name="edit-2" />
+              </span>
+              <span @click.stop="handleClassifyOpt(node, data, 'del')">
+                <t-icon name="close" />
+              </span>
+            </div>
+          </div>
+        </template>
+      </el-tree>
+
+      <!-- <t-tree
+        v-model:actived="classifyActived"
+        :data="classifyList"
+        activable
+        transition
+        draggable
+        :load="classifyLoad"
+        value-mode="all"
+        :keys="classifyTreeKeys"
+        check-strictly
+        :onDragOver="canDrag"
+        @drop="handleClassifyDrop"
+      >
         <template #icon="{ node }">
-          <svg-icon style="font-size:16px" v-if="node.getChildren() && node.expanded" name="tree_opend" />
-          <svg-icon style="font-size:16px" v-if="node.getChildren() && !node.expanded" name="tree_close" />
+          <t-icon
+            name="add-rectangle"
+            v-if="node.getChildren() && !node.expanded"
+          />
+          <t-icon
+            name="minus-rectangle"
+            v-if="node.getChildren() && node.expanded"
+          />
+        </template>
+        <template #label="{ node }">
+          <div class="classify-item-box">
+            <div class="label">{{ node.label }}</div>
+            <div class="opt-box" v-show="showClassifyOpt(node)">
+              <span>
+                <t-icon name="drag-move" />
+              </span>
+              <span @click.stop="handleClassifyOpt(node,'add')" v-if="node.data.Level<6">
+                <t-icon name="add" />
+              </span>
+              <span @click.stop="handleClassifyOpt(node,'edit')">
+                <t-icon name="edit-2" />
+              </span>
+              <span @click.stop="handleClassifyOpt(node,'del')">
+                <t-icon name="close" />
+              </span>
+            </div>
+          </div>
         </template>
-      </t-tree>
+      </t-tree> -->
+    </div>
+    <div class="classify-add-box" @click="showEditClassify = true">
+      <t-icon name="add-rectangle" />
+      <span>添加图表分类</span>
     </div>
   </div>
+  <!-- 编辑分类 -->
+  <t-dialog
+    v-model:visible="showEditClassify"
+    header="新增分类"
+    draggable
+    :confirm-on-enter="true"
+    :on-confirm="handleConfirmClassify"
+  >
+    <t-form
+      ref="formEl"
+      :rules="FORM_RULES"
+      :data="classifyEditState"
+      class="edit-classify-wrap"
+    >
+      <t-form-item
+        label="上级分类"
+        name="parent"
+        v-if="classifyEditState.parent.length"
+      >
+        <div class="parent-box">
+          <span
+            v-for="item in classifyEditState.parent"
+            :key="item.ChartClassifyId"
+            >{{ item.ChartClassifyName }}</span
+          >
+        </div>
+      </t-form-item>
+      <t-form-item label="分类名称" name="name">
+        <t-input
+          v-model="classifyEditState.name"
+          placeholder="请输入内容"
+        ></t-input>
+      </t-form-item>
+    </t-form>
+  </t-dialog>
 </template>
 
+<style lang="scss">
+.classify-wrap {
+  .classify-list-box {
+    .el-tree-node__content{
+      overflow: hidden;
+      width: 100%;
+    }
+    .el-tree-node__expand-icon {
+      position: relative;
+      flex-shrink: 0;
+      span {
+        position: absolute;
+        left: 5%;
+        top: 5%;
+        display: inline-block;
+        width: 90%;
+        height: 90%;
+        background-image: url("@/assets/imgs/tree_closed.png");
+        background-size: cover;
+        background-repeat: no-repeat;
+      }
+    }
+    .el-tree-node__expand-icon.expanded {
+      transform: none;
+      span {
+        background-image: url("@/assets/imgs/tree_opend.png");
+      }
+    }
+  }
+}
+</style>
+
 <style lang="scss" scoped>
 .classify-wrap {
   width: 300px;
   flex-shrink: 0;
   padding: 20px;
-  
+
   .select-wrap {
     display: flex;
     gap: 0 10px;
@@ -108,8 +504,46 @@ const classifyList = ref([{
   }
   .classify-list-box {
     padding-top: 10px;
-    height: calc(100vh - 240px);
+    height: calc(100vh - 260px);
     overflow-y: auto;
+    .classify-item-box {
+      flex: 1;
+      display: flex;
+      align-items: center;
+      gap: 0 10px;
+      .label {
+        flex: 1;
+      }
+      .opt-box {
+        display: flex;
+        gap: 0 5px;
+        .t-icon {
+          color: $primary-color;
+        }
+      }
+    }
+  }
+  .classify-add-box {
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 0 5px;
+    color: $primary-color;
+    .t-icon {
+      color: $primary-color;
+    }
+  }
+}
+
+.edit-classify-wrap {
+  .parent-box {
+    span::after {
+      content: "/";
+    }
+    span:last-child::after {
+      content: "";
+    }
   }
 }
 </style>

+ 12 - 0
src/views/etaChart/hooks/useClassify.js

@@ -0,0 +1,12 @@
+import {ref} from 'vue'
+
+const classifyActived = ref('')//当前选中的分类
+const userVal = ref([])//当前选中的创建人
+
+export function useClassify(){
+
+    return {
+        classifyActived,
+        userVal
+    }
+}

+ 10 - 0
vite.config.js

@@ -80,5 +80,15 @@ export default defineConfig(({ mode }) => {
     build: {
       outDir: ENV.VITE_APP_OUTDIR,
     },
+    server:{
+      port:8900,
+      proxy:{
+        '/v1': {
+          target: 'http://8.136.199.33:8900/v1',
+          changeOrigin: true,
+          rewrite: (path) => path.replace(/^\/v1/, ''),
+        }
+      }
+    }
   };
 });