Compare commits
36 Commits
2.231214.9
...
2.231222.9
| Author | SHA1 | Date | |
|---|---|---|---|
| a23cfd209f | |||
|
|
0f3df3a80b | ||
|
|
04754a022f | ||
|
|
61df24059c | ||
|
|
fbd41e2ebf | ||
|
|
cf7d234f91 | ||
|
|
b556dc36e6 | ||
|
|
0c6ea19878 | ||
|
|
0c52f2cf3b | ||
|
|
2470eed417 | ||
|
|
89c7b483d4 | ||
|
|
b26104b4bc | ||
| acd8337606 | |||
|
|
eac0d957aa | ||
|
|
eae2dd5d30 | ||
|
|
fa1bb05548 | ||
|
|
6b855b8ff2 | ||
|
|
11bfae8c0f | ||
|
|
823114a6d0 | ||
|
|
99c6d233e5 | ||
|
|
728d4b00d8 | ||
|
|
8288184f15 | ||
|
|
6fc9a305ce | ||
|
|
e8b95a594b | ||
|
|
859eeda1fe | ||
|
|
3f20faf5be | ||
|
|
208d7a4725 | ||
|
|
1c44651ad2 | ||
|
|
f015f45601 | ||
|
|
dfe06e2098 | ||
|
|
44c05e964e | ||
|
|
e210dd7e33 | ||
|
|
920bca7912 | ||
|
|
b0d7b7d539 | ||
|
|
cf9968a899 | ||
|
|
f67d083e10 |
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network EMS"
|
||||
VITE_APP_CODE = "CN EMS"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.231211.9"
|
||||
VITE_APP_VERSION = "2.231222.9"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
19
CHANGELOG.md
Normal file
19
CHANGELOG.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# 版本发布日志
|
||||
|
||||
## 2.2312.9-20231222
|
||||
|
||||
- 新增 日志管理查看网元日志文件并下载功能页面
|
||||
- 新增 国际化切换权限控制,系统设置里控制切换默认语言
|
||||
- 新增 性能管理 KPI 指标集功能页面
|
||||
- 新增 UE 管理 PCF 用户策略控制功能页面
|
||||
- 修复 参数配置新增数据类型值不匹配导致的异常失败
|
||||
- 修复 多处英文显示词义不明确的信息提示
|
||||
- 修复 UE 管理 UDM 签约用户数据批量新增时EPSODB转换十六进制问题
|
||||
- 优化 网元管理重启接口超时导致网元信息不一致问题
|
||||
- 优化 参数配置新增编辑参数顺序,参数类型格式化
|
||||
- 优化 主页网元状态饼图颜色选择自定义
|
||||
- 优化 MML 操作 UDM 签约用户信息字段,必填项标红
|
||||
- 优化 软件管理回退功能操作页面
|
||||
- 优化 静态文件地址追加随机戳避免缓存
|
||||
- 优化 补充移动端样式适配调整
|
||||
- 主页修改增加OMC的序列号,点击网元信息不再覆盖右边信息框
|
||||
@@ -56,6 +56,7 @@ eyJhbGciOiJSUzI1NiIsImtpZCI6ImZFVUhIb1puLW04M1dfSUYyRU8zWlZueXBpNUh4T0hTRVlzU19j
|
||||
```
|
||||
|
||||
```ssh
|
||||
# https://blog.csdn.net/m0_54706625/article/details/129721121
|
||||
sudo chmod 700 -/.ssh/
|
||||
sudo chmod 700 /home/mask/.ssh #这个尤其容易忽视掉,我就是从这个坑里爬出来。有木有很高兴呀!
|
||||
sudo chmod 600 ~/.ssh/authorized_keys
|
||||
|
||||
17
package.json
17
package.json
@@ -13,13 +13,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@antv/g6": "^5.0.0-beta.27",
|
||||
"@antv/g6": "^4.8.24",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/merge": "^6.1.2",
|
||||
"@codemirror/merge": "^6.4.0",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@tato30/vue-pdf": "^1.8.1",
|
||||
"@vueuse/components": "^10.6.1",
|
||||
"@vueuse/core": "^10.6.1",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"antdv-pro-layout": "^3.2.6",
|
||||
"codemirror": "^6.0.1",
|
||||
@@ -30,7 +29,7 @@
|
||||
"js-cookie": "^3.0.5",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.3.10",
|
||||
"vue": "^3.3.11",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
"vue-i18n": "^9.8.0",
|
||||
"vue-router": "^4.2.5",
|
||||
@@ -41,12 +40,12 @@
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@vitejs/plugin-vue": "^4.5.1",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"less": "^4.2.0",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript": "^5.3.3",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.0.5",
|
||||
"vite": "^5.0.9",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vue-tsc": "^1.8.25"
|
||||
}
|
||||
|
||||
1
public/svg/base.svg
Normal file
1
public/svg/base.svg
Normal file
@@ -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="1702978675818" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4327" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512.1 955.7c-1.4 0-2.7-0.4-4-1.1L80.4 706.4c-2.4-1.4-3.9-4-3.9-6.8v-88.8c0-2.8 1.5-5.4 3.9-6.8l427.7-252c1.2-0.7 2.6-1.1 4-1.1s2.8 0.4 4 1.1l427.6 252c2.4 1.4 3.9 4 3.9 6.8v88.8c0 2.8-1.5 5.4-3.9 6.8L516 954.6c-1.2 0.7-2.6 1.1-3.9 1.1z" fill="#B5D6FB" p-id="4328"></path><path d="M512.1 358.8l427.5 252v88.8L869.9 740 512.1 947.8 154.1 740l-69.7-40.4v-88.8l427.7-252m0-15.8c-2.8 0-5.5 0.7-8 2.2l-427.7 252c-4.8 2.8-7.8 8-7.8 13.6v88.8c0 5.6 3 10.8 7.9 13.7l69.7 40.5 358 207.8c2.4 1.4 5.2 2.1 7.9 2.1s5.5-0.7 7.9-2.1l357.8-207.8 69.7-40.4c4.9-2.8 7.9-8 7.9-13.7v-88.8c0-5.6-3-10.8-7.8-13.6l-427.5-252c-2.5-1.5-5.3-2.3-8-2.3z" fill="#0276F7" p-id="4329"></path><path d="M84.4 610.8L512 860.7l427.6-249.9-427.5-252z" fill="#FFFFFF" p-id="4330"></path><path d="M500.9 70.3L232.1 562.1l277.7 159L791 561.2z" fill="#D4E4FC" p-id="4331"></path><path d="M214.6 601.2c-1.3 0-2.6-0.3-3.9-1-3.8-2.1-5.2-6.9-3-10.7l294.9-525c1.4-2.5 4-4 6.9-4 2.8 0 5.4 1.5 6.9 4l299.9 525c2.2 3.8 0.8 8.6-2.9 10.8-3.8 2.1-8.6 0.8-10.8-2.9l-293-512.9-288.1 512.7c-1.5 2.6-4.2 4-6.9 4z" fill="#0276F7" p-id="4332"></path><path d="M509.8 752.4c-4.4 0-7.9-3.5-7.9-7.9V86.7c0-4.4 3.5-7.9 7.9-7.9s7.9 3.5 7.9 7.9v657.9c0 4.3-3.6 7.8-7.9 7.8z" fill="#0276F7" p-id="4333"></path><path d="M509.8 729c-1.4 0-2.7-0.4-3.9-1L228.2 569c-3.8-2.2-5.1-7-2.9-10.8 2.2-3.8 7-5.1 10.8-2.9L509.8 712l277.3-157.7c3.8-2.2 8.6-0.8 10.8 3 2.2 3.8 0.8 8.6-3 10.8L513.7 728c-1.2 0.7-2.6 1-3.9 1z" fill="#0276F7" p-id="4334"></path><path d="M509.8 578.9c-1.4 0-2.7-0.4-4-1.1l-213-124c-3.8-2.2-5-7-2.9-10.8 2.2-3.8 7-5.1 10.8-2.8l209.1 121.7 212.8-120.5c3.8-2.2 8.6-0.8 10.8 3 2.2 3.8 0.8 8.6-3 10.7L513.7 577.9c-1.2 0.7-2.6 1-3.9 1zM507.2 423.5c-1.3 0-2.5-0.3-3.7-0.9L354.9 344c-3.9-2.1-5.3-6.8-3.3-10.7 2-3.9 6.8-5.3 10.7-3.3l145 76.7 150.8-78.8c3.9-2 8.6-0.5 10.7 3.3 2 3.9 0.5 8.6-3.3 10.7L511 422.6c-1.3 0.6-2.5 0.9-3.8 0.9zM509.8 277c-1.3 0-2.6-0.3-3.7-0.9l-86.8-46.8c-3.8-2.1-5.3-6.9-3.2-10.7 2.1-3.9 6.9-5.2 10.7-3.2l83 44.8 82.6-46.9c3.8-2.2 8.6-0.8 10.8 3 2.2 3.8 0.8 8.6-3 10.8l-86.4 49c-1.3 0.6-2.7 0.9-4 0.9z" fill="#0276F7" p-id="4335"></path><path d="M509.8 578.9h-0.3L231.9 570c-4.4-0.1-7.8-3.8-7.6-8.1 0.1-4.4 4.3-7.6 8.1-7.6l277.7 8.9c4.4 0.1 7.8 3.8 7.6 8.1-0.2 4.2-3.7 7.6-7.9 7.6zM509.8 729c-1.7 0-3.4-0.6-4.9-1.7-3.4-2.7-4-7.7-1.3-11.1l216.7-272.9c2.7-3.4 7.7-4 11.1-1.3 3.4 2.7 4 7.7 1.3 11.1L516 726c-1.6 2-3.9 3-6.2 3z" fill="#0276F7" p-id="4336"></path><path d="M509.8 578.9c-1.5 0-2.9-0.4-4.3-1.2-3.7-2.4-4.7-7.2-2.4-10.9l152-236.1c2.4-3.7 7.3-4.7 10.9-2.4 3.7 2.4 4.7 7.2 2.4 10.9l-152 236.1c-1.5 2.3-4 3.6-6.6 3.6zM507.2 423.5c-1.1 0-2.2-0.2-3.3-0.7-4-1.8-5.7-6.5-3.9-10.5l89-195.5c1.8-4 6.4-5.7 10.5-3.9 4 1.8 5.7 6.5 3.9 10.5l-88.9 195.5c-1.4 2.9-4.3 4.6-7.3 4.6z" fill="#0276F7" p-id="4337"></path><path d="M596.2 377c-2.9 0-5.7-1.6-7.1-4.4-1.9-3.9-0.3-8.6 3.6-10.5L729 295.7 343.3 120l22 205.3 141.3-63.4c3.9-1.8 8.6 0 10.4 4 1.8 4 0 8.6-4 10.4l-151.2 67.9c-2.3 1-4.9 0.9-7.2-0.4-2.2-1.3-3.6-3.5-3.9-6l-24.6-229.9c-0.3-2.8 0.9-5.6 3.2-7.2 2.3-1.6 5.3-2 7.9-0.8l413.6 188.4c2.8 1.3 4.6 4 4.6 7.1 0 3.1-1.7 5.9-4.4 7.2l-151.4 73.6c-1.1 0.6-2.3 0.8-3.4 0.8zM296.8 454.9c-3.8 0-7.2-2.8-7.8-6.7-0.6-4.3 2.3-8.3 6.6-9l210.5-31.4c4.3-0.6 8.3 2.3 9 6.6 0.6 4.3-2.3 8.3-6.6 9L298 454.8c-0.5 0.1-0.9 0.1-1.2 0.1z" fill="#0276F7" p-id="4338"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
1
public/svg/cloud.svg
Normal file
1
public/svg/cloud.svg
Normal file
@@ -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="1702978716365" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4633" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M817.3 505c2.7-14 4.3-28.4 4.3-43.2 0-123.8-100.4-224.2-224.2-224.2-81.9 0-153.3 44.1-192.5 109.7-15.1-5.9-31.5-9.2-48.7-9.2-72.6 0-131.6 57.6-134.3 129.5-69 18.6-119.9 81.5-119.9 156.5 0 89.6 72.7 162.3 162.3 162.3h514.3c79.2 0 143.4-64.2 143.4-143.4 0-65.8-44.3-121-104.7-138z" fill="#D4E4FC" p-id="4634"></path><path d="M778.6 793.2H264.3C171 793.2 95.2 717.3 95.2 624c0-74.5 49.2-140.3 120.2-161.8 5.3-73.9 66.2-131.1 140.9-131.1 15.6 0 30.9 2.6 45.8 7.7 42.7-67.8 115.3-108.1 195.4-108.1 127.4 0 231 103.6 231 231 0 12.7-1.1 25.6-3.3 38.4 61.3 20 103.6 77.8 103.6 142.7 0 83-67.4 150.4-150.2 150.4zM356.3 344.9c-68.9 0-124.9 54-127.4 122.9l-0.2 5.1-4.9 1.3c-67.6 18.3-114.9 79.9-114.9 149.9 0 85.7 69.7 155.4 155.4 155.4h514.3c75.3 0 136.5-61.2 136.5-136.5 0-60.9-41-114.9-99.6-131.3l-6.1-1.7 1.2-6.2c2.7-14 4.1-28.1 4.1-41.9 0-119.8-97.5-217.3-217.3-217.3-77.1 0-146.9 39.7-186.6 106.3l-3 5-5.4-2.1c-14.9-5.9-30.4-8.9-46.1-8.9z" fill="#0276F7" p-id="4635"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
public/svg/service.svg
Normal file
1
public/svg/service.svg
Normal file
@@ -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="1702974353127" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4174" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M470.9 954.6c-1.2 0-2.3-0.3-3.4-0.9l-240-132.2c-2.2-1.2-3.6-3.6-3.6-6.1V270.6c0-2.5 1.3-4.7 3.4-6L546.9 70.4c1.1-0.7 2.4-1 3.6-1 1.2 0 2.4 0.3 3.4 0.9l242.6 136.9c2.2 1.2 3.6 3.6 3.6 6.1v552.8c0 2.5-1.4 4.9-3.6 6.1L474.4 953.7c-1.1 0.6-2.3 0.9-3.5 0.9z" fill="#B5D6FB" p-id="4175"></path><path d="M550.5 76.4l242.6 136.9v552.8L470.9 947.6l-240-132.2V270.6L550.5 76.4m0-14c-2.5 0-5 0.7-7.3 2L223.6 258.6c-4.2 2.5-6.7 7.1-6.7 12v544.8c0 5.1 2.8 9.8 7.3 12.3l240 132.2c2.1 1.2 4.4 1.7 6.8 1.7 2.4 0 4.7-0.6 6.9-1.8L800 778.3c4.4-2.5 7.1-7.2 7.1-12.2V213.3c0-5.1-2.7-9.7-7.1-12.2L557.4 64.2c-2.1-1.2-4.5-1.8-6.9-1.8z" fill="#0276F7" p-id="4176"></path><path d="M550.5 76.4L230.9 270.6l240 130.5 322.2-187.8z" fill="#FFFFFF" p-id="4177"></path><path d="M470.9 916V401.1" fill="#B5D6FB" p-id="4178"></path><path d="M470.9 923.1c-3.9 0-7-3.1-7-7v-515c0-3.9 3.1-7 7-7s7 3.1 7 7V916c0 3.9-3.1 7.1-7 7.1z" fill="#0276F7" p-id="4179"></path><path d="M754.5 235.8L470.9 401.1" fill="#B5D6FB" p-id="4180"></path><path d="M470.9 408.1c-2.4 0-4.8-1.2-6.1-3.5-1.9-3.3-0.8-7.6 2.5-9.6L751 229.7c3.4-2 7.6-0.8 9.6 2.5 1.9 3.3 0.8 7.6-2.5 9.6L474.5 407.2c-1.1 0.6-2.4 0.9-3.6 0.9z" fill="#0276F7" p-id="4181"></path><path d="M261.5 287.2l209.4 113.9" fill="#B5D6FB" p-id="4182"></path><path d="M470.9 408.1c-1.1 0-2.3-0.3-3.3-0.8L258.1 293.4c-3.4-1.8-4.7-6.1-2.8-9.5 1.9-3.4 6.1-4.6 9.5-2.8L474.3 395c3.4 1.8 4.7 6.1 2.8 9.5-1.3 2.3-3.7 3.6-6.2 3.6z" fill="#0276F7" p-id="4183"></path><path d="M258.2 744.4l177.2 99.3" fill="#B5D6FB" p-id="4184"></path><path d="M435.4 850.7c-1.2 0-2.3-0.3-3.4-0.9l-177.1-99.3c-3.4-1.9-4.6-6.2-2.7-9.5 1.9-3.4 6.1-4.6 9.5-2.7l177.1 99.3c3.4 1.9 4.6 6.2 2.7 9.5-1.3 2.3-3.7 3.6-6.1 3.6z" fill="#0276F7" p-id="4185"></path><path d="M258.2 694.8l177.2 99.3" fill="#B5D6FB" p-id="4186"></path><path d="M435.4 801.1c-1.2 0-2.3-0.3-3.4-0.9l-177.1-99.3c-3.4-1.9-4.6-6.2-2.7-9.5 1.9-3.4 6.1-4.6 9.5-2.7L438.8 788c3.4 1.9 4.6 6.2 2.7 9.5-1.3 2.3-3.7 3.6-6.1 3.6z" fill="#0276F7" p-id="4187"></path><path d="M258.2 645.1l177.2 99.3" fill="#B5D6FB" p-id="4188"></path><path d="M435.4 751.4c-1.2 0-2.3-0.3-3.4-0.9l-177.1-99.3c-3.4-1.9-4.6-6.2-2.7-9.5 1.9-3.4 6.1-4.6 9.5-2.7l177.1 99.3c3.4 1.9 4.6 6.2 2.7 9.5-1.3 2.3-3.7 3.6-6.1 3.6z" fill="#0276F7" p-id="4189"></path><path d="M222.7 538.9l248.2 135.5" fill="#B5D6FB" p-id="4190"></path><path d="M219.335 545.028l6.708-12.289 248.241 135.49-6.707 12.289z" fill="#0276F7" p-id="4191"></path><path d="M393 430.7l40.3 20.1" fill="#B5D6FB" p-id="4192"></path><path d="M433.3 457.8c-1.1 0-2.1-0.2-3.1-0.7L389.9 437c-3.5-1.7-4.9-5.9-3.1-9.4 1.7-3.5 6-4.9 9.4-3.1l40.3 20.1c3.5 1.7 4.9 5.9 3.1 9.4-1.3 2.4-3.8 3.8-6.3 3.8z" fill="#0276F7" p-id="4193"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
1
public/svg/service_db.svg
Normal file
1
public/svg/service_db.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.1 KiB |
12
src/App.vue
12
src/App.vue
@@ -102,12 +102,14 @@ body .ant-pro-basicLayout {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-container > button {
|
||||
.button-container > button,
|
||||
.button-container > span {
|
||||
margin-right: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.button-container > button:last-child {
|
||||
.button-container > button:last-child,
|
||||
.button-container > span:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
/* 平板端 */
|
||||
@@ -117,7 +119,8 @@ body .ant-pro-basicLayout {
|
||||
align-items: flex-start;
|
||||
align-items: left;
|
||||
}
|
||||
.button-container > button {
|
||||
.button-container > button,
|
||||
.button-container > span {
|
||||
margin-right: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@@ -129,7 +132,8 @@ body .ant-pro-basicLayout {
|
||||
align-items: flex-start;
|
||||
align-items: left;
|
||||
}
|
||||
.button-container > button {
|
||||
.button-container > button,
|
||||
.button-container > span {
|
||||
margin-right: 0px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
29
src/api/ne/ne.ts
Normal file
29
src/api/ne/ne.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询网元列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listNe(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元状态
|
||||
* @param neType 网元类型
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function stateNe(neType: string, neId: string) {
|
||||
return request({
|
||||
url: '/ne/state',
|
||||
method: 'get',
|
||||
params: { neType, neId },
|
||||
});
|
||||
}
|
||||
@@ -1,25 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<div ref="g6Dom" :style="{ height: height, width: width }"></div>
|
||||
<button @click="actions['Enable/Disable Node States'].Breathing()">
|
||||
Breathing()
|
||||
</button>
|
||||
<button @click="actions['Enable/Disable Node States'].Scaling()">
|
||||
Scaling()
|
||||
</button>
|
||||
|
||||
<button @click="actions['Enable/Disable Edge States'].Growing()">
|
||||
Growing()
|
||||
</button>
|
||||
<button @click="actions['Enable/Disable Edge States'].Running()">
|
||||
Running()
|
||||
</button>
|
||||
<div ref="chartGraphG6Dom" :style="{ height: height, width: width }"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, watch, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import { Graph, extend, Extensions } from '@antv/g6';
|
||||
import { Graph } from '@antv/g6';
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
@@ -43,169 +30,151 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const g6Dom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
const ExtGraph = extend(Graph, {
|
||||
layouts: {},
|
||||
edges: {
|
||||
'polyline-edge': Extensions.PolylineEdge,
|
||||
'cubic-edge': Extensions.CubicEdge,
|
||||
// 'custom-edge': Extensions.CubicEdge,
|
||||
},
|
||||
});
|
||||
const chartGraphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
const data = {
|
||||
nodes: [
|
||||
// 0 基站
|
||||
{
|
||||
id: '0',
|
||||
data: {
|
||||
x: 80,
|
||||
y: 150,
|
||||
type: 'circle-node',
|
||||
color: '#fffff',
|
||||
keyShape: {
|
||||
r: 24,
|
||||
width: 48,
|
||||
height: 48,
|
||||
fill: '#9EC9FF',
|
||||
stroke: '#5B8FF9',
|
||||
lineWidth: 2,
|
||||
},
|
||||
labelShape: {
|
||||
text: '基站',
|
||||
position: 'bottom',
|
||||
maxWidth: '200%',
|
||||
offsetY: 10,
|
||||
},
|
||||
labelBackgroundShape: {},
|
||||
iconShape: {
|
||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||
width: 24,
|
||||
height: 24,
|
||||
x: 50,
|
||||
y: 150,
|
||||
size: 48,
|
||||
type: 'circle',
|
||||
label: '基站',
|
||||
labelCfg: {
|
||||
position: 'bottom',
|
||||
offset: 10,
|
||||
style: {
|
||||
fill: '#333',
|
||||
stroke: '#fff',
|
||||
lineWidth: 10,
|
||||
},
|
||||
},
|
||||
style: {
|
||||
fill: '#9EC9FF',
|
||||
stroke: '#5B8FF9',
|
||||
lineWidth: 2,
|
||||
},
|
||||
icon: {
|
||||
show: true,
|
||||
// 可更换为其他图片地址
|
||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
},
|
||||
// 1 DM
|
||||
{
|
||||
id: '1',
|
||||
data: {
|
||||
label: 'DM',
|
||||
x: 600,
|
||||
y: 350,
|
||||
x: 450,
|
||||
y: 450,
|
||||
label: 'DM',
|
||||
labelCfg: {
|
||||
position: 'center',
|
||||
},
|
||||
style: {
|
||||
fill: '#00b050',
|
||||
stroke: '#00b050',
|
||||
lineWidth: 1,
|
||||
},
|
||||
},
|
||||
// 2 O&M
|
||||
{
|
||||
id: '2',
|
||||
data: {
|
||||
label: 'O&M',
|
||||
x: 900,
|
||||
y: 100,
|
||||
},
|
||||
x: 50,
|
||||
y: 450,
|
||||
label: 'O&M',
|
||||
},
|
||||
// 5GC控制面
|
||||
// 100 EMS
|
||||
{
|
||||
id: '170',
|
||||
data: {
|
||||
label: 'NSSF',
|
||||
parentId: 'combo-5gc',
|
||||
x: 300,
|
||||
y: 50,
|
||||
},
|
||||
id: '100',
|
||||
label: 'EMS',
|
||||
comboId: 'combo-ems',
|
||||
x: 300,
|
||||
y: 450,
|
||||
},
|
||||
{
|
||||
id: '130',
|
||||
data: {
|
||||
label: 'AUSF',
|
||||
parentId: 'combo-5gc',
|
||||
x: 400,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '140',
|
||||
data: {
|
||||
label: 'UDM',
|
||||
parentId: 'combo-5gc',
|
||||
x: 500,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '120',
|
||||
data: {
|
||||
label: 'AMF',
|
||||
parentId: 'combo-5gc',
|
||||
x: 300,
|
||||
y: 150,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '180',
|
||||
data: {
|
||||
label: 'NRF',
|
||||
parentId: 'combo-5gc',
|
||||
x: 400,
|
||||
y: 150,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '150',
|
||||
data: {
|
||||
label: 'SMF',
|
||||
parentId: 'combo-5gc',
|
||||
x: 500,
|
||||
y: 250,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '160',
|
||||
data: {
|
||||
label: 'PCF',
|
||||
parentId: 'combo-5gc',
|
||||
x: 600,
|
||||
y: 250,
|
||||
},
|
||||
},
|
||||
// 5GC用户面
|
||||
// 190 UPF
|
||||
{
|
||||
id: '190',
|
||||
data: {
|
||||
label: 'UPF',
|
||||
parentId: 'combo-upf',
|
||||
x: 300,
|
||||
y: 350,
|
||||
comboId: 'combo-upf',
|
||||
x: 300,
|
||||
y: 350,
|
||||
label: 'UPF',
|
||||
labelCfg: {
|
||||
position: 'center',
|
||||
},
|
||||
style: {
|
||||
fill: '#d580ff',
|
||||
stroke: '#d580ff',
|
||||
lineWidth: 1,
|
||||
},
|
||||
},
|
||||
// EP-IMS
|
||||
{
|
||||
id: '110',
|
||||
data: {
|
||||
label: 'I/S-CSCF',
|
||||
parentId: 'combo-ims',
|
||||
x: 800,
|
||||
y: 350,
|
||||
comboId: 'combo-ims',
|
||||
x: 600,
|
||||
y: 350,
|
||||
label: 'IMS',
|
||||
labelCfg: {
|
||||
position: 'center',
|
||||
},
|
||||
style: {
|
||||
fill: '#ed7d31',
|
||||
stroke: '#ed7d31',
|
||||
lineWidth: 1,
|
||||
},
|
||||
},
|
||||
// 5GC控制面
|
||||
{
|
||||
id: '111',
|
||||
data: {
|
||||
label: 'P-CSCF',
|
||||
parentId: 'combo-ims',
|
||||
x: 800,
|
||||
y: 400,
|
||||
},
|
||||
id: '170',
|
||||
label: 'NSSF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 300,
|
||||
y: 50,
|
||||
},
|
||||
// O&M
|
||||
{
|
||||
id: '100',
|
||||
data: {
|
||||
label: 'EMS',
|
||||
parentId: 'combo-ems',
|
||||
x: 800,
|
||||
y: 100,
|
||||
},
|
||||
id: '130',
|
||||
label: 'AUSF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 450,
|
||||
y: 50,
|
||||
},
|
||||
{
|
||||
id: '140',
|
||||
label: 'UDM',
|
||||
comboId: 'combo-5gc',
|
||||
x: 600,
|
||||
y: 50,
|
||||
},
|
||||
{
|
||||
id: '120',
|
||||
label: 'AMF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 300,
|
||||
y: 150,
|
||||
},
|
||||
{
|
||||
id: '180',
|
||||
label: 'NRF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 450,
|
||||
y: 150,
|
||||
},
|
||||
{
|
||||
id: '150',
|
||||
label: 'SMF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 300,
|
||||
y: 250,
|
||||
},
|
||||
{
|
||||
id: '160',
|
||||
label: 'PCF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 700,
|
||||
y: 250,
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
@@ -213,129 +182,62 @@ const data = {
|
||||
id: '0-5gc',
|
||||
source: '0',
|
||||
target: 'combo-5gc',
|
||||
data: {
|
||||
type: 'cubic-edge',
|
||||
animates: {
|
||||
update: [
|
||||
{
|
||||
// 在 selected 和 active 状态变化导致的 haloShape opacity 变化时,使 opacity 带动画地更新
|
||||
fields: ['opacity'],
|
||||
shapeId: 'haloShape',
|
||||
states: ['selected', 'active'],
|
||||
duration: 500,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5gc-ems',
|
||||
source: 'combo-5gc',
|
||||
target: 'combo-ems',
|
||||
data: {},
|
||||
id: '0-upf',
|
||||
source: '0',
|
||||
target: 'combo-upf',
|
||||
},
|
||||
{
|
||||
id: 'upf-ems',
|
||||
id: 'upf-1',
|
||||
source: 'combo-upf',
|
||||
target: 'combo-ems',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: 'ims-ems',
|
||||
source: 'combo-ims',
|
||||
target: 'combo-ems',
|
||||
data: {},
|
||||
target: '1',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'ems-2',
|
||||
source: 'combo-ems',
|
||||
target: '2',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: '170-120',
|
||||
source: '170',
|
||||
target: '120',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: '130-120',
|
||||
source: '130',
|
||||
target: '120',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: '140-120',
|
||||
source: '140',
|
||||
target: '120',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: '140-180',
|
||||
source: '140',
|
||||
target: '180',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: '120-180',
|
||||
source: '120',
|
||||
target: '180',
|
||||
data: {
|
||||
type: 'polyline-edge',
|
||||
keyShape: {
|
||||
endArrow: true,
|
||||
routeCfg: {
|
||||
/**
|
||||
* 目前支持正交路由 'orth' 和地铁路由 'er'
|
||||
*/
|
||||
// name: 'er',
|
||||
/**
|
||||
* 是否开启自动避障,默认为 false
|
||||
* Whether to enable automatic obstacle avoidance, default is false
|
||||
*/
|
||||
enableObstacleAvoidance: true,
|
||||
},
|
||||
/**
|
||||
* 拐弯处的圆角弧度,默认为直角,值为 0
|
||||
* The radius of the corner rounding, defaults to a right angle
|
||||
*/
|
||||
// radius: 20,
|
||||
/**
|
||||
* 拐弯处距离节点最小距离, 默认为 2
|
||||
* Minimum distance from the node at the corner, default is 5.
|
||||
*/
|
||||
// offset: 0,
|
||||
/**
|
||||
* 控制点数组,不指定时根据 A* 算法自动生成折线。若指定了,则按照 controlPoints 指定的位置进行弯折
|
||||
* An array of control points that, when not specified, automatically generates the bends according to the A* algorithm. If specified, bends are made at the position specified by controlPoints.
|
||||
*/
|
||||
// controlPoints: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '130-180',
|
||||
source: '130',
|
||||
target: '180',
|
||||
data: {
|
||||
type: 'polyline-edge',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '140-150',
|
||||
source: '140',
|
||||
target: '150',
|
||||
data: {
|
||||
type: 'polyline-edge',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '140-110',
|
||||
source: '140',
|
||||
target: '110',
|
||||
data: {
|
||||
type: 'polyline-edge',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '120-150',
|
||||
@@ -353,56 +255,48 @@ const data = {
|
||||
id: '150-160',
|
||||
source: '150',
|
||||
target: '160',
|
||||
data: { type: 'polyline-edge' },
|
||||
},
|
||||
{
|
||||
id: '160-120',
|
||||
source: '160',
|
||||
target: '120',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: '160-180',
|
||||
source: '160',
|
||||
target: '180',
|
||||
data: {
|
||||
keyShape: {
|
||||
endArrow: true,
|
||||
},
|
||||
labelShape: {
|
||||
text: 'asdf-arrow',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '160-111',
|
||||
id: '160-110',
|
||||
source: '160',
|
||||
target: '111',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: '0-upf',
|
||||
source: '0',
|
||||
target: 'combo-upf',
|
||||
data: {},
|
||||
target: '110',
|
||||
},
|
||||
|
||||
{
|
||||
id: '150-190',
|
||||
source: '150',
|
||||
target: '190',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: 'upf-1',
|
||||
source: 'combo-upf',
|
||||
target: '1',
|
||||
data: {},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'upf-ims',
|
||||
source: 'combo-upf',
|
||||
target: 'combo-ims',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: 'ems-5gc',
|
||||
source: 'combo-ems',
|
||||
target: 'combo-5gc',
|
||||
},
|
||||
{
|
||||
id: 'ems-upf',
|
||||
source: 'combo-ems',
|
||||
target: 'combo-upf',
|
||||
},
|
||||
{
|
||||
id: 'ems-ims',
|
||||
source: 'combo-ems',
|
||||
target: 'combo-ims',
|
||||
},
|
||||
],
|
||||
combos: [
|
||||
@@ -415,354 +309,77 @@ const data = {
|
||||
{
|
||||
id: 'combo-upf',
|
||||
data: {
|
||||
label: '5GC用户面',
|
||||
keyShape: {
|
||||
opacity: 0.8,
|
||||
padding: [20, 20, 20, 20],
|
||||
radius: 4,
|
||||
lineWidth: 1,
|
||||
stroke: '#d580ff',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'combo-ims',
|
||||
data: {
|
||||
label: 'EP-IMS用户面',
|
||||
},
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: 'combo-ems',
|
||||
data: {
|
||||
label: 'EMS',
|
||||
},
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let graph: any = null;
|
||||
|
||||
/**初始化渲染图表 */
|
||||
function initChart() {
|
||||
if (!g6Dom.value) return;
|
||||
graph = new ExtGraph({
|
||||
container: g6Dom.value,
|
||||
height: 500,
|
||||
if (!chartGraphG6Dom.value) return;
|
||||
console.log(chartGraphG6Dom.value.clientWidth);
|
||||
console.log(chartGraphG6Dom.value.clientHeight);
|
||||
graph = new Graph({
|
||||
container: chartGraphG6Dom.value,
|
||||
height: chartGraphG6Dom.value.clientHeight,
|
||||
width: chartGraphG6Dom.value.clientWidth,
|
||||
fitCenter: true,
|
||||
modes: {
|
||||
default: [
|
||||
{
|
||||
type: 'drag-node',
|
||||
enableTransient: false,
|
||||
updateComboStructure: false,
|
||||
},
|
||||
{
|
||||
type: 'click-select',
|
||||
itemTypes: ['node', 'edge', 'combo'],
|
||||
},
|
||||
{
|
||||
type: 'drag-combo',
|
||||
enableTransient: true,
|
||||
updateComboStructure: true,
|
||||
},
|
||||
// 'drag-combo',
|
||||
'drag-canvas',
|
||||
// 'click-select',
|
||||
'zoom-canvas',
|
||||
] as any,
|
||||
default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // 允许拖拽画布、放缩画布、拖拽节点
|
||||
},
|
||||
// 布局 力向吸引
|
||||
layout: {
|
||||
type: 'force',
|
||||
animated: true,
|
||||
linkDistance: 280,
|
||||
maxSpeed: 100,
|
||||
clustering: true,
|
||||
nodeClusterBy: 'parentId',
|
||||
clusterNodeStrength: 80,
|
||||
},
|
||||
// 主题
|
||||
theme: {
|
||||
type: 'spec',
|
||||
base: 'light',
|
||||
specification: {
|
||||
node: {
|
||||
dataTypeField: 'parentId',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
// 插件
|
||||
plugins: [
|
||||
{
|
||||
// 避免文本过于密集导致的视觉混乱
|
||||
type: 'lod-controller',
|
||||
disableLod: true,
|
||||
},
|
||||
] as any,
|
||||
// 全局节点 矩形
|
||||
node: model => {
|
||||
const { id, data } = model;
|
||||
return {
|
||||
id,
|
||||
data: {
|
||||
type: 'rect-node',
|
||||
keyShape: {
|
||||
width: 80,
|
||||
height: 40,
|
||||
radius: 8,
|
||||
},
|
||||
labelShape: {
|
||||
position: 'center',
|
||||
text: data.label,
|
||||
},
|
||||
animates: {
|
||||
update: [
|
||||
{
|
||||
fields: ['opacity'],
|
||||
shapeId: 'haloShape',
|
||||
states: ['breathing'],
|
||||
iterations: Infinity,
|
||||
direction: 'alternate',
|
||||
duration: 500,
|
||||
},
|
||||
{
|
||||
fields: ['lineWidth'],
|
||||
shapeId: 'keyShape',
|
||||
states: ['breathing'],
|
||||
iterations: Infinity,
|
||||
direction: 'alternate',
|
||||
duration: 500,
|
||||
},
|
||||
{
|
||||
fields: ['height', 'width'],
|
||||
shapeId: 'keyShape',
|
||||
states: ['scaling'],
|
||||
iterations: Infinity,
|
||||
direction: 'alternate',
|
||||
duration: 500,
|
||||
},
|
||||
],
|
||||
},
|
||||
...data,
|
||||
},
|
||||
};
|
||||
defaultNode: {
|
||||
type: 'rect',
|
||||
size: [80, 40],
|
||||
style: {
|
||||
fill: '#fff',
|
||||
lineWidth: 1,
|
||||
radius: 8,
|
||||
},
|
||||
labelCfg: {},
|
||||
},
|
||||
// 全局边 三次贝塞尔曲线
|
||||
edge: model => {
|
||||
const { id, source, target, data } = model;
|
||||
return {
|
||||
id,
|
||||
source,
|
||||
target,
|
||||
data: {
|
||||
type: 'cubic-edge',
|
||||
animates: {
|
||||
update: [
|
||||
{
|
||||
fields: ['lineDash'],
|
||||
shapeId: 'keyShape',
|
||||
states: ['growing', 'running'],
|
||||
iterations: Infinity,
|
||||
duration: 2000,
|
||||
},
|
||||
{
|
||||
fields: ['offsetDistance'],
|
||||
shapeId: 'buShape',
|
||||
states: ['circleRunning'],
|
||||
iterations: Infinity,
|
||||
duration: 2000,
|
||||
},
|
||||
],
|
||||
},
|
||||
...data,
|
||||
},
|
||||
};
|
||||
defaultEdge: {
|
||||
type: 'polyline',
|
||||
style: {
|
||||
offset: 20, // 拐弯处距离节点最小距离
|
||||
radius: 4, // 拐弯处的圆角弧度,若不设置则为直角
|
||||
lineWidth: 1,
|
||||
stroke: '#87e8de',
|
||||
},
|
||||
},
|
||||
// 全局框节点 矩形
|
||||
combo: model => {
|
||||
const { id, data } = model;
|
||||
return {
|
||||
id,
|
||||
data: {
|
||||
type: 'rect-combo',
|
||||
keyShape: {
|
||||
opacity: 0.8,
|
||||
padding: [20, 20, 20, 20],
|
||||
radius: 8,
|
||||
},
|
||||
labelShape: {
|
||||
text: data.label,
|
||||
offsetY: 8,
|
||||
},
|
||||
labelBackgroundShape: {},
|
||||
buildIn: [
|
||||
{
|
||||
fields: ['opacity'],
|
||||
duration: 500,
|
||||
delay: 500 + Math.random() * 500,
|
||||
},
|
||||
],
|
||||
buildOut: [
|
||||
{
|
||||
fields: ['opacity'],
|
||||
duration: 200,
|
||||
},
|
||||
],
|
||||
animates: {
|
||||
update: [
|
||||
{
|
||||
fields: ['width', 'height', 'x', 'y'],
|
||||
shapeId: 'keyShape',
|
||||
},
|
||||
],
|
||||
},
|
||||
...data,
|
||||
},
|
||||
};
|
||||
},
|
||||
// 节点状态
|
||||
nodeState: {
|
||||
breathing: {
|
||||
haloShape: {
|
||||
opacity: 0.25,
|
||||
lineWidth: 20,
|
||||
visible: true,
|
||||
},
|
||||
keyShape: {
|
||||
radius: 4,
|
||||
},
|
||||
},
|
||||
scaling: {
|
||||
keyShape: {
|
||||
width: 100,
|
||||
height: 60,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
edgeState: {
|
||||
growing: {
|
||||
keyShape: {
|
||||
lineWidth: 2,
|
||||
lineDash: ['100%', 0],
|
||||
},
|
||||
},
|
||||
running: {
|
||||
keyShape: {
|
||||
lineWidth: 2,
|
||||
lineDash: [2, 2],
|
||||
// TODO: lineDashOffset
|
||||
},
|
||||
defaultCombo: {
|
||||
type: 'rect', // Combo 类型
|
||||
size: [40, 40],
|
||||
// ... 其他配置
|
||||
style: {
|
||||
lineWidth: 1,
|
||||
},
|
||||
},
|
||||
data,
|
||||
});
|
||||
|
||||
graph.on('node:click', (e: any) => {
|
||||
const s = graph.getItemState(e.itemId);
|
||||
console.log(s);
|
||||
// graph.updateData('node', {
|
||||
// id: e.itemId,
|
||||
// data: {
|
||||
// cluster: Math.random(),
|
||||
// keyShape: {
|
||||
// r: 32 + Math.random() * 10 - 5,
|
||||
// lineWidth: 6 + Math.random() * 6 - 3,
|
||||
// stroke: '#000',
|
||||
// },
|
||||
// labelShape: {
|
||||
// fontWeight: 700,
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
});
|
||||
|
||||
graph.on('node:pointerenter', (e: any) => {
|
||||
const { itemId } = e;
|
||||
if (graph.getItemState(itemId, 'breathing')) {
|
||||
graph.setItemState(itemId, 'breathing', false);
|
||||
} else {
|
||||
graph.setItemState(itemId, 'scaling', false);
|
||||
graph.setItemState(itemId, 'breathing', true);
|
||||
}
|
||||
// graph.updateData('node', {
|
||||
// id: itemId,
|
||||
// data: {
|
||||
// label: `after been hovered ${itemId}`,
|
||||
// labelShape: {
|
||||
// fill: '#0f0',
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
});
|
||||
|
||||
graph.on('node:pointerleave', (e: any) => {
|
||||
const { itemId } = e;
|
||||
if (graph.getItemState(itemId, 'breathing')) {
|
||||
graph.setItemState(itemId, 'breathing', false);
|
||||
} else {
|
||||
graph.setItemState(itemId, 'scaling', false);
|
||||
graph.setItemState(itemId, 'breathing', true);
|
||||
}
|
||||
// graph.updateData('node', {
|
||||
// id: itemId,
|
||||
// data: {
|
||||
// label: 'label before been hovered',
|
||||
// labelShape: {
|
||||
// fill: '#000',
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
});
|
||||
graph.data(data); // 加载数据
|
||||
graph.render(); // 渲染
|
||||
}
|
||||
|
||||
const actions = {
|
||||
'Enable/Disable Node States': {
|
||||
Breathing: () => {
|
||||
graph.getAllNodesData().forEach((node: any) => {
|
||||
if (graph.getItemState(node.id, 'breathing')) {
|
||||
graph.setItemState(node.id, 'breathing', false);
|
||||
} else {
|
||||
graph.setItemState(node.id, 'scaling', false);
|
||||
graph.setItemState(node.id, 'breathing', true);
|
||||
}
|
||||
});
|
||||
},
|
||||
Scaling: () => {
|
||||
graph.getAllNodesData().forEach((node: any) => {
|
||||
if (graph.getItemState(node.id, 'scaling')) {
|
||||
graph.setItemState(node.id, 'scaling', false);
|
||||
} else {
|
||||
graph.setItemState(node.id, 'breathing', false);
|
||||
graph.setItemState(node.id, 'scaling', true);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
'Enable/Disable Edge States': {
|
||||
Growing: () => {
|
||||
graph.getAllEdgesData().forEach((edge: any) => {
|
||||
if (graph.getItemState(edge.id, 'growing')) {
|
||||
graph.setItemState(edge.id, 'growing', false);
|
||||
} else {
|
||||
graph.setItemState(edge.id, 'running', false);
|
||||
graph.setItemState(edge.id, 'growing', true);
|
||||
}
|
||||
});
|
||||
},
|
||||
Running: () => {
|
||||
graph.getAllEdgesData().forEach((edge: any) => {
|
||||
if (graph.getItemState(edge.id, 'running')) {
|
||||
graph.setItemState(edge.id, 'running', false);
|
||||
} else {
|
||||
graph.setItemState(edge.id, 'growing', false);
|
||||
graph.setItemState(edge.id, 'running', true);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// watch(
|
||||
// () => props.option,
|
||||
// val => {
|
||||
// if (val) {
|
||||
// nextTick(() => {
|
||||
// initChart();
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
initChart();
|
||||
|
||||
@@ -49,7 +49,7 @@ export default {
|
||||
hide: 'Hidden',
|
||||
open: 'Open',
|
||||
shut: 'Shut',
|
||||
launch: 'Launch',
|
||||
launch: 'Expand',
|
||||
fold: 'Fold',
|
||||
},
|
||||
rowId: 'ID',
|
||||
@@ -170,11 +170,11 @@ export default {
|
||||
settings: "Settings",
|
||||
},
|
||||
tabs: {
|
||||
reload: "Refresh current",
|
||||
reload: "Refresh Current Tab",
|
||||
more: "More Options",
|
||||
closeCurrent: "Close Current",
|
||||
closeOther: "Close Other",
|
||||
closeAll: "Close All",
|
||||
closeCurrent: "Close Current Tab",
|
||||
closeOther: "Close Other Tabs",
|
||||
closeAll: "Close All Tabs",
|
||||
}
|
||||
},
|
||||
|
||||
@@ -402,6 +402,7 @@ export default {
|
||||
createTime:'Creation time',
|
||||
onlyAble:'Only upload file format {fileText} is supported',
|
||||
nullData:'No network element list data yet',
|
||||
nullVersion:'There is no rollback version for the current network element.',
|
||||
},
|
||||
license: {
|
||||
neTypePlease: 'Select network element type',
|
||||
@@ -464,7 +465,7 @@ export default {
|
||||
editOkTip: "Confirm updating the value of this {num} attribute?",
|
||||
updateItemTip: "Confirm updating the data item with Index [{num}]?",
|
||||
delItemTip: "Confirm deleting the data item with Index [{num}]?",
|
||||
arrayMore: "commence",
|
||||
arrayMore: "Expand",
|
||||
},
|
||||
},
|
||||
neUser: {
|
||||
@@ -545,6 +546,7 @@ export default {
|
||||
imsiTip2: 'MNC = Mobile Network Number, consisting of two digits',
|
||||
imsiTip3: 'MSIN = Mobile Subscriber Identification Number, consisting of 10 equal digits.',
|
||||
delSure:'Are you sure you want to delete the user with IMSI number: {imsi}?',
|
||||
uploadFileOk: 'File Upload Successful',
|
||||
uploadFileErr: 'File Upload Failed',
|
||||
},
|
||||
base5G: {
|
||||
@@ -1302,6 +1304,7 @@ export default {
|
||||
dictData:'Dictionary Data',
|
||||
reload:'Refresh Cache',
|
||||
mark:'Dictionary Description',
|
||||
colorSelect:'Color Picker',
|
||||
},
|
||||
dictData: {
|
||||
dictType: "Dictionary name",
|
||||
|
||||
@@ -170,11 +170,11 @@ export default {
|
||||
settings: "个人设置",
|
||||
},
|
||||
tabs: {
|
||||
reload: "刷新当前",
|
||||
reload: "刷新当前标签",
|
||||
more: "更多选项",
|
||||
closeCurrent: "关闭当前",
|
||||
closeOther: "关闭其他",
|
||||
closeAll: "关闭全部",
|
||||
closeCurrent: "关闭当前标签",
|
||||
closeOther: "关闭其他标签",
|
||||
closeAll: "关闭全部标签",
|
||||
}
|
||||
},
|
||||
|
||||
@@ -402,6 +402,7 @@ export default {
|
||||
createTime:'创建时间',
|
||||
onlyAble:'只支持上传文件格式 {fileText}',
|
||||
nullData:'暂无网元列表数据',
|
||||
nullVersion:'当前网元无可回退版本',
|
||||
},
|
||||
license: {
|
||||
neTypePlease: '选择网元类型',
|
||||
@@ -545,6 +546,7 @@ export default {
|
||||
imsiTip2: 'MNC=移动网络号,由两位数字组成',
|
||||
imsiTip3: 'MSIN=移动客户识别码,采用等长10位数字构成',
|
||||
delSure:'确认删除IMSI编号为: {imsi} 的用户吗?',
|
||||
uploadFileOk: '文件上传成功',
|
||||
uploadFileErr: '文件上传失败',
|
||||
},
|
||||
base5G: {
|
||||
@@ -1302,6 +1304,7 @@ export default {
|
||||
dictData:'字典数据',
|
||||
reload:'刷新缓存',
|
||||
mark:'字典说明',
|
||||
colorSelect:'取色器',
|
||||
},
|
||||
dictData: {
|
||||
dictType: "字典名称",
|
||||
|
||||
@@ -14,6 +14,11 @@ type AppStore = {
|
||||
/**应用版本 */
|
||||
appVersion: string;
|
||||
|
||||
/**服务版本 */
|
||||
version: string,
|
||||
buildTime: string,
|
||||
// 序列号
|
||||
serialNum: string,
|
||||
/**应用版权声明 */
|
||||
copyright: string;
|
||||
/**LOGO显示类型 */
|
||||
@@ -41,6 +46,9 @@ const useAppStore = defineStore('app', {
|
||||
appCode: import.meta.env.VITE_APP_CODE,
|
||||
appVersion: import.meta.env.VITE_APP_VERSION,
|
||||
|
||||
version: `-`,
|
||||
buildTime: `-`,
|
||||
serialNum: `-`,
|
||||
copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`,
|
||||
logoType: 'icon',
|
||||
filePathIcon: '',
|
||||
@@ -70,6 +78,9 @@ const useAppStore = defineStore('app', {
|
||||
async fnSysConf() {
|
||||
const res = await getSysConf();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
this.version = res.data.version;
|
||||
this.buildTime = res.data.buildTime;
|
||||
this.serialNum = res.data.serialNum;
|
||||
this.appName = res.data.title;
|
||||
this.copyright = res.data.copyright;
|
||||
this.logoType = res.data.logoType;
|
||||
|
||||
@@ -837,6 +837,8 @@ function arrayEditInit(data: Record<string, any>, dataRule: any) {
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
// 可选的
|
||||
row.optional = 'true';
|
||||
// 根据规则类型转值
|
||||
if (['enum', 'int'].includes(row.type)) {
|
||||
row.value = Number(item.value);
|
||||
@@ -870,6 +872,8 @@ function arrayAddInit(data: any[], dataRule: any) {
|
||||
row.value = [];
|
||||
continue;
|
||||
}
|
||||
// 可选的
|
||||
row.optional = 'true';
|
||||
// index值
|
||||
if (row.name === 'index') {
|
||||
let newIndex = dataLastIndex !== 0 ? dataLastIndex : parseInt(row.value);
|
||||
@@ -904,7 +908,7 @@ function ruleVerification(row: Record<string, any>): (string | boolean)[] {
|
||||
if (row.array) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// 可选的同时没有值不检查
|
||||
if (row.optional === 'true' && !value) {
|
||||
return result;
|
||||
|
||||
@@ -453,26 +453,31 @@ function fnRecordDelete(row: Record<string, any>) {
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.configManage.neManage.delSure', { msg: row.neName }),
|
||||
onOk() {
|
||||
tableState.loading = true;
|
||||
const key = 'delNotice';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
delNeInfo(row).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.deleteText') }),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
// 刷新缓存的网元信息
|
||||
useNeInfoStore().fnRefreshNelist();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
delNeInfo(row)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.deleteText') }),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
// 刷新缓存的网元信息
|
||||
useNeInfoStore().fnRefreshNelist();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
tableState.loading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -902,7 +907,9 @@ onMounted(() => {
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
key="reload"
|
||||
v-if="!['OMC', 'PCF', 'IMS', 'MME'].includes(record.neType)"
|
||||
v-if="
|
||||
!['OMC', 'PCF', 'IMS', 'MME'].includes(record.neType)
|
||||
"
|
||||
>
|
||||
<SyncOutlined />
|
||||
{{ t('views.configManage.neManage.reload') }}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import SoftwareHistory from './components/software-history.vue';
|
||||
|
||||
import { listNeVersion } from '@/api/configManage/softwareManage';
|
||||
import {
|
||||
listNeSoftware,
|
||||
delNeSoftware,
|
||||
@@ -154,14 +154,20 @@ type FileStateType = {
|
||||
visible: boolean;
|
||||
/**框类型 */
|
||||
visibleType: string;
|
||||
/**回退框是否显示 */
|
||||
visibleByBack: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**提示内容 */
|
||||
content: string;
|
||||
/**OK按钮是否禁用 */
|
||||
okDisable: boolean;
|
||||
/**网元参数 */
|
||||
neOtions: Record<string, any>[];
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**回退表单数据 */
|
||||
backFrom: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
@@ -170,12 +176,20 @@ type FileStateType = {
|
||||
let fileModalState: FileStateType = reactive({
|
||||
visible: false,
|
||||
visibleType: 'send',
|
||||
visibleByBack: false,
|
||||
title: '下发激活回退',
|
||||
content: '',
|
||||
okDisable: false,
|
||||
neOtions: [],
|
||||
from: {
|
||||
neId: undefined,
|
||||
},
|
||||
backFrom: {
|
||||
ne:undefined,
|
||||
neType: undefined,
|
||||
neId: undefined,
|
||||
version: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
@@ -192,6 +206,19 @@ const fileModalStateFrom = Form.useForm(
|
||||
})
|
||||
);
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const fileModalStateBackFrom = Form.useForm(
|
||||
fileModalState.backFrom,
|
||||
reactive({
|
||||
ne: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.configManage.softwareManage.neIdPlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 文件对话框弹出显示为 下发或激活
|
||||
*/
|
||||
@@ -217,13 +244,7 @@ function fnFileModalVisible(type: string | number, row: Record<string, any>) {
|
||||
fileName: row.fileName,
|
||||
});
|
||||
}
|
||||
if (type === 'back') {
|
||||
fileModalState.title = t('views.configManage.softwareManage.backTitle');
|
||||
fileModalState.content = t(
|
||||
'views.configManage.softwareManage.backContent',
|
||||
{ fileName: row.fileName }
|
||||
);
|
||||
}
|
||||
|
||||
if (!fileModalState.content) {
|
||||
return;
|
||||
}
|
||||
@@ -263,9 +284,6 @@ function fnFileModalOk() {
|
||||
if (type === 'run') {
|
||||
fnType = runNeSoftware(from);
|
||||
}
|
||||
if (type === 'back') {
|
||||
fnType = backNeSoftware(from);
|
||||
}
|
||||
if (fnType === null) {
|
||||
return;
|
||||
}
|
||||
@@ -396,6 +414,7 @@ type ModalStateType = {
|
||||
visibleByEdit: boolean;
|
||||
/**网元版本历史框是否显示 */
|
||||
visibleByHistory: boolean;
|
||||
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
@@ -408,6 +427,7 @@ type ModalStateType = {
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByEdit: false,
|
||||
visibleByHistory: false,
|
||||
visibleByBack: false,
|
||||
title: '上传更新',
|
||||
from: {
|
||||
neType: undefined,
|
||||
@@ -512,8 +532,10 @@ function fnModalOk() {
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
fileModalState.visibleByBack = false;
|
||||
modalState.visibleByHistory = false;
|
||||
modalStateFrom.resetFields();
|
||||
fileModalStateBackFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -523,6 +545,92 @@ function fnModalVisibleByHistory() {
|
||||
modalState.visibleByHistory = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 回退框
|
||||
*/
|
||||
function fnModalVisibleByBack() {
|
||||
fileModalState.visibleByBack = true;
|
||||
//fileModalStateBackFrom.resetFields();
|
||||
fileModalState.title = t('views.configManage.softwareManage.backTitle');
|
||||
fileModalState.content = t('views.configManage.softwareManage.neIdPlease');
|
||||
|
||||
if (!fileModalState.content) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**回退网元类型选择对应修改 */
|
||||
function fnNeChange(_: any, item: any) {
|
||||
fileModalState.backFrom.neType = item[1].neType;
|
||||
fileModalState.backFrom.neId = item[1].neId;
|
||||
let queryData: any = fileModalState.backFrom;
|
||||
queryData.status = 'Active';
|
||||
queryData.pageNum = 1;
|
||||
queryData.pageSize = 20;
|
||||
listNeVersion(toRaw(queryData)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
if (
|
||||
!res.total ||
|
||||
!res.rows[0].preVersion ||
|
||||
res.rows[0].preVersion === '-'
|
||||
) {
|
||||
fileModalState.okDisable = true;
|
||||
fileModalState.content = t(
|
||||
'views.configManage.softwareManage.nullVersion'
|
||||
);
|
||||
tableState.loading = false;
|
||||
return;
|
||||
}
|
||||
fileModalState.content = t(
|
||||
'views.configManage.softwareManage.backContent',
|
||||
{ fileName: res.rows[0].preVersion }
|
||||
);
|
||||
fileModalState.backFrom.version = res.rows[0].preVersion;
|
||||
fileModalState.okDisable = false;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 回退对话框弹出确认执行函数
|
||||
*/
|
||||
function fnBackModalOk() {
|
||||
fileModalStateBackFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
const from = toRaw(fileModalState.backFrom);
|
||||
// 发送请求
|
||||
fileModalState.confirmLoading = true;
|
||||
const hide = message.loading({ content: t('common.loading') });
|
||||
backNeSoftware(from)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: fileModalState.title }),
|
||||
duration: 3,
|
||||
});
|
||||
fnFileModalCancel();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${fileModalState.title} ${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
fileModalState.visibleByBack = false;
|
||||
fileModalStateBackFrom.resetFields();
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
fileModalState.visibleByBack = false;
|
||||
fileModalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**上传前检查或转换压缩 */
|
||||
function fnBeforeUploadFile(file: FileType) {
|
||||
if (modalState.confirmLoading) return false;
|
||||
@@ -537,10 +645,10 @@ function fnBeforeUploadFile(file: FileType) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// 根据给定的软件名取版本号 ims-r2.2312.8_u18.deb
|
||||
const nameArr = fileName.split('.')
|
||||
if(nameArr.length > 3) {
|
||||
modalState.from.version = nameArr[1]
|
||||
// 根据给定的软件名取版本号 ims-r2.2312.x-ub22.deb
|
||||
const nameArr = fileName.split('.');
|
||||
if (nameArr.length > 3) {
|
||||
modalState.from.version = nameArr[1];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -657,6 +765,10 @@ onMounted(() => {
|
||||
<template #icon><HistoryOutlined /></template>
|
||||
{{ t('views.configManage.softwareManage.historyBtn') }}
|
||||
</a-button>
|
||||
<a-button type="dashed" @click.prevent="fnModalVisibleByBack()">
|
||||
<template #icon> <UndoOutlined /></template>
|
||||
{{ t('views.configManage.softwareManage.backBtn') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
@@ -762,10 +874,6 @@ onMounted(() => {
|
||||
<DeleteOutlined />
|
||||
{{ t('common.deleteText') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="back">
|
||||
<UndoOutlined />
|
||||
{{ t('views.configManage.softwareManage.backBtn') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
@@ -879,7 +987,7 @@ onMounted(() => {
|
||||
@cancel="fnModalCancel"
|
||||
/>
|
||||
|
||||
<!-- 文件框 下发激活回退 -->
|
||||
<!-- 文件框 下发激活 -->
|
||||
<a-modal
|
||||
width="600px"
|
||||
:keyboard="false"
|
||||
@@ -910,6 +1018,41 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 回退框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="fileModalState.visibleByBack"
|
||||
:title="fileModalState.title"
|
||||
:confirm-loading="fileModalState.confirmLoading"
|
||||
@ok="fnBackModalOk"
|
||||
:ok-button-props="{ disabled: fileModalState.okDisable }"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="fileModalState" layout="horizontal">
|
||||
<a-form-item name="content">
|
||||
<QuestionCircleOutlined class="file-model__icon" />
|
||||
<span class="file-model__tip">
|
||||
{{ fileModalState.content }}
|
||||
</span>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.configManage.license.neType')"
|
||||
name="ne"
|
||||
v-bind="fileModalStateBackFrom.validateInfos.ne"
|
||||
>
|
||||
<a-cascader
|
||||
v-model:value="fileModalState.backFrom.ne"
|
||||
:options="useNeInfoStore().getNeCascaderOptions"
|
||||
:allow-clear="false"
|
||||
@change="fnNeChange"
|
||||
:placeholder="t('views.configManage.softwareManage.neTypePlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1029,7 +1029,7 @@ onMounted(() => {
|
||||
:data-source="alarmTableState.data"
|
||||
:size="alarmTableState.size"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 1700, y: 560 }"
|
||||
:scroll="{ x: 1700, y: '82vh' }"
|
||||
>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
@@ -1575,6 +1575,9 @@ onMounted(() => {
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.full-modal {
|
||||
.ant-modal {
|
||||
max-width: 100%;
|
||||
|
||||
@@ -340,7 +340,7 @@ function fnModalVisibleByVive(row: Record<string, any>) {
|
||||
function fnModalOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
if (from.ackState) {
|
||||
if (from.ackState==='1') {
|
||||
message.error({
|
||||
content: t('views.faultManage.activeAlarm.ackError'),
|
||||
duration: 3,
|
||||
|
||||
@@ -34,7 +34,12 @@ echarts.use([
|
||||
/**用户性别字典 */
|
||||
let indexColor = ref<DictType[]>([
|
||||
{ label: 'normal', value: 'normal', elTagType: '', elTagClass: '#91cc75' },
|
||||
{ label: 'abnormal', value: 'abnormal', elTagType: '', elTagClass: '#ee6666' },
|
||||
{
|
||||
label: 'abnormal',
|
||||
value: 'abnormal',
|
||||
elTagType: '',
|
||||
elTagClass: '#ee6666',
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格字段列 */
|
||||
@@ -101,10 +106,10 @@ let tableState: TabeStateType = reactive({
|
||||
/**表格状态 */
|
||||
let nfInfo: any = reactive({
|
||||
obj: 'OMC',
|
||||
version: '2.2312.8',
|
||||
version: appStore.version,
|
||||
status: t('views.index.normal'),
|
||||
number: '',
|
||||
outTimeDate: '',
|
||||
serialNum: appStore.serialNum,
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
@@ -158,7 +163,7 @@ function fnGetList(one: boolean) {
|
||||
tableState.loading = false;
|
||||
var rightNum = 0;
|
||||
var errorNum = 0;
|
||||
|
||||
// if (res.length) nfInfo.serialNum = res[0].serialNum;
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
if (res[i].status == '正常' || res[i].status == 'Normal') {
|
||||
rightNum++;
|
||||
@@ -166,9 +171,16 @@ function fnGetList(one: boolean) {
|
||||
errorNum++;
|
||||
}
|
||||
}
|
||||
echarts.init(document.getElementById('echarts-records')).clear();
|
||||
var chartDom = document.getElementById('echarts-records');
|
||||
var myChart = echarts.init(chartDom);
|
||||
|
||||
var chartDom: any = document.getElementById('echarts-records');
|
||||
var existingChart = echarts.getInstanceByDom(chartDom);
|
||||
var myChart: any;
|
||||
if (existingChart) {
|
||||
myChart = existingChart;
|
||||
myChart.clear(); // 清空图表,重新设置数据
|
||||
} else {
|
||||
myChart = echarts.init(chartDom);
|
||||
}
|
||||
|
||||
var option = {
|
||||
title: {
|
||||
@@ -218,125 +230,6 @@ function fnGetList(one: boolean) {
|
||||
});
|
||||
}
|
||||
|
||||
/**點擊網元名 */
|
||||
function init(e: any) {
|
||||
let realData = toRaw(e);
|
||||
var chartDom = document.getElementById('echarts-records');
|
||||
var myChart = echarts.init(chartDom);
|
||||
myChart.clear(); //怕遗留以前得元素
|
||||
|
||||
let cpuUsage = realData.cpuUsage;
|
||||
let memUsage = realData.memUsage;
|
||||
var nfMenUsage =
|
||||
Math.round((memUsage?.nfUsedMem / memUsage?.totalMem) * 10000) / 100.0;
|
||||
let partitionInfo = realData.diskSpace?.partitionInfo[1];
|
||||
var nfMaxDiskSpace =
|
||||
Math.round((partitionInfo?.used / partitionInfo?.total) * 10000) / 100.0;
|
||||
let option = {
|
||||
tooltip: {
|
||||
formatter: '{a} <br/>{b} : {c}%',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Nf Memory usage',
|
||||
center: ['17%', '50%'],
|
||||
radius: '80%',
|
||||
type: 'gauge',
|
||||
progress: {
|
||||
show: true,
|
||||
},
|
||||
pointer: {
|
||||
//仪表盘的指针
|
||||
//这个show属性好像有问题,因为在这次开发中,需要去掉指正,我设置false的时候,还是显示指针,估计是BUG吧,我用的echarts-3.2.3;希望改进。最终,我把width属性设置为0,成功搞定!
|
||||
show: true,
|
||||
//指针长度
|
||||
length: '60%',
|
||||
},
|
||||
title: {
|
||||
offsetCenter: [0, '90%'],
|
||||
},
|
||||
detail: {
|
||||
valueAnimation: true,
|
||||
formatter: '{value}',
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
},
|
||||
offsetCenter: [0, '60%'],
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: nfMenUsage,
|
||||
name: 'MEM',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
center: ['50%', '50%'],
|
||||
name: 'Nf CPU usage',
|
||||
radius: '80%',
|
||||
type: 'gauge',
|
||||
progress: {
|
||||
show: true,
|
||||
},
|
||||
|
||||
title: {
|
||||
offsetCenter: [0, '90%'],
|
||||
},
|
||||
detail: {
|
||||
valueAnimation: true,
|
||||
formatter: '{value}',
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
},
|
||||
offsetCenter: [0, '60%'],
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: cpuUsage?.nfCpuUsage / 100,
|
||||
name: 'CPU',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
center: ['83%', '50%'],
|
||||
name: 'NF maximum disk usage',
|
||||
radius: '80%',
|
||||
type: 'gauge',
|
||||
progress: {
|
||||
show: true,
|
||||
},
|
||||
title: {
|
||||
offsetCenter: [0, '90%'],
|
||||
},
|
||||
detail: {
|
||||
valueAnimation: true,
|
||||
formatter: '{value}',
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
},
|
||||
offsetCenter: [0, '60%'],
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: nfMaxDiskSpace,
|
||||
name: 'DiskSpace',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
option && myChart.setOption(option);
|
||||
window.onresize = function () {
|
||||
// echarts 窗口缩放自适应 随着div--echarts-records的大小来适应
|
||||
myChart.resize();
|
||||
};
|
||||
|
||||
nfInfo.obj = realData.name;
|
||||
nfInfo.number = realData.serialNum;
|
||||
nfInfo.outTimeDate = realData.expiryDate;
|
||||
}
|
||||
|
||||
/**抽屉 网元详细信息 */
|
||||
const visible = ref(false);
|
||||
const closeDrawer = () => {
|
||||
@@ -481,9 +374,7 @@ onBeforeUnmount(() => {
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<div v-if="record.status == '正常' || record.status == 'Normal'">
|
||||
<a-tag color="blue" @click="init(record)">{{
|
||||
record.name
|
||||
}}</a-tag>
|
||||
<a-tag color="blue">{{ record.name }}</a-tag>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-tag color="pink">{{ record.name }}</a-tag>
|
||||
@@ -509,6 +400,9 @@ onBeforeUnmount(() => {
|
||||
nfInfo.obj
|
||||
}}</a-descriptions-item>
|
||||
<template v-if="nfInfo.obj === 'OMC'">
|
||||
<a-descriptions-item :label="t('views.index.serialNum')">{{
|
||||
nfInfo.serialNum
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.versionNum')">{{
|
||||
nfInfo.version
|
||||
}}</a-descriptions-item>
|
||||
@@ -518,7 +412,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-descriptions-item :label="t('views.index.serialNum')">{{
|
||||
nfInfo.number
|
||||
nfInfo.serialNum
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.expiryDate')">{{
|
||||
nfInfo.outTimeDate
|
||||
|
||||
@@ -202,6 +202,7 @@ function fnNeChange(keys: any, _: any) {
|
||||
queryParams.neId = keys[1];
|
||||
if (neType === 'IMS') {
|
||||
nePathArr.value = ['/var/log/ims'];
|
||||
queryParams.search = '';
|
||||
} else {
|
||||
nePathArr.value = ['/var/log'];
|
||||
queryParams.search = neType.toLowerCase();
|
||||
|
||||
1742
src/views/monitor/topology-build/graph.ts
Normal file
1742
src/views/monitor/topology-build/graph.ts
Normal file
File diff suppressed because it is too large
Load Diff
656
src/views/monitor/topology-build/index.vue
Normal file
656
src/views/monitor/topology-build/index.vue
Normal file
@@ -0,0 +1,656 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listNe, stateNe } from '@/api/ne/ne';
|
||||
import message from 'ant-design-vue/lib/message';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { Graph, Util } from '@antv/g6';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图实例对象 */
|
||||
const graphG6 = ref<any>(null);
|
||||
|
||||
/**图数据 */
|
||||
const graphG6Data = reactive<Record<string, any>>({
|
||||
nodes: [
|
||||
// 0 基站
|
||||
{
|
||||
id: '0',
|
||||
x: 50,
|
||||
y: 150,
|
||||
size: 48,
|
||||
type: 'circle',
|
||||
label: '基站',
|
||||
labelCfg: {
|
||||
position: 'bottom',
|
||||
offset: 10,
|
||||
},
|
||||
style: {
|
||||
fill: '#9EC9FF',
|
||||
stroke: '#5B8FF9',
|
||||
lineWidth: 2,
|
||||
},
|
||||
icon: {
|
||||
show: true,
|
||||
// 可更换为其他图片地址
|
||||
img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
},
|
||||
// 1 DN
|
||||
{
|
||||
id: '1',
|
||||
x: 450,
|
||||
y: 450,
|
||||
label: 'DN',
|
||||
labelCfg: {
|
||||
position: 'center',
|
||||
},
|
||||
style: {
|
||||
fill: '#00b050',
|
||||
stroke: '#00b050',
|
||||
lineWidth: 1,
|
||||
},
|
||||
},
|
||||
// 2 O&M
|
||||
{
|
||||
id: '2',
|
||||
x: 50,
|
||||
y: 450,
|
||||
label: 'O&M',
|
||||
},
|
||||
// 100 EMS
|
||||
{
|
||||
id: '100',
|
||||
label: 'EMS',
|
||||
comboId: 'combo-ems',
|
||||
x: 300,
|
||||
y: 450,
|
||||
},
|
||||
// 190 UPF
|
||||
{
|
||||
id: '190',
|
||||
comboId: 'combo-upf',
|
||||
x: 300,
|
||||
y: 350,
|
||||
label: 'UPF',
|
||||
labelCfg: {
|
||||
position: 'center',
|
||||
},
|
||||
style: {
|
||||
fill: '#d580ff',
|
||||
stroke: '#d580ff',
|
||||
lineWidth: 1,
|
||||
},
|
||||
},
|
||||
// EP-IMS
|
||||
{
|
||||
id: '110',
|
||||
comboId: 'combo-ims',
|
||||
x: 600,
|
||||
y: 350,
|
||||
label: 'IMS',
|
||||
labelCfg: {
|
||||
position: 'center',
|
||||
},
|
||||
style: {
|
||||
fill: '#ed7d31',
|
||||
stroke: '#ed7d31',
|
||||
lineWidth: 1,
|
||||
},
|
||||
},
|
||||
// 5GC控制面
|
||||
{
|
||||
id: '170',
|
||||
label: 'NSSF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 300,
|
||||
y: 50,
|
||||
},
|
||||
{
|
||||
id: '130',
|
||||
label: 'AUSF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 450,
|
||||
y: 50,
|
||||
},
|
||||
{
|
||||
id: '140',
|
||||
label: 'UDM',
|
||||
comboId: 'combo-5gc',
|
||||
x: 600,
|
||||
y: 50,
|
||||
},
|
||||
{
|
||||
id: '120',
|
||||
label: 'AMF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 300,
|
||||
y: 150,
|
||||
},
|
||||
{
|
||||
id: '180',
|
||||
label: 'NRF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 450,
|
||||
y: 150,
|
||||
},
|
||||
{
|
||||
id: '150',
|
||||
label: 'SMF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 300,
|
||||
y: 250,
|
||||
},
|
||||
{
|
||||
id: '160',
|
||||
label: 'PCF',
|
||||
comboId: 'combo-5gc',
|
||||
x: 700,
|
||||
y: 250,
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: '0-5gc',
|
||||
source: '0',
|
||||
target: 'combo-5gc',
|
||||
},
|
||||
{
|
||||
id: '0-upf',
|
||||
source: '0',
|
||||
target: 'combo-upf',
|
||||
},
|
||||
{
|
||||
id: 'upf-1',
|
||||
source: 'combo-upf',
|
||||
target: '1',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'ems-2',
|
||||
source: 'combo-ems',
|
||||
target: '2',
|
||||
},
|
||||
{
|
||||
id: '170-120',
|
||||
source: '170',
|
||||
target: '120',
|
||||
},
|
||||
{
|
||||
id: '130-120',
|
||||
source: '130',
|
||||
target: '120',
|
||||
},
|
||||
{
|
||||
id: '140-120',
|
||||
source: '140',
|
||||
target: '120',
|
||||
},
|
||||
{
|
||||
id: '140-180',
|
||||
source: '140',
|
||||
target: '180',
|
||||
},
|
||||
{
|
||||
id: '120-180',
|
||||
source: '120',
|
||||
target: '180',
|
||||
},
|
||||
{
|
||||
id: '130-180',
|
||||
source: '130',
|
||||
target: '180',
|
||||
},
|
||||
{
|
||||
id: '140-150',
|
||||
source: '140',
|
||||
target: '150',
|
||||
},
|
||||
{
|
||||
id: '140-110',
|
||||
source: '140',
|
||||
target: '110',
|
||||
},
|
||||
{
|
||||
id: '120-150',
|
||||
source: '120',
|
||||
target: '150',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: '150-180',
|
||||
source: '150',
|
||||
target: '180',
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: '150-160',
|
||||
source: '150',
|
||||
target: '160',
|
||||
},
|
||||
{
|
||||
id: '160-120',
|
||||
source: '160',
|
||||
target: '120',
|
||||
},
|
||||
{
|
||||
id: '160-180',
|
||||
source: '160',
|
||||
target: '180',
|
||||
},
|
||||
{
|
||||
id: '160-110',
|
||||
source: '160',
|
||||
target: '110',
|
||||
},
|
||||
|
||||
{
|
||||
id: '150-190',
|
||||
source: '150',
|
||||
target: '190',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'upf-ims',
|
||||
source: 'combo-upf',
|
||||
target: 'combo-ims',
|
||||
},
|
||||
{
|
||||
id: 'ems-5gc',
|
||||
source: 'combo-ems',
|
||||
target: 'combo-5gc',
|
||||
},
|
||||
{
|
||||
id: 'ems-upf',
|
||||
source: 'combo-ems',
|
||||
target: 'combo-upf',
|
||||
},
|
||||
{
|
||||
id: 'ems-ims',
|
||||
source: 'combo-ems',
|
||||
target: 'combo-ims',
|
||||
},
|
||||
],
|
||||
combos: [
|
||||
{
|
||||
id: 'combo-5gc',
|
||||
label: 'combo 5GC控制面',
|
||||
},
|
||||
{
|
||||
id: 'combo-upf',
|
||||
label: 'combo upf',
|
||||
},
|
||||
{
|
||||
id: 'combo-ims',
|
||||
label: 'combo ims',
|
||||
},
|
||||
{
|
||||
id: 'combo-ems',
|
||||
label: 'Combo ems',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**图绑定事件 */
|
||||
function graphEvent(graph: Graph) {
|
||||
// 鼠标进入节点事件
|
||||
graph.on('edge:mouseenter', (ev: any) => {
|
||||
// 获得鼠标当前目标边
|
||||
const edge = ev.item;
|
||||
// 该边的起始点
|
||||
const source = edge.getSource();
|
||||
// 该边的结束点
|
||||
const target = edge.getTarget();
|
||||
// 先将边提前,再将端点提前。这样该边两个端点还是在该边上层,较符合常规。
|
||||
edge.toFront();
|
||||
source.toFront();
|
||||
target.toFront();
|
||||
});
|
||||
|
||||
graph.on('edge:mouseleave', (ev: any) => {
|
||||
// 获得图上所有边实例
|
||||
const edges = graph.getEdges();
|
||||
// 遍历边,将所有边的层级放置在后方,以恢复原样
|
||||
edges.forEach(edge => {
|
||||
edge.toBack();
|
||||
});
|
||||
});
|
||||
|
||||
graph.on('node:mouseenter', (ev: any) => {
|
||||
// 获得鼠标当前目标节点
|
||||
const node = ev.item;
|
||||
// 获取该节点的所有相关边
|
||||
const edges = node.getEdges();
|
||||
// 遍历相关边,将所有相关边提前,再将相关边的两个端点提前,以保证相关边的端点在边的上方常规效果
|
||||
edges.forEach((edge: any) => {
|
||||
edge.toFront();
|
||||
edge.getSource().toFront();
|
||||
edge.getTarget().toFront();
|
||||
});
|
||||
});
|
||||
|
||||
graph.on('node:mouseleave', (ev: any) => {
|
||||
// 获得图上所有边实例
|
||||
const edges = graph.getEdges();
|
||||
// 遍历边,将所有边的层级放置在后方,以恢复原样
|
||||
edges.forEach(edge => {
|
||||
edge.toBack();
|
||||
});
|
||||
});
|
||||
|
||||
// 使用内置交互 create-edge,创建边之后触发
|
||||
graph.on('aftercreateedge', e => {
|
||||
console.log(JSON.parse(JSON.stringify(graph.save())));
|
||||
const edges = graph.save().edges as any;
|
||||
// Util.processParallelEdges(edges);
|
||||
graph.getEdges().forEach((edge, i) => {
|
||||
graph.updateItem(edge, {
|
||||
curveOffset: edges[i].curveOffset,
|
||||
curvePosition: edges[i].curvePosition,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**查询全部网元数据列表 */
|
||||
function fnRanderGraph() {
|
||||
if (!graphG6Dom.value) return;
|
||||
const { clientHeight, clientWidth } = graphG6Dom.value;
|
||||
|
||||
const graph = new Graph({
|
||||
container: graphG6Dom.value,
|
||||
width: clientWidth,
|
||||
height: clientHeight,
|
||||
animate: true,
|
||||
fitCenter: true,
|
||||
modes: {
|
||||
// default: [
|
||||
// // 允许拖拽画布、放缩画布、拖拽节点
|
||||
// 'drag-canvas',
|
||||
// 'zoom-canvas',
|
||||
// 'drag-node',
|
||||
// ],
|
||||
default: [
|
||||
{
|
||||
type: 'click-select',
|
||||
selectEdge: true,
|
||||
},
|
||||
'drag-combo',
|
||||
{
|
||||
type: 'drag-node',
|
||||
onlyChangeComboSize: true,
|
||||
},
|
||||
'drag-canvas',
|
||||
'zoom-canvas',
|
||||
'collapse-expand-combo',
|
||||
{
|
||||
type: 'create-edge',
|
||||
trigger: 'click', // 'click' by default. options: 'drag', 'click'
|
||||
},
|
||||
],
|
||||
edit: [
|
||||
{
|
||||
type: 'create-edge',
|
||||
trigger: 'click', // 'click' by default. options: 'drag', 'click'
|
||||
},
|
||||
],
|
||||
},
|
||||
groupByTypes: false,
|
||||
// layout: {
|
||||
// type: 'dagre',
|
||||
// sortByCombo: false,
|
||||
// ranksep: 10,
|
||||
// nodesep: 10,
|
||||
// },
|
||||
// 全局节点 矩形
|
||||
defaultNode: {
|
||||
type: 'rect',
|
||||
size: [80, 40],
|
||||
style: {
|
||||
radius: 8,
|
||||
},
|
||||
},
|
||||
// 全局边 三次贝塞尔曲线
|
||||
defaultEdge: {
|
||||
type: 'polyline',
|
||||
style: {
|
||||
offset: 20, // 拐弯处距离节点最小距离
|
||||
radius: 4, // 拐弯处的圆角弧度,若不设置则为直角
|
||||
lineWidth: 1,
|
||||
},
|
||||
},
|
||||
// 全局框节点 矩形
|
||||
defaultCombo: {
|
||||
type: 'rect', // Combo 类型
|
||||
size: [40, 40],
|
||||
style: {
|
||||
fillOpacity: 0.1,
|
||||
},
|
||||
},
|
||||
});
|
||||
graph.data(graphG6Data);
|
||||
graph.render();
|
||||
|
||||
graphEvent(graph);
|
||||
|
||||
graphG6.value = graph;
|
||||
}
|
||||
|
||||
/**查询网元状态 */
|
||||
async function fnGetState() {
|
||||
for (const node of graphG6Data.nodes) {
|
||||
const ne = node.info;
|
||||
if (ne.neType === 'OMC') continue;
|
||||
const result = await stateNe(ne.neType, ne.neId);
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
ne.serverState = result.data;
|
||||
ne.serverState.refreshTime = parseDateToStr(
|
||||
ne.serverState.refreshTime,
|
||||
'HH:mm:ss'
|
||||
);
|
||||
const node = graphG6.value.findById(ne.neName);
|
||||
console.log('查询网元状态', node);
|
||||
graphG6.value.setItemState(node, 'neState', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**查询全部网元数据列表 */
|
||||
function fnGetList(refresh: boolean = false) {
|
||||
listNe({
|
||||
bandStatus: false,
|
||||
})
|
||||
.then(res => {
|
||||
if (
|
||||
res.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(res.data) &&
|
||||
res.data.length > 0
|
||||
) {
|
||||
let rootNode = 'OMC';
|
||||
const nodes = [];
|
||||
const edges = [];
|
||||
for (const item of res.data) {
|
||||
item.serverState = {};
|
||||
const nodeIndex = nodes.findIndex(v => v.id === item.neName);
|
||||
if (nodeIndex === -1) {
|
||||
// 根网管
|
||||
if (item.neType === 'OMC') {
|
||||
rootNode = item.neName;
|
||||
item.serverState = {
|
||||
neId: item.neId,
|
||||
neName: item.neName,
|
||||
neType: item.neType,
|
||||
expire: '2024-03-31',
|
||||
refreshTime: '10:31:47',
|
||||
sn: '13770707',
|
||||
version: '2.2312.8',
|
||||
};
|
||||
nodes.push({
|
||||
id: item.neName,
|
||||
label: item.neName,
|
||||
info: item,
|
||||
labelCfg: {
|
||||
position: 'bottom',
|
||||
offset: 8,
|
||||
style: {
|
||||
fill: '#fff',
|
||||
fontSize: 14,
|
||||
},
|
||||
},
|
||||
size: 20,
|
||||
icon: {
|
||||
x: -30,
|
||||
y: -30,
|
||||
// 可更换为其他图片地址
|
||||
img: '/svg/service_db.svg',
|
||||
width: 60,
|
||||
height: 60,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
nodes.push({
|
||||
id: item.neName,
|
||||
label: item.neName,
|
||||
info: item,
|
||||
size: 20,
|
||||
icon: {
|
||||
x: -24,
|
||||
y: -24,
|
||||
img: '/svg/service.svg',
|
||||
width: 48,
|
||||
height: 48,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (item.neType !== 'OMC') {
|
||||
const edgeIndex = edges.findIndex(v => v.source === item.neName);
|
||||
if (edgeIndex === -1) {
|
||||
edges.push({
|
||||
source: item.neName,
|
||||
target: rootNode,
|
||||
label: `${item.neType}-${rootNode}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
graphG6Data.nodes = nodes;
|
||||
graphG6Data.edges = edges;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.then(hasNeList => {
|
||||
if (!hasNeList) return;
|
||||
if (refresh) {
|
||||
graphG6.value.destroy();
|
||||
}
|
||||
fnGetState();
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元列表
|
||||
// fnGetList();
|
||||
fnRanderGraph();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px' }"
|
||||
size="small"
|
||||
>
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<div class="button-container" style="margin-bottom: -12px">
|
||||
<a-select size="small" value="view">
|
||||
<a-select-option value="view" key="view"> 查看 </a-select-option>
|
||||
<a-select-option value="edit" key="edit"> 编辑 </a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
|
||||
<a-button type="primary" danger ghost>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
{{ t('views.neUser.auth.batchDelText') }}
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
:title="t('views.neUser.sub.loadDataConfirm')"
|
||||
:ok-text="t('common.ok')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
>
|
||||
<a-button type="dashed" danger>
|
||||
<template #icon>
|
||||
<SyncOutlined />
|
||||
</template>
|
||||
{{ t('views.neUser.sub.loadData') }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-button type="dashed">
|
||||
<template #icon>
|
||||
<ImportOutlined />
|
||||
</template>
|
||||
{{ t('views.neUser.sub.import') }}
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm
|
||||
:title="t('views.neUser.sub.exportConfirm')"
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon>
|
||||
<ExportOutlined />
|
||||
</template>
|
||||
{{ t('views.neUser.sub.export') }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<div ref="graphG6Dom" class="chart"></div>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: calc(100vh - 300px);
|
||||
background-color: rgb(43, 47, 51);
|
||||
}
|
||||
</style>
|
||||
1755
src/views/monitor/topology/graph.ts
Normal file
1755
src/views/monitor/topology/graph.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,68 +1,334 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import ChartGraphG6 from '@/components/ChartGraphG6/index.vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listNe, stateNe } from '@/api/ne/ne';
|
||||
import message from 'ant-design-vue/lib/message';
|
||||
import { randerGroph, switchLayout } from './graph';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**查询全部网元数据列表 */
|
||||
function fnGetList() {}
|
||||
/**图DOM节点实例对象 */
|
||||
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
onMounted(() => {});
|
||||
/**图实例对象 */
|
||||
const graphG6 = ref<any>(null);
|
||||
|
||||
/**图数据 */
|
||||
const graphG6Data = reactive<Record<string, any>>({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
});
|
||||
|
||||
/**查询全部网元数据列表 */
|
||||
function fnRanderData() {
|
||||
if (!graphG6Dom.value) return;
|
||||
graphG6.value = randerGroph(graphG6Dom.value, graphG6Data);
|
||||
}
|
||||
|
||||
/**查询网元状态 */
|
||||
async function fnGetState() {
|
||||
for (const node of graphG6Data.nodes) {
|
||||
const ne = node.info;
|
||||
if (ne.neType === 'OMC') continue;
|
||||
const result = await stateNe(ne.neType, ne.neId);
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
ne.serverState = result.data;
|
||||
ne.serverState.refreshTime = parseDateToStr(
|
||||
ne.serverState.refreshTime,
|
||||
'HH:mm:ss'
|
||||
);
|
||||
}
|
||||
// 修改网元状态颜色
|
||||
const neShape = graphG6.value.findById(ne.neName);
|
||||
graphG6.value.setItemState(
|
||||
neShape,
|
||||
'neState',
|
||||
result.code === RESULT_CODE_SUCCESS
|
||||
);
|
||||
}
|
||||
setTimeout(() => fnGetState(), 30_000);
|
||||
}
|
||||
|
||||
/**查询全部网元数据列表 */
|
||||
function fnGetList(refresh: boolean = false) {
|
||||
listNe({
|
||||
bandStatus: false,
|
||||
})
|
||||
.then(res => {
|
||||
if (
|
||||
res.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(res.data) &&
|
||||
res.data.length > 0
|
||||
) {
|
||||
let rootNode = 'OMC';
|
||||
const nodes = [];
|
||||
const edges = [];
|
||||
for (const item of res.data) {
|
||||
item.serverState = {};
|
||||
const nodeIndex = nodes.findIndex(v => v.id === item.neName);
|
||||
if (nodeIndex === -1) {
|
||||
// 根网管
|
||||
if (item.neType === 'OMC') {
|
||||
rootNode = item.neName;
|
||||
item.serverState = {
|
||||
neId: item.neId,
|
||||
neName: item.neName,
|
||||
neType: item.neType,
|
||||
expire: '2024-03-31',
|
||||
refreshTime: '10:31:47',
|
||||
sn: '13770707',
|
||||
version: '2.2312.8',
|
||||
};
|
||||
nodes.push({
|
||||
id: item.neName,
|
||||
label: item.neName,
|
||||
info: item,
|
||||
labelCfg: {
|
||||
position: 'bottom',
|
||||
offset: 8,
|
||||
style: {
|
||||
fill: '#fff',
|
||||
fontSize: 14,
|
||||
},
|
||||
},
|
||||
size: 60,
|
||||
icon: {
|
||||
x: -30,
|
||||
y: -30,
|
||||
// 可更换为其他图片地址
|
||||
img: '/svg/service_db.svg',
|
||||
width: 60,
|
||||
height: 60,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
nodes.push({
|
||||
id: item.neName,
|
||||
label: item.neName,
|
||||
info: item,
|
||||
size: 48,
|
||||
icon: {
|
||||
x: -24,
|
||||
y: -24,
|
||||
img: '/svg/service.svg',
|
||||
width: 48,
|
||||
height: 48,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (item.neType !== 'OMC') {
|
||||
const edgeIndex = edges.findIndex(v => v.source === item.neName);
|
||||
if (edgeIndex === -1) {
|
||||
edges.push({
|
||||
source: item.neName,
|
||||
target: rootNode,
|
||||
label: `${item.neType}-${rootNode}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
graphG6Data.nodes = nodes;
|
||||
graphG6Data.edges = edges;
|
||||
console.log(graphG6Data);
|
||||
return true;
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.then(hasNeList => {
|
||||
if (!hasNeList) return;
|
||||
if (refresh) {
|
||||
// graphG6.value.get('canvas').set('localRefresh', true);
|
||||
graphG6.value.destroy();
|
||||
// graphG6.value.clear();
|
||||
}
|
||||
fnRanderData();
|
||||
fnGetState();
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元列表
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
:body-style="{ marginBottom: '24px' }"
|
||||
size="small"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="10" :md="10" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.monitor.monitor.filter')"
|
||||
name="neTypeSelect"
|
||||
>
|
||||
ssdf
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList()">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title v-if="false">
|
||||
<div class="button-container" style="margin-bottom: -12px">
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-card :bordered="false" :body-style="{ marginBottom: '24px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>asdf</template>
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra> adf </template>
|
||||
<a-button type="primary" danger ghost>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
{{ t('views.neUser.auth.batchDelText') }}
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
:title="t('views.neUser.sub.loadDataConfirm')"
|
||||
:ok-text="t('common.ok')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
>
|
||||
<a-button type="dashed" danger>
|
||||
<template #icon>
|
||||
<SyncOutlined />
|
||||
</template>
|
||||
{{ t('views.neUser.sub.loadData') }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
||||
<div class="chart">
|
||||
<ChartGraphG6 :option="{}"></ChartGraphG6>
|
||||
<a-button type="dashed">
|
||||
<template #icon>
|
||||
<ImportOutlined />
|
||||
</template>
|
||||
{{ t('views.neUser.sub.import') }}
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm
|
||||
:title="t('views.neUser.sub.exportConfirm')"
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon>
|
||||
<ExportOutlined />
|
||||
</template>
|
||||
{{ t('views.neUser.sub.export') }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-button type="default" @click.prevent="switchLayout()">
|
||||
<template #icon><RetweetOutlined /></template>
|
||||
Switch Layout
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<div ref="graphG6Dom" class="chart"></div>
|
||||
|
||||
<template v-if="false">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 200px;
|
||||
background: #e6f7ff;
|
||||
"
|
||||
>
|
||||
<div><span>状态:</span><span>正常</span></div>
|
||||
<div><span>刷新时间:</span><span>18:37:22</span></div>
|
||||
<div><span>ID:</span><span>neID</span></div>
|
||||
<div><span>名称:</span><span>neName</span></div>
|
||||
<div><span>版本:</span><span>2.2312.8</span></div>
|
||||
<div><span>SN:</span><span>13770707</span></div>
|
||||
<div><span>有效期:</span><span>2024-03-31</span></div>
|
||||
</div>
|
||||
===
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 140px;
|
||||
background: #e6f7ff;
|
||||
"
|
||||
>
|
||||
<div id="show" style="cursor: pointer; margin-bottom: 2px">
|
||||
1. 显示所有隐藏项
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div id="collapseAll" style="cursor: pointer; margin-bottom: 2px">
|
||||
2. 折叠所有集群
|
||||
</div>
|
||||
</div>
|
||||
===
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100px;
|
||||
background: #e6f7ff;
|
||||
"
|
||||
>
|
||||
<div id="expand" style="cursor: pointer; margin-bottom: 2px">
|
||||
1. 展开集群
|
||||
</div>
|
||||
<div id="hide" style="cursor: pointer; margin-bottom: 2px">
|
||||
2. 隐藏节点
|
||||
</div>
|
||||
</div>
|
||||
===
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 160px;
|
||||
background: #e6f7ff;
|
||||
"
|
||||
>
|
||||
<div id="collapse" style="cursor: pointer; margin-bottom: 2px">
|
||||
1. 折叠集群
|
||||
</div>
|
||||
<div id="hide" style="cursor: pointer; margin-bottom: 2px">
|
||||
2. 隐藏节点
|
||||
</div>
|
||||
<div id="restart" style="cursor: pointer; margin-bottom: 2px">
|
||||
3. 重启
|
||||
</div>
|
||||
</div>
|
||||
===
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100px;
|
||||
background: #e6f7ff;
|
||||
"
|
||||
>
|
||||
<div id="hide" style="cursor: pointer; margin-bottom: 2px">
|
||||
1. 隐藏边
|
||||
</div>
|
||||
</div>
|
||||
<!-- 组件 -->
|
||||
<div class="charts">
|
||||
<ChartGraphG6></ChartGraphG6>
|
||||
</div>
|
||||
</template>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: calc(100vh - 300px);
|
||||
background-color: rgb(43, 47, 51);
|
||||
}
|
||||
.charts {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
background-color: rgb(238, 237, 237);
|
||||
margin-top: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -469,7 +469,9 @@ function fnModalUploadImportUpload(file: File) {
|
||||
return res;
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data?.detail) {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data?.filePath) {
|
||||
uploadImportState.msg = t('views.neUser.pcf.uploadFileOk');
|
||||
} else if (res.code === RESULT_CODE_SUCCESS && res.data?.detail) {
|
||||
uploadImportState.msg = res.data?.detail;
|
||||
} else {
|
||||
uploadImportState.msg = t('views.neUser.pcf.uploadFileErr');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getLocalColor, changePrimaryColor } from '@/hooks/useTheme';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Form, message, Modal } from 'ant-design-vue/lib';
|
||||
@@ -32,6 +33,18 @@ const dictId = route.params && (route.params.dictId as string);
|
||||
|
||||
const zh = currentLocale.value === 'zh_CN';
|
||||
|
||||
let color = ref<string>(getLocalColor());
|
||||
/**改变主题色 */
|
||||
function fnColorChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.nodeName === 'INPUT') {
|
||||
changePrimaryColor(target.value ?? '#1890ff');
|
||||
} else {
|
||||
changePrimaryColor();
|
||||
}
|
||||
color.value = getLocalColor();
|
||||
}
|
||||
|
||||
/**标签类型数据固定项 */
|
||||
const tagTypeOptions = ref([
|
||||
{ value: '', label: zh ? '普通文本' : 'Plain text' },
|
||||
@@ -289,7 +302,8 @@ function fnModalVisibleByVive(row: Record<string, string>) {
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param dictCode 数据编号id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(dictCode?: string | number) {
|
||||
function fnModalVisibleByEdit(dictCode?: string | number, record?: any) {
|
||||
console.log(record);
|
||||
if (!dictCode) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.from.dictType = queryParams.dictType;
|
||||
@@ -686,7 +700,7 @@ onMounted(() => {
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.dictCode)"
|
||||
@click.prevent="fnModalVisibleByEdit(record.dictCode, record)"
|
||||
v-perms:has="['system:dict:edit']"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
@@ -903,8 +917,27 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<!-- 首页状态取色器 -->
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-col
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:xs="24"
|
||||
v-if="modalState.from.dictType === 'index_status'"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('views.system.dict.colorSelect')"
|
||||
name="tagClass"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.tagClass"
|
||||
type="color"
|
||||
allow-clear
|
||||
:placeholder="t('common.ipnutPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24" v-else>
|
||||
<a-form-item
|
||||
:label="t('views.system.dictData.tagType')"
|
||||
name="tagType"
|
||||
@@ -931,16 +964,21 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
:label="t('views.system.dictData.tagClass')"
|
||||
name="tagClass"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.tagClass"
|
||||
allow-clear
|
||||
:placeholder="t('common.ipnutPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<!-- 非首页状态取色器 -->
|
||||
<a-row :gutter="16" v-if="modalState.from.dictType !== 'index_status'">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.dictData.tagClass')"
|
||||
name="tagClass"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.tagClass"
|
||||
allow-clear
|
||||
:placeholder="t('common.ipnutPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item :label="t('views.system.dictData.remark')" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.remark"
|
||||
|
||||
Reference in New Issue
Block a user