Compare commits
546 Commits
main
...
multi-tena
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c315a1548 | ||
|
|
4dfe872c27 | ||
|
|
d2e6e52fd0 | ||
|
|
df7dacdc39 | ||
|
|
0b54c730d7 | ||
|
|
70ddcb56ba | ||
|
|
719a08d993 | ||
|
|
81f287e9ff | ||
|
|
cf724e8693 | ||
|
|
869e2b23fb | ||
|
|
5d33611ef1 | ||
|
|
95c3001f35 | ||
|
|
6597b20ff9 | ||
|
|
25538b2cc4 | ||
|
|
14590545f5 | ||
|
|
f7a9258473 | ||
|
|
49b78d9933 | ||
|
|
b7fa412733 | ||
|
|
14864cbf9b | ||
|
|
c40942694e | ||
|
|
c1df7e2700 | ||
|
|
f6c3d730af | ||
|
|
22cab89749 | ||
|
|
c85b588719 | ||
|
|
2ac730dfe2 | ||
|
|
669bdf5b7d | ||
|
|
744867243f | ||
|
|
79830b752a | ||
|
|
4bb5a99b37 | ||
|
|
dfa8539b9b | ||
|
|
226a104417 | ||
|
|
539ecbc039 | ||
|
|
61adb5c8ae | ||
|
|
be2595f49d | ||
|
|
63d02ab19d | ||
|
|
12d2c50248 | ||
|
|
2bdad2db47 | ||
|
|
33055d74ba | ||
|
|
702eeb9caa | ||
|
|
8d01eb48e6 | ||
|
|
b98f2394b8 | ||
|
|
80f72549a2 | ||
|
|
bd729b7437 | ||
|
|
3aaa90746b | ||
|
|
b55b4ad49d | ||
|
|
ae171a2a82 | ||
|
|
2828deaece | ||
|
|
f8376c803f | ||
|
|
fe13f56aa1 | ||
|
|
1e27a414db | ||
|
|
6edd445e3d | ||
|
|
4a5008f1b5 | ||
|
|
749e880aa7 | ||
|
|
f0789acf42 | ||
|
|
5acbec4c45 | ||
|
|
4f6ad8edc4 | ||
|
|
4048d2b736 | ||
|
|
f919d31232 | ||
|
|
5e2184945c | ||
|
|
c3e046d9d9 | ||
|
|
33dc7cb7dc | ||
|
|
5cbe885d7e | ||
|
|
6b45194c65 | ||
|
|
1d3ab0f6af | ||
|
|
2d29bd4226 | ||
|
|
6bbd5d723e | ||
|
|
2a45acad33 | ||
|
|
c7dd92d3b3 | ||
|
|
905d8c3bb2 | ||
|
|
bb6cae1433 | ||
|
|
d85889b376 | ||
|
|
d1a20296d9 | ||
|
|
2f8c80572b | ||
|
|
7ef9d23e94 | ||
|
|
0a130781d1 | ||
|
|
6ce5a24614 | ||
|
|
627f847d5e | ||
|
|
89c2a61737 | ||
|
|
4403b06416 | ||
|
|
55f3e1c673 | ||
|
|
007b640209 | ||
|
|
59af80fe7f | ||
|
|
ed3b5bb5e8 | ||
|
|
58f78432f0 | ||
|
|
a2a5e52576 | ||
|
|
b50c962201 | ||
|
|
2209a7206f | ||
|
|
828c640307 | ||
|
|
4293c611be | ||
|
|
f155a0dc8e | ||
|
|
dbf6dfbe26 | ||
|
|
826e63a861 | ||
|
|
357292d445 | ||
|
|
87e5261942 | ||
|
|
5d60156456 | ||
|
|
ccd5edb76f | ||
|
|
99f7ddb760 | ||
|
|
40137f182c | ||
|
|
4666a0e9b3 | ||
|
|
17650d134a | ||
|
|
f40107a50f | ||
|
|
656dad2f3e | ||
|
|
c2dac86db0 | ||
|
|
858e3e6559 | ||
|
|
86cb6e0ffe | ||
|
|
3c485dea48 | ||
|
|
9b79fe8271 | ||
|
|
4f854f77b7 | ||
|
|
2191db06d4 | ||
|
|
274353d016 | ||
|
|
f0e63da1a2 | ||
|
|
4b34db817e | ||
|
|
a4aeb06d13 | ||
|
|
3389fbad53 | ||
|
|
3aa6822a72 | ||
|
|
bdfad20dd5 | ||
|
|
d8c9bbc775 | ||
|
|
5e014309cc | ||
|
|
390b9cf73e | ||
|
|
760beb0087 | ||
|
|
5b2f883934 | ||
|
|
98521a3a89 | ||
|
|
51c9f7fca3 | ||
|
|
d593abf718 | ||
|
|
fdd390c389 | ||
|
|
cfba4ecdb2 | ||
|
|
e45c17c4c5 | ||
|
|
f58a0151fe | ||
|
|
ff9aca14b8 | ||
|
|
de74af4fec | ||
|
|
393a90a0d0 | ||
|
|
3f06ab62b1 | ||
|
|
a148dc82a9 | ||
|
|
a039fd7a3a | ||
|
|
00485a5c7f | ||
|
|
50cf5e70fb | ||
|
|
7e5864b97b | ||
|
|
194ad023bd | ||
|
|
f16b07a2d6 | ||
|
|
7bf8bad9e4 | ||
|
|
ab1725e673 | ||
|
|
77631de0ff | ||
|
|
f6bdecd877 | ||
|
|
e05084afdc | ||
|
|
4c49640763 | ||
|
|
4433990878 | ||
|
|
6d092cb3e7 | ||
|
|
66957520f7 | ||
|
|
3596d7815b | ||
|
|
b72724649e | ||
|
|
aed81672a2 | ||
|
|
a1c530640c | ||
|
|
bcd7599676 | ||
|
|
5edb5932b5 | ||
|
|
7bd524976a | ||
|
|
7b7907616f | ||
|
|
e763418e38 | ||
|
|
36e4f2d5f1 | ||
|
|
38f0b9b560 | ||
|
|
246bb9a7e0 | ||
|
|
678bfce993 | ||
|
|
f8bc9fc622 | ||
|
|
d37fd4deae | ||
|
|
394cef66c2 | ||
|
|
7432f1e237 | ||
|
|
8eea52c4e5 | ||
|
|
4c777245fc | ||
|
|
42464bc5bd | ||
|
|
a436efab67 | ||
|
|
5ae056cb03 | ||
|
|
e37a7fb42b | ||
|
|
e3306bab3a | ||
|
|
2cdbcd6de8 | ||
|
|
fae66a2b79 | ||
|
|
f45ad79015 | ||
|
|
f9dd0964d2 | ||
|
|
30ca7ae14d | ||
|
|
82cb796a50 | ||
|
|
a98aeda96f | ||
|
|
1a10a5a9d1 | ||
|
|
edd3865094 | ||
|
|
8990fa8dee | ||
|
|
2b624b3c04 | ||
|
|
93b600affb | ||
|
|
bea6f032ac | ||
|
|
20b8531dc8 | ||
|
|
05c1638984 | ||
|
|
218dfef16b | ||
|
|
bcdb64777a | ||
|
|
dd9df3a08c | ||
|
|
fa12a5d3a8 | ||
|
|
0196b322f0 | ||
|
|
87cfc2ed79 | ||
|
|
b9bcd4c265 | ||
|
|
a54de8a9bb | ||
|
|
c5d7026fc5 | ||
|
|
93841a02ea | ||
|
|
42bd112649 | ||
|
|
d0add206ba | ||
|
|
b9659837c2 | ||
|
|
04f81af6c3 | ||
|
|
8bd1ea4faa | ||
|
|
fa00b6bfa8 | ||
|
|
6a8e08a81a | ||
|
|
25a33678f2 | ||
|
|
1e8081e83e | ||
|
|
88aea40885 | ||
|
|
a341800efc | ||
|
|
d75a2a1651 | ||
|
|
38c0e3d08d | ||
|
|
951d48f470 | ||
|
|
5f81d73c95 | ||
|
|
9ee5fef458 | ||
|
|
1723d314c5 | ||
|
|
f14316256c | ||
|
|
a945e4dc5f | ||
|
|
8575d5d711 | ||
|
|
a9cb35ba4f | ||
|
|
7e21e25cf3 | ||
|
|
ce9b8815df | ||
|
|
f2018eca30 | ||
|
|
af513ba157 | ||
|
|
1aa1b471a5 | ||
|
|
f42b921a50 | ||
|
|
db26e9a054 | ||
|
|
1dd2ae6a4c | ||
|
|
2d672250fd | ||
|
|
59f959fcc2 | ||
|
|
d35179c8ae | ||
|
|
9e0f6d3946 | ||
|
|
b627b6aa83 | ||
|
|
e98c6cda51 | ||
|
|
481b099655 | ||
|
|
d9534b635e | ||
|
|
d7173ff737 | ||
|
|
571bc840ad | ||
|
|
8664e72189 | ||
|
|
f1aec581c7 | ||
|
|
2492c7562a | ||
|
|
83690f95d7 | ||
|
|
02b071f4a0 | ||
|
|
beca94906c | ||
|
|
d6c10050b4 | ||
|
|
5fc9aa7c2f | ||
|
|
9214474325 | ||
|
|
55008a112c | ||
|
|
600ee94ed9 | ||
|
|
55f5734d7b | ||
|
|
a4fa53556b | ||
|
|
ca8f16fb0c | ||
|
|
6d7cde6058 | ||
|
|
2b8e222f23 | ||
|
|
00e97feac7 | ||
|
|
10dd1270fc | ||
|
|
94181fa0da | ||
|
|
9203113c09 | ||
|
|
e326bd1ef8 | ||
|
|
69bae32c80 | ||
|
|
00c20df133 | ||
|
|
bcb94ae9e7 | ||
|
|
64072af7ad | ||
|
|
b9f540f1ee | ||
|
|
cb7e3038fa | ||
|
|
460a4e1b3b | ||
|
|
14495fce2b | ||
|
|
e06b7d4fd3 | ||
|
|
72bd9004cc | ||
|
|
84b0ab50f4 | ||
|
|
aaaf5679ae | ||
|
|
b1bfb7a915 | ||
|
|
58919ad4d4 | ||
|
|
49f0037145 | ||
|
|
d0b5ee7e75 | ||
|
|
c37552661c | ||
|
|
d6a7d8348e | ||
|
|
586003c9b9 | ||
|
|
163ce9b64c | ||
|
|
1d5268d348 | ||
|
|
ef098b5d02 | ||
|
|
1eabb4445e | ||
|
|
b10aed3d14 | ||
|
|
6590a0c811 | ||
|
|
8eed156143 | ||
|
|
0ab9a98ba9 | ||
|
|
72dadaf3f2 | ||
|
|
f0079d67fd | ||
|
|
bc4560b8d9 | ||
|
|
466e56b90f | ||
|
|
aa0397ad1a | ||
|
|
c91f3e7927 | ||
|
|
69cf72af22 | ||
|
|
f3491a1a31 | ||
|
|
5caa285ede | ||
|
|
e14c1ce771 | ||
|
|
8d22ab850a | ||
|
|
82af22b997 | ||
|
|
37a1f748e7 | ||
|
|
a24122ecd7 | ||
|
|
ccbea1fc51 | ||
|
|
ac7079b91a | ||
|
|
8af7031c92 | ||
|
|
ad31a52663 | ||
|
|
ed9bff5d61 | ||
|
|
3ca8154279 | ||
|
|
73d7d64225 | ||
|
|
3d4d785f33 | ||
|
|
1d02b17c20 | ||
|
|
5f75197a42 | ||
|
|
2056d7c51b | ||
|
|
91866293bc | ||
|
|
2714537140 | ||
|
|
c037660b01 | ||
|
|
fb9db32da7 | ||
|
|
874720b68d | ||
|
|
597883fa08 | ||
|
|
73a1a4c51b | ||
|
|
7962c7e7a8 | ||
|
|
6f5759b5ba | ||
|
|
a7df09d56f | ||
|
|
bc8207d29d | ||
|
|
6bfd0f4792 | ||
|
|
93e00ed436 | ||
|
|
33bdbd6d08 | ||
|
|
efa30f4ee3 | ||
|
|
401a7d65a0 | ||
|
|
91242e74b0 | ||
|
|
f8f2ba1f92 | ||
|
|
e3792eff57 | ||
|
|
081c79b85b | ||
|
|
c1fdb68d96 | ||
|
|
8f1de1c396 | ||
|
|
9b84ff9452 | ||
|
|
e4222e7b03 | ||
|
|
c86c498ab9 | ||
|
|
2e91ab319f | ||
|
|
d537f56b9d | ||
|
|
7c4710e2e7 | ||
|
|
ea368f9162 | ||
|
|
7cab3f6556 | ||
|
|
a53eaaf533 | ||
|
|
a0cc882a4b | ||
|
|
d6c0f89de5 | ||
|
|
f3ae3da2a5 | ||
|
|
d5c91e733b | ||
|
|
ea6dfa7558 | ||
|
|
6129002b38 | ||
|
|
e11cb11ec9 | ||
|
|
a95a96dfc0 | ||
|
|
5be94b499d | ||
|
|
f66454256b | ||
|
|
1c07167f1a | ||
|
|
d5c42f761e | ||
|
|
831e7ee987 | ||
|
|
f15e52573b | ||
|
|
fcf32800fe | ||
|
|
a96c587559 | ||
|
|
0544495d70 | ||
|
|
5d7334a4de | ||
|
|
802a91a96d | ||
|
|
44cd1d354a | ||
|
|
612592ec2a | ||
|
|
2b9fa490d4 | ||
|
|
0ab04ab819 | ||
|
|
580b931610 | ||
|
|
dd7df775e2 | ||
|
|
18c2a2e4dc | ||
|
|
fbdd04b4dd | ||
|
|
b508d4393c | ||
|
|
d257151718 | ||
|
|
7b60c30548 | ||
|
|
cf5c8906c3 | ||
|
|
249c9a5a6e | ||
|
|
a78b830a05 | ||
|
|
879cf783b6 | ||
|
|
895a55f367 | ||
|
|
536c77ff08 | ||
|
|
37e70e7af8 | ||
|
|
87e044276c | ||
|
|
cd3e87c1c9 | ||
|
|
009c4cf590 | ||
|
|
3c45427bff | ||
|
|
56cbe9915c | ||
|
|
cf3103db46 | ||
|
|
879499b595 | ||
| e199498a8e | |||
|
|
82ecee9941 | ||
| 4ee1d87d04 | |||
|
|
39268e2162 | ||
|
|
d6b755c234 | ||
|
|
a889f97bd6 | ||
|
|
a77b968a17 | ||
|
|
da3d712e97 | ||
|
|
bb662aefe3 | ||
|
|
7bbffccd21 | ||
|
|
0de115ad9e | ||
|
|
ec67414cb6 | ||
|
|
c30e8b4891 | ||
|
|
7d5d82e7fc | ||
| 60083183c5 | |||
| 8fbd4f3952 | |||
|
|
2048b6f2db | ||
|
|
ea6fca405b | ||
|
|
9bc5eba0d6 | ||
|
|
b8c56c8868 | ||
| 9785837980 | |||
|
|
977a4e11ee | ||
|
|
21e42709a7 | ||
|
|
f8a43042db | ||
|
|
a9ee7aa925 | ||
|
|
811aedaaf4 | ||
| 172b4a4856 | |||
|
|
447ee401cb | ||
|
|
5f8e9954fe | ||
|
|
f8a234822d | ||
| 09245fdfda | |||
|
|
3dafe8d1f6 | ||
|
|
c7606df740 | ||
|
|
e5c40d11d3 | ||
|
|
c9d8bb87de | ||
|
|
f7c2adf58c | ||
|
|
363288a141 | ||
|
|
b1dbacffcc | ||
|
|
3089f8911e | ||
|
|
83ec17343a | ||
|
|
ef9db9ddf6 | ||
|
|
1e8da20c44 | ||
|
|
d9b2b6b567 | ||
|
|
636c7a5939 | ||
|
|
15f6cf0b4c | ||
|
|
c024c304d5 | ||
|
|
a6e100b5c2 | ||
|
|
48bac47c6b | ||
|
|
24a147afaf | ||
|
|
5b17c9e497 | ||
|
|
79f143c22c | ||
|
|
a8764fe627 | ||
|
|
6a0ed31cdc | ||
|
|
8a2e21a794 | ||
|
|
51226fbfb4 | ||
|
|
af5ac7d29a | ||
|
|
0b98a1f697 | ||
|
|
f66cd875fb | ||
|
|
795a6fabee | ||
|
|
1f7becef0a | ||
|
|
aa4f11bf44 | ||
|
|
0b6a08a977 | ||
|
|
5089e769da | ||
|
|
c36f402528 | ||
|
|
1f6a13951f | ||
| 4b69b44df5 | |||
| 5643c625ef | |||
| 1607c97b82 | |||
| d0ac6d4e2e | |||
| 0ad9d546cd | |||
| f8d54e35c9 | |||
|
|
be2a077bba | ||
|
|
cb5dfebb59 | ||
|
|
71d23cbc32 | ||
|
|
93315776da | ||
|
|
0d5cbe6459 | ||
|
|
9a88364d8b | ||
|
|
c0ec4893c8 | ||
|
|
24cd0ff101 | ||
|
|
7e92659217 | ||
|
|
e8fefab74e | ||
|
|
78c86be8a0 | ||
|
|
8f8c9f8395 | ||
|
|
7d5635560d | ||
|
|
e83bf43bc8 | ||
|
|
f89ba87fef | ||
|
|
ca8a4128fb | ||
|
|
21621c2056 | ||
|
|
22797a8ae8 | ||
|
|
374b6feef3 | ||
|
|
c1902f978a | ||
|
|
b431ae70a5 | ||
|
|
8e57bcfd25 | ||
|
|
9e5a000383 | ||
|
|
f4771892aa | ||
|
|
842f09cc90 | ||
|
|
2cb92eaf69 | ||
|
|
02eb368a45 | ||
|
|
4c7b5c8b55 | ||
|
|
7aebab3734 | ||
|
|
d26fb9af85 | ||
|
|
28213cde43 | ||
|
|
c21af696fe | ||
|
|
ebd8821e64 | ||
|
|
361fdf6959 | ||
|
|
60dc02010d | ||
|
|
7c420c7c95 | ||
| a78e7049cf | |||
|
|
ef3796c34f | ||
|
|
c38855acf8 | ||
|
|
a2672d300f | ||
|
|
bfaf0e6aaf | ||
|
|
7fe0f5d84d | ||
|
|
1eeddb20c5 | ||
|
|
52b61ec34a | ||
|
|
faefef7d2f | ||
|
|
938679cbda | ||
|
|
83aefc5dde | ||
|
|
0157ec50d3 | ||
|
|
2f7c947708 | ||
|
|
4c16c39243 | ||
|
|
693ed533f9 | ||
|
|
584b25d9fc | ||
|
|
1fa59d163e | ||
|
|
c94f9c5943 | ||
|
|
2480ae1c0b | ||
|
|
440dc81182 | ||
|
|
0b64302b54 | ||
|
|
0e4e8e09e6 | ||
|
|
0b05f40e65 | ||
|
|
ff0b3a40cd | ||
|
|
fe541886d5 | ||
|
|
6406ed0e57 | ||
|
|
1aa2f2382a | ||
|
|
197a962c0d | ||
|
|
ac923d5943 | ||
|
|
92fe93b4dc | ||
|
|
a26301af1e | ||
|
|
6aa07dc756 | ||
|
|
f5faa054b3 | ||
|
|
5ada366f17 | ||
|
|
80a41dd559 | ||
|
|
7031d4cdd4 | ||
|
|
f72b79c628 | ||
|
|
490db476dd | ||
|
|
1bae6e55e0 | ||
|
|
d2503348fd | ||
|
|
03710d6811 | ||
|
|
322dccfbc1 | ||
|
|
0d0603058c | ||
|
|
acef786c56 | ||
|
|
4e726913f1 | ||
|
|
f699f6f3ba | ||
|
|
e35356a8c6 | ||
|
|
9152f14430 | ||
|
|
faa3fa546b | ||
|
|
5c31093c28 | ||
|
|
eee652bd1f | ||
|
|
b7b66ad28d | ||
|
|
29e092421c | ||
|
|
0022e259ca | ||
|
|
0a711ee3ce |
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.250509"
|
||||
VITE_APP_VERSION = "local-dev"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.250509"
|
||||
VITE_APP_VERSION = "local-prod"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
38
CHANGELOG.md
@@ -1,5 +1,43 @@
|
||||
# 版本发布日志
|
||||
|
||||
## 2.2510.4-20251024
|
||||
|
||||
- 修复 AMF配置WhiteList Content导入index0无效问题
|
||||
- 新增 更新背景图片固定BA的
|
||||
- 新增 基站状态补充randId列
|
||||
- 新增 Roaming CDR自定义导出功能
|
||||
- 优化 基站名称和位置信息改为非必填
|
||||
- 修复 网元信息更新响应修复
|
||||
- 新增 UDM鉴权cnFlag添加5G/4G接入选择
|
||||
- 新增 更新getAllNeConfig函数,支持传入neId参数
|
||||
- 修复 更新根网管节点处理逻辑,支持无OMC情况
|
||||
- 优化 移除ID列显示
|
||||
- 优化 操作日志信息json结构格式美化
|
||||
- 优化 备份网元日志文件数据查看
|
||||
- 优化 悬浮标签修改
|
||||
|
||||
## 2.2510.2-20251011
|
||||
|
||||
- 新增 UDM-auth数据导出按钮权限定义
|
||||
- 新增 SMSC-CDR添加结果原因说明
|
||||
- 优化 移除CDR/UE/网元版本/授权列表显示ID列
|
||||
- 修复 MML回车undefined问题,关闭搜索匹配
|
||||
|
||||
## 2.2408.1-20250815
|
||||
|
||||
- 修复 给config.js加随机数避免缓存
|
||||
- 优化 UDM鉴权用户显示创建时间
|
||||
- 修复 系统用户登录主页重复刷新问题
|
||||
- 新增 kpi自定义仪表盘需求更改
|
||||
- 修复 租户主页暗色模式显示不一致
|
||||
- 修复 租户和系统用户的主页显示差异
|
||||
- 修复 图片上传取值fileName失败
|
||||
- 优化 第三方用户不可删除和修改密码
|
||||
- 修复 恢复显示快速PLMN修改窗口
|
||||
- 新增 第三方登录认证功能和管理页
|
||||
- 修复 回退看板,修复切换upf的流量统计显示
|
||||
|
||||
|
||||
## 2.2404.1-20240402
|
||||
|
||||
- 新增 网元安装流程相关页面与操作相关接口联调
|
||||
|
||||
28
README.md
@@ -46,5 +46,31 @@ export NODE_OPTIONS=--max-old-space-size=50000
|
||||
|
||||
```text
|
||||
https://192.168.5.23/
|
||||
admin / admin
|
||||
admin
|
||||
admin
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 多租户涉及页面
|
||||
ems_frontend_vue3\src\views\dashboard\smfCDR\index.vue
|
||||
ems_frontend_vue3\src\views\dashboard\imsCDR\index.vue
|
||||
ems_frontend_vue3\src\views\dashboard\amfUE\index.vue
|
||||
ems_frontend_vue3\src\views\dashboard\mmeUE\index.vue
|
||||
ems_frontend_vue3\src\views\system\user\index.vue
|
||||
ems_frontend_vue3\src\views\system\tenant\index.vue
|
||||
ems_frontend_vue3\src\views\system\log\operate\index.vue
|
||||
ems_frontend_vue3\src\views\neUser\sub\index.vue
|
||||
ems_frontend_vue3\src\views\neUser\ims\index.vue
|
||||
ems_frontend_vue3\src\views\neUser\ue\index.vue
|
||||
ems_frontend_vue3\src\views\neUser\base5G\index.vue
|
||||
|
||||
ems_frontend_vue3\src\views\tenant\amfUE\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\base5G\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\ims\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\imsCDR\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\mmeUE\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\operate\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\smfCDR\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\sub\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\ue\index.vue
|
||||
12
index.html
@@ -2,14 +2,18 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="google" content="notranslate">
|
||||
<meta name="google" content="notranslate" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>loading...</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="preload" href="/loading.js" as="script">
|
||||
<link rel="preload" href="/loading.js" as="script"/>
|
||||
<script async src="/loading.js"></script>
|
||||
<link rel="preload" href="/config.js" as="script">
|
||||
<script async src="/config.js"></script>
|
||||
<script>
|
||||
var script = document.createElement('script');
|
||||
script.src = '/config.js?nocache=' + new Date().getTime();
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -29,6 +29,7 @@
|
||||
"crypto-js": "4.2.0",
|
||||
"dayjs": "1.11.13",
|
||||
"echarts": "5.6.0",
|
||||
"echarts-liquidfill": "^3.1.0",
|
||||
"file-saver": "2.0.5",
|
||||
"grid-layout-plus": "1.0.6",
|
||||
"intl-tel-input": "25.2.0",
|
||||
|
||||
BIN
public/background/dark.jpg
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
public/background/light.jpg
Normal file
|
After Width: | Height: | Size: 216 KiB |
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* =============== Configuration File Description ===============
|
||||
*
|
||||
*
|
||||
* - Nginx Deployment
|
||||
* Delete the file with the same name under the same level of loading.js, Nginx proxy address: /omc-api
|
||||
*
|
||||
@@ -20,7 +20,7 @@
|
||||
host = `${hostname}:33443`;
|
||||
wsprotocol = "wss:"
|
||||
}
|
||||
|
||||
|
||||
// Service Address
|
||||
sessionStorage.setItem('baseUrl', `${protocol}//${host}`);
|
||||
// websocket Address
|
||||
|
||||
BIN
public/neDataImput/import_amf_imeiWhitelist_template.xlsx
Normal file
BIN
public/neDataImput/import_amf_whitelist_template.xlsx
Normal file
BIN
public/neDataImput/import_mme_imeiWhitelist_template.xlsx
Normal file
1
public/neDataImput/pcf_template.txt
Normal file
@@ -0,0 +1 @@
|
||||
001012082101039,1234,internet|ims_sig,internet|ims_sig,321321,255,321312,32131,32131
|
||||
2
public/neDataImput/udm_auth_template.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
001011100001157,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
|
||||
001011100001158,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
|
||||
2
public/neDataImput/udm_sub_template.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
001011100001157,62357000583,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,-
|
||||
001011100001158,62357000585,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,-
|
||||
2
public/neDataImput/udm_voip_template.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
62357000580,123456
|
||||
62357000581,123456
|
||||
2
public/neDataImput/udm_volte_template.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
001012082101039,62357000580,1,ims.mnc001.mcc001.3gppnetwork.org
|
||||
62357000581,62357000581,0,ims.mnc001.mcc001.3gppnetwork.org
|
||||
@@ -9,6 +9,8 @@ import advancedFormat from 'dayjs/plugin/advancedFormat';
|
||||
import useLayoutStore from '@/store/modules/layout';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { CACHE_LOCAL_PROCONFIG } from './constants/cache-keys-constants';
|
||||
import { localRemove } from './utils/cache-local-utils';
|
||||
const { t, currentLocale } = useI18n();
|
||||
const { themeConfig, initPrimaryColor, changeConf } = useLayoutStore();
|
||||
dayjs.extend(advancedFormat);
|
||||
@@ -34,6 +36,7 @@ onBeforeMount(() => {
|
||||
maxCount: 15,
|
||||
});
|
||||
initPrimaryColor();
|
||||
localRemove(CACHE_LOCAL_PROCONFIG);
|
||||
|
||||
// 输出应用版本号
|
||||
const appStore = useAppStore();
|
||||
|
||||
@@ -211,7 +211,7 @@ export function getPass() {
|
||||
* @param data 鉴权对象
|
||||
* @returns object
|
||||
*/
|
||||
export function clearAlarm(data: Record<string, any>) {
|
||||
export function clearAlarm2(data: Record<string, any>) {
|
||||
var time = new Date();
|
||||
const userName = useUserStore().userName;
|
||||
let finalData = {
|
||||
@@ -232,6 +232,19 @@ export function clearAlarm(data: Record<string, any>) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除告警信息
|
||||
* @param ids 记录ID
|
||||
* @returns object
|
||||
*/
|
||||
export function clearAlarm(ids: string[]) {
|
||||
return request({
|
||||
url: `/neData/alarm/clear`,
|
||||
method: 'PUT',
|
||||
data: { ids },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 手工同步
|
||||
* @param data 鉴权对象
|
||||
@@ -360,3 +373,137 @@ export async function top3Sel(filterFlag?: string) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export async function alarmDashGetAct() {
|
||||
let totalSQL = `select count(*) as total from alarm where alarm_status='1' `;
|
||||
let rowsSQL = `select ne_type,alarm_id,alarm_title,orig_severity,event_time from alarm WHERE alarm_status='1' and orig_severity!='Event' order by event_time desc limit 0,10 `;
|
||||
// 查询
|
||||
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: totalSQL,
|
||||
rowsSQL: rowsSQL,
|
||||
},
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
const data: DataList = {
|
||||
total: 0,
|
||||
rows: [],
|
||||
code: result.code,
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
const itemData = item['alarm'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
data.total = itemData[0]['total'];
|
||||
} else {
|
||||
data.rows = itemData.map(v => parseObjLineToHump(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function alarmDashGetHis() {
|
||||
let totalSQL = `select count(*) as total from alarm where alarm_status='0' `;
|
||||
let rowsSQL = `select ne_type,alarm_id,alarm_title,orig_severity,event_time from alarm WHERE alarm_status='0' and orig_severity!='Event' order by event_time desc limit 0,10 `;
|
||||
// 查询
|
||||
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: totalSQL,
|
||||
rowsSQL: rowsSQL,
|
||||
},
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
const data: DataList = {
|
||||
total: 0,
|
||||
rows: [],
|
||||
code: result.code,
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
const itemData = item['alarm'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
data.total = itemData[0]['total'];
|
||||
} else {
|
||||
data.rows = itemData.map(v => parseObjLineToHump(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
export async function getAlarmTrend(params: any) {
|
||||
const days = Number(params.days || 1);
|
||||
let groupFormat = days === 1 ? '%H:00' : '%Y-%m-%d';
|
||||
let timeCondition = days === 1
|
||||
? `event_time >= DATE_SUB(NOW(), INTERVAL 1 DAY)`
|
||||
: `event_time >= DATE_SUB(CURDATE(), INTERVAL ${days} DAY)`;
|
||||
let totalSQL = `select count(*) as total from alarm where alarm_status='0' `;
|
||||
|
||||
let rowsSQL = ` SELECT
|
||||
DATE_FORMAT(event_time, '${groupFormat}') AS time,
|
||||
SUM(CASE WHEN orig_severity='Critical' THEN 1 ELSE 0 END) AS Critical,
|
||||
SUM(CASE WHEN orig_severity='Major' THEN 1 ELSE 0 END) AS Major,
|
||||
SUM(CASE WHEN orig_severity='Minor' THEN 1 ELSE 0 END) AS Minor,
|
||||
SUM(CASE WHEN orig_severity='Warning' THEN 1 ELSE 0 END) AS Warning
|
||||
FROM alarm
|
||||
WHERE alarm_status='0'
|
||||
AND ${timeCondition}
|
||||
GROUP BY time
|
||||
ORDER BY time ASC `;
|
||||
// 查询
|
||||
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: totalSQL,
|
||||
rowsSQL: rowsSQL,
|
||||
},
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
const data: DataList = {
|
||||
total: 0,
|
||||
rows: [],
|
||||
code: result.code,
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
const itemData = item['alarm'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
data.total = itemData[0]['total'];
|
||||
} else {
|
||||
data.rows = itemData.map(v => parseObjLineToHump(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,3 +61,69 @@ export function getCaptchaImage() {
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录认证源
|
||||
* @returns object
|
||||
*/
|
||||
export function getLoginSource() {
|
||||
return request({
|
||||
url: '/auth/login/source',
|
||||
method: 'GET',
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP登录
|
||||
* @returns object
|
||||
*/
|
||||
export function loginLDAP(data: Record<string, string>) {
|
||||
return request({
|
||||
url: '/auth/login/ldap',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SMTP登录
|
||||
* @returns object
|
||||
*/
|
||||
export function loginSMTP(data: Record<string, string>) {
|
||||
return request({
|
||||
url: '/auth/login/smtp',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录认证源OAuth2跳转登录URL
|
||||
* @returns object
|
||||
*/
|
||||
export function loginOAuth2URL(state: string): string {
|
||||
// 兼容旧前端可改配置文件
|
||||
const baseUrl = import.meta.env.PROD
|
||||
? sessionGet('baseUrl') || import.meta.env.VITE_API_BASE_URL
|
||||
: import.meta.env.VITE_API_BASE_URL;
|
||||
return `${baseUrl}/auth/login/oauth2/authorize?state=${state}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录认证源OAuth2认证登录
|
||||
* @returns object
|
||||
*/
|
||||
export function loginOAuth2Token(code: string, state: string) {
|
||||
return request({
|
||||
url: '/auth/login/oauth2/token',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code,
|
||||
state,
|
||||
},
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import { request } from '@/plugins/http-fetch';
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function getAllNeConfig(neType: string) {
|
||||
export function getAllNeConfig(neType: string, neId: string) {
|
||||
return request({
|
||||
url: `/ne/config/list/${neType}`,
|
||||
method: 'get',
|
||||
params: { neId },
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function exportAMFDataUE(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
68
src/api/neData/backup.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 备份文件-获取FTP配置
|
||||
* @returns object
|
||||
*/
|
||||
export function getBackupFTP() {
|
||||
return request({
|
||||
url: '/neData/backup/ftp',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-文件FTP发送
|
||||
* @param data 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function pushBackupFTP(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/backup/ftp',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-更新FTP配置
|
||||
* @param data 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateBackupFTP(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/backup/ftp',
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-导出OMC
|
||||
* @returns object
|
||||
*/
|
||||
export function exportBackupOMC() {
|
||||
return request({
|
||||
url: '/neData/backup/export-omc',
|
||||
method: 'POST',
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-导入OMC
|
||||
* @param filePath 备份文件上传返回的/upload 路径
|
||||
* @returns object
|
||||
*/
|
||||
export function importBackupOMC(filePath: string) {
|
||||
return request({
|
||||
url: '/neData/backup/import-omc',
|
||||
method: 'POST',
|
||||
data: {
|
||||
neType: 'OMC',
|
||||
path: filePath,
|
||||
},
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
@@ -38,6 +38,6 @@ export function exportIMSDataCDR(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
148
src/api/neData/ims_sub.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* UDM签约用户重载数据
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function resetIMSSub(neId: string) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/resetData/${neId}`,
|
||||
method: 'put',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listIMSSub(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ue/udm/imsuser/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户信息
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @returns object
|
||||
*/
|
||||
export function getIMSSub(neId: string, imsi: string) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${neId}/${imsi}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户新增
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addIMSSub(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${data.neId}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户批量新增
|
||||
* @param data 签约对象
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchAddIMSSub(data: Record<string, any>, num: number) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${data.neId}/${num}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户修改
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
// export function updateIMSSub(data: Record<string, any>) {
|
||||
// return request({
|
||||
// url: `/ue/udm/imsuser/${data.neId}`,
|
||||
// method: 'put',
|
||||
// data: data,
|
||||
// timeout: 180_000,
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* UDM签约用户删除
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
export function delIMSSub(neId: string, imsi_msisdn: string, tag: string) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${neId}/${imsi_msisdn}`,
|
||||
method: 'delete',
|
||||
params: { volte: tag },
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户批量删除
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchDelIMSSub(
|
||||
neId: string,
|
||||
imsi: string,
|
||||
num: number,
|
||||
tag: string
|
||||
) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${neId}/${imsi}/${num}`,
|
||||
method: 'delete',
|
||||
params: { volte: tag },
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户导出
|
||||
* @param data 数据参数
|
||||
* @returns bolb
|
||||
*/
|
||||
export function exportIMSSub(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ue/udm/imsuser/export',
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户导入
|
||||
* @param data 表单数据对象
|
||||
* @returns object
|
||||
*/
|
||||
export function importIMSSub(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/import`,
|
||||
method: 'post',
|
||||
data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export function exportMMEDataUE(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,6 @@ export function exportNBState(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,6 +38,6 @@ export function exportSGWCDataCDR(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function exportSMFDataCDR(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,6 @@ export function exportSMSCDataCDR(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,3 +140,17 @@ export function exportUDMAuth(data: Record<string, any>) {
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户导出DecAuth
|
||||
* @param neId 网元ID
|
||||
* @returns bolb
|
||||
*/
|
||||
export function exportUDMDecAuth(neId: string) {
|
||||
return request({
|
||||
url: `/neData/udm/auth/export-dec?neId=${neId}`,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
142
src/api/neData/voip_auth.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* UDM鉴权用户重载数据
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function resetUDMAuth(neId: string) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/resetData/${neId}`,
|
||||
method: 'put',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listUDMAuth(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ue/udm/voipauth/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户信息
|
||||
* @param neId 网元ID
|
||||
* @param userName 用户名
|
||||
* @returns object
|
||||
*/
|
||||
export function getUDMAuth(neId: string, userName: string) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${neId}/${userName}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户新增
|
||||
* @param data 鉴权对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addUDMAuth(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${data.neId}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户批量新增
|
||||
* @param data 鉴权对象
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchAddUDMAuth(data: Record<string, any>, num: number) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${data.neId}/${num}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户修改
|
||||
* @param data 鉴权对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateUDMAuth(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${data.neId}`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户删除
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @returns object
|
||||
*/
|
||||
export function delUDMAuth(neId: string, imsi: string) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${neId}/${imsi}`,
|
||||
method: 'delete',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户批量删除
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchDelUDMAuth(neId: string, imsi: string, num: number) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${neId}/${imsi}/${num}`,
|
||||
method: 'delete',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户导入
|
||||
* @param data 表单数据对象
|
||||
* @returns object
|
||||
*/
|
||||
export function importUDMAuth(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/import`,
|
||||
method: 'post',
|
||||
data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户导出
|
||||
* @param data 数据参数
|
||||
* @returns bolb
|
||||
*/
|
||||
export function exportUDMAuth(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ue/udm/voipauth/export',
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
@@ -21,12 +21,14 @@ export async function listUEInfoBySMF(query: Record<string, any>) {
|
||||
};
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && result.data) {
|
||||
if (Array.isArray(result.data.data)) {
|
||||
const rows = result.data.data;
|
||||
data.total = rows.length;
|
||||
data.rows = rows;
|
||||
if (result.data.total && result.data.data) {
|
||||
data.total = result.data.total;
|
||||
data.rows = result.data.data;
|
||||
} else {
|
||||
Object.assign(data, result.data);
|
||||
Object.assign(data, {
|
||||
total: result.data.length,
|
||||
rows: result.data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,78 +110,52 @@ export async function getKPITitle(neType: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo 废弃
|
||||
* 查询UPF上下行速率数据
|
||||
* @param query 查询参数
|
||||
* 查询黄金指标数据kpi.id转换title
|
||||
* @param neType 网元类型
|
||||
* @returns object
|
||||
*/
|
||||
export async function listUPFData(timeArr: any) {
|
||||
const initTime: Date = new Date();
|
||||
const twentyFourHoursAgo: Date = new Date(
|
||||
initTime.getTime() - 10 * 60 * 1000
|
||||
);
|
||||
|
||||
return await Promise.allSettled([
|
||||
// 获取参数规则
|
||||
request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/gold_kpi`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.03' AND timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 10 MINUTE) AND NOW()`,
|
||||
},
|
||||
timeout: 60_000,
|
||||
}),
|
||||
// 获取对应信息
|
||||
request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/gold_kpi`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.06' AND timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 10 MINUTE) AND NOW()`,
|
||||
},
|
||||
timeout: 60_000,
|
||||
}),
|
||||
]).then(resArr => {
|
||||
let upData: any = [];
|
||||
let downData: any = [];
|
||||
|
||||
// 规则数据
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
const itemV: any = resArr[0].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
let data = itemData[0]['gold_kpi'];
|
||||
if (Array.isArray(data)) {
|
||||
try {
|
||||
upData = data.map(v => parseObjLineToHump(v));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
const itemV = resArr[1].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
const data = itemData[0]['gold_kpi'];
|
||||
if (Array.isArray(data)) {
|
||||
try {
|
||||
downData = data.map(v => parseObjLineToHump(v));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { upData, downData };
|
||||
export async function getKPITitleList(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/kpi/title/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改指标标题
|
||||
* @param data 指标标题对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateKPITitle(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/kpi/title',
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
//忙时呼叫
|
||||
export async function getbusyhour(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/ims/kpi/busy-hour',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
//MOS指标
|
||||
export async function getMosHour(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/ims/cdr/mos-hour',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
//CCT指标
|
||||
export async function getCctHour(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/ims/cdr/cct-hour',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
64
src/api/system/login-source.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询登录源列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listLoginSource(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/login-source/list',
|
||||
method: 'GET',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询登录源详细
|
||||
* @param id 登录源ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getLoginSource(id: string | number) {
|
||||
return request({
|
||||
url: `/system/login-source/${id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增登录源
|
||||
* @param data 登录源对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addLoginSource(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/login-source',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改登录源
|
||||
* @param data 登录源对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateLoginSource(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/login-source',
|
||||
method: 'PUT',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证源删除
|
||||
* @param id 登录源ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delLoginSource(id: string | number) {
|
||||
return request({
|
||||
url: `/system/login-source/${id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
99
src/api/system/tenant.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询部门列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listTenant(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/tenant/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门列表(排除节点)
|
||||
* @param tenantId 部门ID
|
||||
* @returns object
|
||||
*/
|
||||
export function listTenantExcludeChild(tenantId: string | number) {
|
||||
return request({
|
||||
url: `/system/tenant/list/exclude/${tenantId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门详细
|
||||
* @param tenantId 部门ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getTenant(tenantId: string | number) {
|
||||
return request({
|
||||
url: `/system/tenant/${tenantId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增部门
|
||||
* @param data 部门对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addTenant(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/tenant',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改部门
|
||||
* @param data 部门对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateTenant(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/tenant',
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
* @param TenantId 部门ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delTenant(TenantId: string | number) {
|
||||
return request({
|
||||
url: `/system/tenant/${TenantId}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门下拉树结构
|
||||
* @returns object
|
||||
*/
|
||||
export function tenantTreeSelect() {
|
||||
return request({
|
||||
url: '/system/tenant/treeSelect',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门树结构列表(指定角色)
|
||||
* @param roleId 角色ID
|
||||
* @returns object
|
||||
*/
|
||||
export function roleTenantTreeSelect(roleId: string | number) {
|
||||
return request({
|
||||
url: `/system/tenant/roleDeptTreeSelect/${roleId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import { encode } from 'js-base64';
|
||||
export async function downloadFile(filePath: string, range?: string) {
|
||||
return request({
|
||||
url: `/file/download/${encode(filePath)}`,
|
||||
method: 'get',
|
||||
method: 'GET',
|
||||
headers: range ? { range } : {},
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
@@ -76,8 +76,8 @@ export async function downloadFileChunk(
|
||||
*/
|
||||
export function uploadFile(data: FormData) {
|
||||
return request({
|
||||
url: '/file/upload',
|
||||
method: 'post',
|
||||
url: '/file/upload2',
|
||||
method: 'POST',
|
||||
data,
|
||||
dataType: 'form-data',
|
||||
timeout: 180_000,
|
||||
@@ -169,7 +169,7 @@ export async function uploadFileChunk(
|
||||
export function chunkCheck(identifier: string, fileName: string) {
|
||||
return request({
|
||||
url: '/file/chunkCheck',
|
||||
method: 'post',
|
||||
method: 'POST',
|
||||
data: { identifier, fileName },
|
||||
timeout: 60_000,
|
||||
});
|
||||
@@ -189,7 +189,7 @@ export function chunkMerge(
|
||||
) {
|
||||
return request({
|
||||
url: '/file/chunkMerge',
|
||||
method: 'post',
|
||||
method: 'POST',
|
||||
data: { identifier, fileName, subPath },
|
||||
timeout: 60_000,
|
||||
});
|
||||
@@ -203,13 +203,57 @@ export function chunkMerge(
|
||||
export function chunkUpload(data: FormData) {
|
||||
return request({
|
||||
url: '/file/chunkUpload',
|
||||
method: 'post',
|
||||
method: 'POST',
|
||||
data,
|
||||
dataType: 'form-data',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件列表
|
||||
* @param path 文件路径
|
||||
* @param search search prefix
|
||||
* @returns object
|
||||
*/
|
||||
export async function listFile(query: Record<string, any>) {
|
||||
return request({
|
||||
url: `/file/list`,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件获取下载
|
||||
* @param path 文件路径
|
||||
* @param fileName 文件名
|
||||
* @returns object
|
||||
*/
|
||||
export async function getFile(path: string, fileName: string) {
|
||||
return request({
|
||||
url: `/file`,
|
||||
method: 'GET',
|
||||
params: { path, fileName },
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件删除
|
||||
* @param path 文件路径
|
||||
* @param fileName 文件名
|
||||
* @returns object
|
||||
*/
|
||||
export async function delFile(path: string, fileName: string) {
|
||||
return request({
|
||||
url: `/file`,
|
||||
method: 'DELETE',
|
||||
params: { path, fileName },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 转存上传文件到静态资源
|
||||
* @returns object
|
||||
@@ -217,7 +261,7 @@ export function chunkUpload(data: FormData) {
|
||||
export function transferStaticFile(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/file/transferStaticFile`,
|
||||
method: 'post',
|
||||
method: 'POST',
|
||||
data,
|
||||
timeout: 60_000,
|
||||
});
|
||||
@@ -241,11 +285,12 @@ export async function uploadFileToNE(
|
||||
if (uploadChunkRes.code === RESULT_CODE_SUCCESS) {
|
||||
const transferToNeFileRes = await request({
|
||||
url: `/ne/action/pushFile`,
|
||||
method: 'post',
|
||||
method: 'POST',
|
||||
data: {
|
||||
uploadPath: uploadChunkRes.data.fileName,
|
||||
neType,
|
||||
neId,
|
||||
delTemp: true,
|
||||
},
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
@@ -45,6 +45,6 @@ export function getNeViewFile(data: Record<string, any>) {
|
||||
url: '/ne/action/viewFile',
|
||||
method: 'get',
|
||||
params: data,
|
||||
timeout: 60_000,
|
||||
timeout: 600_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,3 +8,12 @@ export function pingV(data: Record<string, string>) {
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
|
||||
// ping RTT 时延抖动
|
||||
export function pingRTT(data: Record<string, string>) {
|
||||
return request({
|
||||
url: '/tool/ping/rtt',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 70 KiB |
1
src/assets/svg/4gEvent.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="1721721606045" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7339" width="200" height="200"><path d="M610.6 234.24L627 294.6c90.88-24.73 184.58 28.77 209.45 119.65l60.36-16.4C862.92 273.5 734.67 200.37 610.6 234.24z m-34.42-126.22l16.4 60.36c160.51-43.83 326.13 50.81 369.96 211.33l60.36-16.4C969.94 169.45 770.03 55.18 576.18 108.02zM179.47 922.27V827.5H0v-84.83l179.47-261.74h92.22v274.24h62.38v72.33h-62.38v94.77h-92.22z m0-167.09V598.02L72.33 755.18h107.14z m0 0" p-id="7340" fill="#ffffff"></path><path d="M695.69 752.62h-89.8v-77.29h186.99v231.9c-33.34 9.95-67.35 14.92-102.17 14.92-26.62 3.37-61.57 4.97-104.72 4.97-141.29-8.33-213.62-86.44-216.97-234.31 3.36-151.24 75.69-231.9 216.97-241.85 144.65 0 216.03 49.88 214.42 149.63H695.69c0-51.49-30.79-77.3-92.23-77.3-84.83 1.62-127.98 58.21-129.59 169.52 1.61 109.7 44.9 165.49 129.59 167.1 24.87 0 54.05-3.36 87.25-9.95-1.61 1.61 0 1.61 4.98 0v-97.34z m0 0" p-id="7341" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
src/assets/svg/5gEvent.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="1721721630123" class="icon" viewBox="0 0 1216 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="54141" width="200" height="200"><path d="M747.264 190.912l18.368 67.328a190.272 190.272 0 0 1 233.728 133.44l67.264-18.368a260.032 260.032 0 0 0-319.36-182.4z m-38.4-140.736l18.368 67.328a336.192 336.192 0 0 1 412.8 235.776l67.328-18.368A405.952 405.952 0 0 0 708.864 50.176z m133.376 719.232h-100.16v-86.272h208.64v258.752c-37.12 11.136-75.072 16.704-114.048 16.704a984.96 984.96 0 0 1-116.864 5.568c-157.632-9.28-238.336-96.448-242.048-261.504 3.712-168.768 84.416-258.752 242.048-269.888 161.344 0 241.088 55.68 239.296 166.912H842.24c0-57.472-34.368-86.208-102.912-86.208-94.592 1.856-142.848 64.896-144.64 189.184 1.792 122.368 50.048 184.576 144.64 186.368 27.776 0 60.16-3.712 97.344-11.136-1.92 1.92 0 1.92 5.568 0v-108.48zM234.24 969.216c-86.016 0-148.992-30.72-201.216-80.64l71.424-85.248c40.704 36.864 80.64 58.368 128.256 58.368 55.296 0 89.856-26.88 89.856-74.496v-1.536c0-46.08-39.168-72.96-95.232-72.96-33.792 0-64.512 9.216-89.856 19.968L69.12 687.36l15.36-264.96h330.24v103.68H185.856l-6.144 92.928a262.976 262.976 0 0 1 70.656-9.216c104.448 0 188.16 50.688 188.16 172.032v1.536c0 113.664-80.64 185.856-204.288 185.856z" p-id="54142" fill="#ffffff"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
3
src/assets/svg/basefff.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<?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="1720074329868" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4280" width="64" height="64"><path d="M616.5 151.3l-28.3 28.3c19.7 19.6 31.8 46.6 31.8 76.5 0 29.8-12.2 56.9-31.8 76.5l28.3 28.3c26.8-27 43.5-64 43.5-104.9s-16.7-77.9-43.5-104.7zM435.8 179.5l-28.3-28.3C380.7 178.1 364 215.1 364 256s16.7 77.9 43.5 104.7l28.3-28.3C416.2 312.9 404 285.8 404 256c0-29.9 12.2-56.9 31.8-76.5zM346.6 134.3L318.3 106c-38.5 38.4-62.3 91.5-62.3 150s23.8 111.6 62.3 150l28.3-28.3C315.3 346.6 296 303.5 296 256s19.3-90.6 50.6-121.7zM705.7 106l-28.3 28.3C708.7 165.4 728 208.5 728 256s-19.3 90.6-50.6 121.7l28.3 28.3c38.5-38.4 62.3-91.5 62.3-150s-23.8-111.6-62.3-150zM815.2 60.8l-0.1-0.1L786.7 89c0.1 0 0.1 0.1 0.2 0.1C831.5 133.7 856 193 856 256s-24.5 122.3-69.1 166.9c0 0-0.1 0.1-0.2 0.1l28.3 28.3 0.1-0.1C867.3 399 896 329.7 896 256s-28.7-143-80.8-195.2zM237.3 89L209 60.7l-0.1 0.1C156.7 113 128 182.3 128 256s28.7 143 80.8 195.2l0.1 0.1 28.3-28.3c-0.1 0-0.1-0.1-0.2-0.1-44.5-44.6-69-103.9-69-166.9s24.5-122.3 69.1-166.9c0.1 0 0.1-0.1 0.2-0.1zM511.712 256.002l0.283-0.283 0.283 0.283-0.283 0.283z" p-id="4281" fill="#ffffff"></path><path d="M511.719 256.021l0.282-0.282 0.283 0.282-0.283 0.283z" p-id="4282" fill="#ffffff"></path><path d="M708.4 616l-14.2-26.1-7.6-13.9-11.6-21.2L512 256 337.5 576l-21.8 40L128 960h74l33.8-64 28.9-53h494.6l28.9 53 33.8 64h74L708.4 616zM512 389.6L613.7 576H410.3L512 389.6zM388.5 616H569L305.6 768.1 388.5 616z m-63.4 187l313.6-181.1L737.5 803H325.1z" p-id="4283" fill="#ffffff"></path></svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
1
src/assets/svg/smscCdr.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="1729046804258" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1511" width="200" height="200"><path d="M958.359001 286.941198c0-64.308655-52.132334-116.440989-116.440989-116.440989L181.453678 170.500209c-64.308655 0-116.440989 52.132334-116.440989 116.440989l0 448.63995c0 64.308655 52.132334 116.440989 116.440989 116.440989l660.464333 0c64.308655 0 116.440989-52.132334 116.440989-116.440989L958.359001 286.941198zM843.814198 212.455763c12.059664 0 23.444968 3.465938 33.519418 8.598842L530.749016 549.347606c-1.227967 1.161453-1.156336 0.916882-1.752924 1.868557-1.426489 1.067308-6.055926 3.922333-15.449877 3.922333-9.397021 0-14.025435-2.75474-15.4509-3.818979-0.676405-1.077541-0.662079-0.716314-1.995447-1.940189l-353.122503-324.412624c11.609409-7.557116 25.445532-12.509918 40.300868-12.509918L843.814198 212.456786zM916.403446 736.484727c0 40.857547-31.730679 73.582879-72.589248 73.582879L183.27721 810.067606c-40.85857 0-75.28566-32.725332-75.28566-73.582879L107.99155 287.657512c0-10.630105 2.860141-20.735253 6.909363-29.882588l351.486236 322.096882c4.91187 5.302773 19.479657 17.676591 47.009663 17.676591 27.799136 0 42.303478-12.618389 47.076178-17.830087l346.800517-328.899822c6.251378 10.860349 9.129938 23.433712 9.129938 36.838L916.403446 736.484727z" fill="#ffffff" p-id="1512"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
479
src/components/ExportCustomModal/index.vue
Normal file
@@ -0,0 +1,479 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="t('common.exportCustom')"
|
||||
width="750px"
|
||||
:confirm-loading="confirmLoading"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="export-custom-container">
|
||||
<!-- 列配置区域 -->
|
||||
<div class="columns-config">
|
||||
<div class="config-header">
|
||||
<h4>{{ t('common.exportColumns') }}</h4>
|
||||
<a-button type="link" @click="resetToDefault">
|
||||
{{ t('common.resetToDefault') }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="columns-list">
|
||||
<Container
|
||||
@drop="onDrop"
|
||||
:get-child-payload="getChildPayload"
|
||||
drag-class="drag-ghost"
|
||||
drop-class="drop-ghost"
|
||||
drag-handle-selector=".drag-handle"
|
||||
:non-drag-area-selector="'.column-name, .ant-checkbox-wrapper'"
|
||||
>
|
||||
<Draggable
|
||||
v-for="(column, index) in customColumns"
|
||||
:key="column.key"
|
||||
class="column-item"
|
||||
>
|
||||
<div class="column-controls">
|
||||
<a-checkbox
|
||||
v-model:checked="column.visible"
|
||||
@change="updateColumnVisibility(column)"
|
||||
/>
|
||||
<div class="drag-handle">
|
||||
<HolderOutlined />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column-info">
|
||||
<div class="column-name">
|
||||
<a-input
|
||||
v-model:value="column.title"
|
||||
:placeholder="t('common.columnName')"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<!-- 隐藏col_数字标签,不影响用户使用 -->
|
||||
<!-- <div class="column-key">
|
||||
<a-tag size="small" color="blue">{{ column.key }}</a-tag>
|
||||
</div> -->
|
||||
</div>
|
||||
</Draggable>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览区域 -->
|
||||
<div class="preview-section">
|
||||
<h4>{{ t('common.preview') }}</h4>
|
||||
<div class="preview-table">
|
||||
<a-table
|
||||
:columns="previewColumns"
|
||||
:data-source="previewData"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
:scroll="{ x: 400 }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import { Container, Draggable } from 'vue3-smooth-dnd';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
|
||||
interface CustomColumn {
|
||||
key: string;
|
||||
title: string;
|
||||
visible: boolean;
|
||||
originalTitle: string;
|
||||
dataIndex?: string;
|
||||
customRender?: any;
|
||||
columnIndex?: number; // Excel列索引
|
||||
}
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
originalColumns: ColumnsType;
|
||||
sampleData: any[];
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:open', value: boolean): void;
|
||||
(e: 'confirm', config: CustomColumn[]): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
const { t } = useI18n();
|
||||
|
||||
const visible = ref(false);
|
||||
const confirmLoading = ref(false);
|
||||
const customColumns = ref<CustomColumn[]>([]);
|
||||
|
||||
// 监听props变化
|
||||
watch(() => props.open, (newVal) => {
|
||||
visible.value = newVal;
|
||||
if (newVal) {
|
||||
initCustomColumns();
|
||||
}
|
||||
});
|
||||
|
||||
watch(visible, (newVal) => {
|
||||
emit('update:open', newVal);
|
||||
});
|
||||
|
||||
// 初始化自定义列配置
|
||||
function initCustomColumns() {
|
||||
// 如果传入的是完整的列配置(包含columnIndex),直接使用
|
||||
if (props.originalColumns.length > 0 && (props.originalColumns[0] as any).columnIndex !== undefined) {
|
||||
const columns: CustomColumn[] = props.originalColumns.map(col => ({
|
||||
key: (col as any).key,
|
||||
title: (col as any).title,
|
||||
visible: true,
|
||||
originalTitle: (col as any).originalTitle || (col as any).title,
|
||||
dataIndex: (col as any).dataIndex,
|
||||
columnIndex: (col as any).columnIndex
|
||||
}));
|
||||
|
||||
// 尝试从本地存储加载配置
|
||||
const savedConfig = localStorage.getItem('sgwc-cdr-export-config');
|
||||
if (savedConfig) {
|
||||
try {
|
||||
const saved = JSON.parse(savedConfig);
|
||||
console.log('Loaded saved config:', saved);
|
||||
|
||||
// 基于originalTitle匹配保存的配置
|
||||
const mergedColumns = columns.map(col => {
|
||||
const savedCol = saved.find((s: any) => s.originalTitle === col.originalTitle);
|
||||
if (savedCol) {
|
||||
console.log(`Matched column: ${col.originalTitle}, visible: ${savedCol.visible}, title: ${savedCol.title}`);
|
||||
return {
|
||||
...col,
|
||||
visible: savedCol.visible !== undefined ? savedCol.visible : true,
|
||||
title: savedCol.title || col.title
|
||||
};
|
||||
}
|
||||
return col;
|
||||
});
|
||||
|
||||
// 按照保存的顺序排列(如果存在)
|
||||
const sortedColumns = sortColumnsByConfig(mergedColumns, saved);
|
||||
console.log('Final columns after merge and sort:', sortedColumns);
|
||||
customColumns.value = sortedColumns;
|
||||
} catch (error) {
|
||||
console.error('Failed to load saved export config:', error);
|
||||
customColumns.value = columns;
|
||||
}
|
||||
} else {
|
||||
console.log('No saved config found, using default columns');
|
||||
customColumns.value = columns;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 兼容旧的表格列配置
|
||||
const columns: CustomColumn[] = props.originalColumns
|
||||
.filter(col => col.key !== 'id' && (col as any).dataIndex) // 过滤掉操作列
|
||||
.map(col => ({
|
||||
key: col.key as string,
|
||||
title: col.title as string,
|
||||
visible: true,
|
||||
originalTitle: col.title as string,
|
||||
dataIndex: (col as any).dataIndex as string,
|
||||
customRender: (col as any).customRender
|
||||
}));
|
||||
|
||||
// 尝试从本地存储加载配置
|
||||
const savedConfig = localStorage.getItem('sgwc-cdr-export-config');
|
||||
if (savedConfig) {
|
||||
try {
|
||||
const saved = JSON.parse(savedConfig);
|
||||
// 合并保存的配置和当前列,只合并特定字段
|
||||
const mergedColumns = columns.map(col => {
|
||||
const savedCol = saved.find((s: CustomColumn) => s.key === col.key);
|
||||
if (savedCol) {
|
||||
return {
|
||||
...col,
|
||||
visible: savedCol.visible, // 只合并可见性
|
||||
title: savedCol.title !== savedCol.originalTitle ? savedCol.title : col.title // 只合并自定义的标题
|
||||
};
|
||||
}
|
||||
return col;
|
||||
});
|
||||
customColumns.value = mergedColumns;
|
||||
} catch (error) {
|
||||
console.error('Failed to load saved export config:', error);
|
||||
customColumns.value = columns;
|
||||
}
|
||||
} else {
|
||||
customColumns.value = columns;
|
||||
}
|
||||
}
|
||||
|
||||
// 按照保存的配置排序列
|
||||
function sortColumnsByConfig(columns: CustomColumn[], savedConfig: CustomColumn[]): CustomColumn[] {
|
||||
if (!savedConfig || savedConfig.length === 0) {
|
||||
return columns;
|
||||
}
|
||||
|
||||
// 创建一个映射:originalTitle -> savedOrder
|
||||
const orderMap = new Map<string, number>();
|
||||
savedConfig.forEach((col, index) => {
|
||||
orderMap.set(col.originalTitle, index);
|
||||
});
|
||||
|
||||
// 排序:已保存的列按保存顺序,新列放在最后
|
||||
return [...columns].sort((a, b) => {
|
||||
const orderA = orderMap.has(a.originalTitle) ? orderMap.get(a.originalTitle)! : 9999;
|
||||
const orderB = orderMap.has(b.originalTitle) ? orderMap.get(b.originalTitle)! : 9999;
|
||||
return orderA - orderB;
|
||||
});
|
||||
}
|
||||
|
||||
// 预览列配置
|
||||
const previewColumns = computed(() => {
|
||||
return customColumns.value
|
||||
.filter(col => col.visible)
|
||||
.map(col => ({
|
||||
title: col.title,
|
||||
dataIndex: col.dataIndex || col.key,
|
||||
key: col.key,
|
||||
customRender: col.customRender
|
||||
}));
|
||||
});
|
||||
|
||||
// 预览数据(只显示前2条,降低预览高度)
|
||||
const previewData = computed(() => {
|
||||
return props.sampleData.slice(0, 1);
|
||||
});
|
||||
|
||||
// 更新列可见性
|
||||
function updateColumnVisibility(column: CustomColumn) {
|
||||
// 确保至少有一列可见
|
||||
const visibleCount = customColumns.value.filter(col => col.visible).length;
|
||||
if (visibleCount === 0) {
|
||||
column.visible = true;
|
||||
message.warning(t('common.atLeastOneColumn'));
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽相关函数
|
||||
function getChildPayload(index: number) {
|
||||
return customColumns.value[index];
|
||||
}
|
||||
|
||||
function onDrop(dropResult: any) {
|
||||
const { removedIndex, addedIndex } = dropResult;
|
||||
if (removedIndex !== null && addedIndex !== null) {
|
||||
const item = customColumns.value[removedIndex];
|
||||
customColumns.value.splice(removedIndex, 1);
|
||||
customColumns.value.splice(addedIndex, 0, item);
|
||||
}
|
||||
}
|
||||
|
||||
// 重置为默认配置
|
||||
function resetToDefault() {
|
||||
Modal.confirm({
|
||||
title: t('common.confirm'),
|
||||
content: t('common.resetConfirm'),
|
||||
onOk() {
|
||||
// 清除本地存储的配置
|
||||
localStorage.removeItem('sgwc-cdr-export-config');
|
||||
// 重新初始化
|
||||
initCustomColumns();
|
||||
message.success(t('common.resetSuccess'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 确认导出
|
||||
function handleOk() {
|
||||
const visibleColumns = customColumns.value.filter(col => col.visible);
|
||||
if (visibleColumns.length === 0) {
|
||||
message.error(t('common.selectAtLeastOneColumn'));
|
||||
return;
|
||||
}
|
||||
|
||||
confirmLoading.value = true;
|
||||
|
||||
// 保存配置到本地存储
|
||||
try {
|
||||
// 保存时只保存必要的字段,用于下次加载时恢复配置
|
||||
const configToSave = customColumns.value.map((col, index) => ({
|
||||
originalTitle: col.originalTitle || col.title, // 用于匹配列
|
||||
title: col.title, // 自定义标题
|
||||
visible: col.visible, // 可见性
|
||||
order: index // 顺序
|
||||
}));
|
||||
localStorage.setItem('sgwc-cdr-export-config', JSON.stringify(configToSave));
|
||||
console.log('Export config saved:', configToSave);
|
||||
} catch (error) {
|
||||
console.error('Failed to save export config:', error);
|
||||
}
|
||||
|
||||
// 延迟一下让用户看到loading状态
|
||||
setTimeout(() => {
|
||||
emit('confirm', customColumns.value);
|
||||
confirmLoading.value = false;
|
||||
visible.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.open) {
|
||||
initCustomColumns();
|
||||
}
|
||||
});
|
||||
|
||||
// 调试功能:清除有问题的缓存
|
||||
function clearProblematicCache() {
|
||||
localStorage.removeItem('sgwc-cdr-export-config');
|
||||
console.log('Cleared problematic cache');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.export-custom-container {
|
||||
.columns-config {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.config-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.columns-list {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
.column-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.column-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 12px;
|
||||
|
||||
.drag-handle {
|
||||
margin-left: 8px;
|
||||
cursor: grab;
|
||||
color: #999;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
color: #0050b3;
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
cursor: default;
|
||||
|
||||
.column-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.column-key {
|
||||
display: none; // 隐藏标签
|
||||
}
|
||||
}
|
||||
|
||||
// 禁止复选框触发拖拽
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
h4 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.preview-table {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
max-height: 180px;
|
||||
|
||||
:deep(.ant-table-wrapper) {
|
||||
.ant-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ant-table-thead > tr > th {
|
||||
padding: 8px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr > td {
|
||||
padding: 6px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ant-table-body {
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽样式
|
||||
.drag-ghost {
|
||||
opacity: 0.5;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.drop-ghost {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
</style>
|
||||
@@ -3,3 +3,6 @@ export const ADMIN_ROLE_KEY = 'system';
|
||||
|
||||
/**系统管理员-系统指定权限 */
|
||||
export const ADMIN_PERMISSION = '*:*:*';
|
||||
|
||||
/**次级系统管理员 */
|
||||
export const TENANTADMIN_ROLE_KEY = 'admin';
|
||||
|
||||
47
src/hooks/useRangePicker.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
/**日期快捷选择 */
|
||||
export function dayjsRanges() {
|
||||
return [
|
||||
{
|
||||
label: 'Today',
|
||||
value: [dayjs().startOf('day'), dayjs()],
|
||||
},
|
||||
{
|
||||
label: 'Last 1 hour',
|
||||
value: [
|
||||
dayjs().subtract(1, 'hour').startOf('hour'),
|
||||
dayjs().subtract(1, 'hour').endOf('hour'),
|
||||
],
|
||||
},
|
||||
// {
|
||||
// label: 'Last 3 hour',
|
||||
// value: [dayjs().subtract(3, 'hours'), dayjs()],
|
||||
// },
|
||||
// {
|
||||
// label: 'Last 6 hour',
|
||||
// value: [dayjs().subtract(6, 'hours'), dayjs()],
|
||||
// },
|
||||
{
|
||||
label: 'Last 1 day',
|
||||
value: [
|
||||
dayjs().subtract(1, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Last 7 day',
|
||||
value: [
|
||||
dayjs().subtract(7, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Last 15 day',
|
||||
value: [
|
||||
dayjs().subtract(15, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -10,6 +10,8 @@ export default {
|
||||
desc: 'Core Network Management Platform',
|
||||
loading: 'Please wait...',
|
||||
inputPlease: 'Please input',
|
||||
searchPlease: 'Search menus...',
|
||||
searchTip: 'Enter keywords to search menus',
|
||||
selectPlease: 'please select',
|
||||
tipTitle: 'Prompt',
|
||||
msgSuccess: 'Success {msg}',
|
||||
@@ -28,6 +30,7 @@ export default {
|
||||
editText: 'Edit',
|
||||
deleteText: 'Delete',
|
||||
downloadText: 'Download',
|
||||
CloudServerText:'Sync',
|
||||
import:'Import',
|
||||
export:'Export',
|
||||
uploadText: 'Upload',
|
||||
@@ -38,6 +41,11 @@ export default {
|
||||
columnSetText: 'Column Setting',
|
||||
columnSetTitle: 'Column Display / Sorting',
|
||||
sizeText: 'Density',
|
||||
preview:'Preview',
|
||||
exportCustom:'Custom Export',
|
||||
exportColumns:'Column Definition',
|
||||
resetToDefault:'Reset to default columns',
|
||||
exportDefault:'Export',
|
||||
size: {
|
||||
default: 'Default',
|
||||
middle: 'Medium',
|
||||
@@ -142,7 +150,7 @@ export default {
|
||||
|
||||
// 静态路由
|
||||
router: {
|
||||
index: "Home",
|
||||
index: "Overview",
|
||||
login: "Sign In",
|
||||
register: 'Registrations',
|
||||
page403: 'No Access',
|
||||
@@ -253,12 +261,16 @@ export default {
|
||||
login: {
|
||||
tabPane1: 'Account password login',
|
||||
tabPane2: 'Login with phone number',
|
||||
registerBtn: 'Register an account',
|
||||
registerBtn: 'Register Account',
|
||||
loginBtn: 'Sign In',
|
||||
loginSuccess: 'Login Successful',
|
||||
loginMethod: 'Other login methods:',
|
||||
loginMethodWX: 'WeChat Scan Login',
|
||||
loginMethodQQ: 'QQ Scan Code Login',
|
||||
otherMethod: 'Other Methods',
|
||||
backBtn: 'Back',
|
||||
backBtnLogin: 'Return Login',
|
||||
authorizedNotfound: 'Authorized Not Found',
|
||||
authorizedFailed: 'Authorized Failed',
|
||||
authorizedSuccess: 'Authorized Successful',
|
||||
redirectHome: 'Redirect to home page in {i} seconds',
|
||||
},
|
||||
register: {
|
||||
registerBtn: 'Register',
|
||||
@@ -352,18 +364,36 @@ export default {
|
||||
dashboard: {
|
||||
overview:{
|
||||
title: "Core Network Dashboard",
|
||||
fullscreen: "Click on the full-screen display",
|
||||
fullscreen: "Full Screen Display",
|
||||
toRouter: "Click to jump to the detail page",
|
||||
Users:"UDM Subscription Data",
|
||||
VoNR:"IMS Online Users",
|
||||
sessions:"PDU Sessions in SMF",
|
||||
FivegNodeN:"Online gNB in AMF",
|
||||
Fiveusers:"Active users in AMF",
|
||||
FourgNodeN:"Online eNB in MME",
|
||||
Fourusers:"Active users in MME",
|
||||
UPFjump:"Key Performance Indicators",
|
||||
Networkjump:"5GC System Architecture",
|
||||
Alarmjump:"Active Alarms",
|
||||
IMSUsers:"UDM IMS User Data",
|
||||
VoIPUsers:"UDM VoIP User Data",
|
||||
skim: {
|
||||
users: "Users",
|
||||
userTitle:'User Information',
|
||||
users: "Total Subscriber Base",
|
||||
userTitle:'Subscriber Information',
|
||||
imsUeNum: "VoNR/VoLTE",
|
||||
smfUeNum: "Data Sessions",
|
||||
smfUeNum: "User Sessions",
|
||||
gnbBase: "Online gNodeB",
|
||||
gnbSumBase: "Total gNodeB",
|
||||
enbBase: "Online eNodeB",
|
||||
enbSumBase: "Total eNodeB",
|
||||
gnbUeNum:'5G Active Users',
|
||||
enbUeNum:'4G Active Users',
|
||||
baseTitle:'Online Information',
|
||||
nodeBInfo: 'NodeB Information',
|
||||
onlineinfo:'UE Online Information',
|
||||
ims:'VoLTE',
|
||||
voip:'VoIP'
|
||||
},
|
||||
upfFlow:{
|
||||
title: "UPF Throughput",
|
||||
@@ -393,6 +423,7 @@ export default {
|
||||
},
|
||||
userActivity: {
|
||||
title: "User Activity",
|
||||
imsTitle: "IMS Activity",
|
||||
type: "Type",
|
||||
duration: "Duration",
|
||||
caller: "Caller",
|
||||
@@ -411,15 +442,20 @@ export default {
|
||||
rowInfo: "Info",
|
||||
type: "Type",
|
||||
duration: "Duration",
|
||||
mosAverage: "MOS",
|
||||
callConnectionTime: "Call Connection Time",
|
||||
seizureTime: "Call Start Time",
|
||||
releaseTime: "Hangup Time",
|
||||
caller: "Caller",
|
||||
called: "Called",
|
||||
result: "Result",
|
||||
resultCode: "Result Code",
|
||||
resultCause: "Result Cause",
|
||||
resultOk: "Success",
|
||||
resultFail: "Fail",
|
||||
delTip: "Confirm deletion of the data item numbered [{msg}]?",
|
||||
exportTip: "Do you confirm to export the current query conditions of the CDR data? (Maximum 10,000 items can be exported.)",
|
||||
tenantName: "Tenant Name",
|
||||
exportTip: "Do you confirm to export the current query conditions of the CDR data?",
|
||||
chargingID: 'Charging ID',
|
||||
smfSubscriptionIDData: 'Subscription ID Data',
|
||||
smfSubscriptionIDType: 'Subscription ID Type',
|
||||
@@ -443,7 +479,7 @@ export default {
|
||||
result: "Result",
|
||||
resultOk: "Successes",
|
||||
delTip: "Confirm deletion of the data item numbered [{msg}]?",
|
||||
exportTip: "Do you confirm to export the event data of the current query condition? (Maximum 10,000 items can be exported.)",
|
||||
exportTip: "Do you confirm to export the event data of the current query condition?",
|
||||
},
|
||||
},
|
||||
ne: {
|
||||
@@ -466,6 +502,8 @@ export default {
|
||||
portTip: "Network element port default:33030",
|
||||
serialNum: 'Serial Number',
|
||||
expiryDate: 'Expiry Date',
|
||||
ueNumber: 'UE Number',
|
||||
nbNumber: 'Radio Number',
|
||||
normalcy: 'Normal',
|
||||
exceptions: 'Abnormal',
|
||||
restart: 'Restart',
|
||||
@@ -613,8 +651,8 @@ export default {
|
||||
change: "Change License",
|
||||
reload: "Refresh Info",
|
||||
reloadTip: "Confirmed to refresh license information?",
|
||||
reloadBatch: "Batch Refresh",
|
||||
reloadBatchTip: "Do you do an information refresh on checked records?",
|
||||
reloadBatch: "Refresh License Status",
|
||||
reloadBatchTip: "Do you perform a license status information refresh for the current list of NE?",
|
||||
updateTtile: "Update License",
|
||||
downCodeTop: "Confirmed to save the license activation code to a file?",
|
||||
activationRequestCode: "License Activation Code",
|
||||
@@ -656,6 +694,19 @@ export default {
|
||||
name: "Name",
|
||||
downTip: 'Confirmed to download the backup file [{txt}]?',
|
||||
title: "Modify Backup {txt}",
|
||||
backupModal: {
|
||||
pushFileOper: "Send Current File To Remote Backup",
|
||||
title: "Setting Remote Backup Service",
|
||||
enable: "Enable",
|
||||
toIp: "Service IP",
|
||||
toIpPleace: "Please input the remote backup server IP address",
|
||||
toPort: "Service Port",
|
||||
username: "UserName",
|
||||
usernamePleace: 'Please enter the service login username',
|
||||
password: "Password",
|
||||
dir: "Save Dir",
|
||||
dirPleace: 'Please enter the service address target file directory',
|
||||
}
|
||||
},
|
||||
neQuickSetup: {
|
||||
reloadPara5G: 'Reload',
|
||||
@@ -707,10 +758,32 @@ export default {
|
||||
},
|
||||
},
|
||||
neData: {
|
||||
common: {
|
||||
startIMSI: 'Starting IMSI',
|
||||
imsi: 'IMSI',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
imsiTip1: 'MCC=Mobile Country Code, consisting of three digits.',
|
||||
imsiTip2: 'MNC = Mobile Network Number, consisting of two digits',
|
||||
imsiTip3: 'MSIN = Mobile Subscriber Identification Number, consisting of 10 equal digits.',
|
||||
imsiPlease: "Please enter IMSI correctly",
|
||||
msisdn: 'Mobile Customer Identification Number',
|
||||
msisdnPlease: "Please enter the Mobile Customer Identification Number correctly",
|
||||
loadDataConfirm: 'Confirmed to reload data?',
|
||||
loadData: 'Load Data',
|
||||
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
|
||||
batchOper: 'Batch Operation',
|
||||
batchAddText: 'Batch Addition',
|
||||
batchDelText: 'Batch Deletion',
|
||||
batchUpdateText: 'Batch Update',
|
||||
batchNum: 'Number of releases',
|
||||
checkDel:'Check Delete',
|
||||
importTemplate: 'Download Template',
|
||||
},
|
||||
baseStation: {
|
||||
list: "List",
|
||||
topology: "Topology",
|
||||
nbName: "RanNodeName",
|
||||
nbName: "RAN Node Name",
|
||||
nbId: "RAN Node ID",
|
||||
ueNum: "UE Number",
|
||||
topologyTitle: "Radio State Graph",
|
||||
name: "Name",
|
||||
@@ -729,10 +802,16 @@ export default {
|
||||
exportTip: "Confirm exporting xlsx table files based on search criteria?",
|
||||
importDataEmpty: "Imported data is empty",
|
||||
},
|
||||
backupData: {
|
||||
auth: "UDM Authentication",
|
||||
sub: "UDM Subscribers",
|
||||
voip: "VoIP Authentication",
|
||||
volte: "IMS Subscribers",
|
||||
}
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
authInfo:' Authentication Info',
|
||||
authInfo:' Authentication of Info',
|
||||
neTypePlease: 'Query network element Object',
|
||||
neType: 'UDM Object',
|
||||
export: 'Export',
|
||||
@@ -747,7 +826,7 @@ export default {
|
||||
startIMSI: 'Start IMSI',
|
||||
batchAddText: 'Batch Add',
|
||||
batchDelText: 'Batch Delete',
|
||||
numAdd: 'Number of releases',
|
||||
numAdd: 'Number of authentication profiles',
|
||||
numDel: 'Number of deleted',
|
||||
checkDel: 'Check Delete',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
@@ -760,10 +839,12 @@ export default {
|
||||
opcTip: 'The authentication key, OPC, is calculated from Ki and OP, OP is the root key of the operator, ki is the authentication key, and the maximum length is 32.',
|
||||
delSure:'Are you sure you want to delete the user with IMSI number: {imsi} ?',
|
||||
imsiConfirm:'The length of the IMSI must be 15',
|
||||
startUserName:'Start username',
|
||||
},
|
||||
sub: {
|
||||
subInfo:' Subscription Info',
|
||||
neType: 'UDM Object',
|
||||
imsType:'IMS Object',
|
||||
export: 'Export',
|
||||
exportConfirm: 'Are you sure to export all signed user data?',
|
||||
checkExport : 'Check Export',
|
||||
@@ -773,7 +854,7 @@ export default {
|
||||
loadDataConfirm: 'Are you sure you want to reload the data?',
|
||||
loadData: 'Load Data',
|
||||
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
|
||||
numAdd: 'Number of releases',
|
||||
numAdd: 'Number of subscriber profiles',
|
||||
numDel: 'Number of deleted',
|
||||
checkDel: 'Check Delete',
|
||||
batchAddText: 'Batch Add',
|
||||
@@ -793,6 +874,10 @@ export default {
|
||||
rfspTip:'RFSP index, in NG-RAN, the index of a specific RRM configuration, parameter between 0 and 127',
|
||||
ueTypeTip: 'Operator-defined subscriber UE Usage Type, integer, parameter between 0 and 127',
|
||||
cnFlag: 'Whether to enable 5G Core Network service',
|
||||
cnFlag0: 'No Access Allowed',
|
||||
cnFlag1: 'Access Only 5G',
|
||||
cnFlag2: 'Access Only 4G',
|
||||
cnFlag3: 'Access 4G/5G',
|
||||
epsFlagTip: 'Whether to enable 4G EPS service',
|
||||
contextIdTip: 'To sign up for an APN Context ID, you must select it from the APN Context list.',
|
||||
apnContextTip: 'The list of APNs available to the phone, up to six, is defined in the HSS.',
|
||||
@@ -802,6 +887,12 @@ export default {
|
||||
ardTip:'Access-Restriction-Data (Access-Restriction-Data), can be used to distinguish between 2G/3G/LTE users, to facilitate the coexistence of 2G/3G/LTE network for different types of users to distinguish between the service',
|
||||
smDataTip:'The IP in sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5: 1.2.3.4 is the static IP assigned to the APN of 5G user internet, and 1.2.3.5 is the static IP assigned to the APN of 5G user ims. If it is dynamic allocation, just remove the IP and the previous connector. Need to support multiple dnn uses & connections',
|
||||
smDataArrTip:'SST,DNN/APN is required',
|
||||
tenantName:'Tenant Name',
|
||||
imsiMode:'IMSI Matching Mode',
|
||||
fuzzyMatch:'Fuzzy Match',
|
||||
prefixMatch:'Prefix Match',
|
||||
fullMatch:'Full Match',
|
||||
suffixMatch:'Suffix Match',
|
||||
},
|
||||
pcf: {
|
||||
neType: 'PCF Object',
|
||||
@@ -835,7 +926,9 @@ export default {
|
||||
rfsfTip:'RAT Frequency Selection Priority',
|
||||
},
|
||||
base5G: {
|
||||
neType: 'NE Object',
|
||||
neType: 'Radio Type',
|
||||
gnb:'5G Radios',
|
||||
enb:'4G Radios',
|
||||
},
|
||||
n3iwf: {
|
||||
neType: 'N3IWF Object',
|
||||
@@ -947,8 +1040,13 @@ export default {
|
||||
expression:'Expression',
|
||||
description:' Description',
|
||||
kpiSet:' Statistical Settings',
|
||||
sixHoursAgo:'Six hours ago',
|
||||
threeHoursAgo:'Three hours ago.',
|
||||
ago1Hour:'Last 1 hour',
|
||||
ago3Hour:'Last 3 hour',
|
||||
ago6Hour:'Last 6 hour',
|
||||
toDay:'Today',
|
||||
ago1Day:'Last 1 day',
|
||||
ago7Day:'Last 7 day',
|
||||
ago15Day:'Last 15 day',
|
||||
delCustomTip:'Confirm deletion of data item with record Custom Indicator {num}?',
|
||||
delCustom:' Successfully delete record number {num} custom indicator',
|
||||
addCustom:' Add custom indicator',
|
||||
@@ -965,6 +1063,11 @@ export default {
|
||||
expressionErrorTip:'Please check the expression, the wrong indicator is {kpiId}',
|
||||
expressionNoIdTip:'Please check the expression, no valid indicator is found',
|
||||
unitSelect:'To better display the image, the same unit needs to be selected. The current unit is:',
|
||||
avg:'(average)',
|
||||
total:'(total)',
|
||||
ago1:'Past 24 hrs value',
|
||||
ago7:'Past 7 days value',
|
||||
ago30:'Past 30 days value',
|
||||
},
|
||||
kpiKeyTarget:{
|
||||
"time":"Time",
|
||||
@@ -996,6 +1099,22 @@ export default {
|
||||
"changeBar":"Change to Bar Charts",
|
||||
"chooseShowMetrics":"Select the metric you want to display",
|
||||
"chooseMetrics":"Select an indicator",
|
||||
"tips":"The percentages and rates are averages,the counts are statistical values.",
|
||||
},
|
||||
voiceOverView:{
|
||||
"voiceTitle":"Voice Calls Dashboard",
|
||||
"tips":"Voice Call Statistics Per Minute",
|
||||
"ne":"NE",
|
||||
"now":"Now",
|
||||
"last":"Past",
|
||||
"calls":"Calls",
|
||||
"activeCall":"Active Calls",
|
||||
"callMOMT":"MO/MT Call Success Rate",
|
||||
"failedcall":"Failed Calls",
|
||||
"registration":"Registrations",
|
||||
"activeregistration":"Active Registrations",
|
||||
"registrationsuccess":"Registration Success Rate",
|
||||
"failedregistration":"Failed Registrations",
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
@@ -1033,6 +1152,7 @@ export default {
|
||||
},
|
||||
task: {
|
||||
traceId: 'Tracing No',
|
||||
title: 'Tracing Title',
|
||||
trackType: 'Tracing Type',
|
||||
trackTypePlease: 'Please select a tracing type',
|
||||
creater: 'Created by',
|
||||
@@ -1097,7 +1217,7 @@ export default {
|
||||
clear: 'Clear',
|
||||
mySelf: 'Personalization',
|
||||
exportAll: 'Export All',
|
||||
disPlayFilfter: 'Display Filters',
|
||||
disPlayFilfter: 'Filter out Alerts',
|
||||
alarmId:'ID',
|
||||
alarmTitle:'Title',
|
||||
clearUser:'Clear User',
|
||||
@@ -1198,12 +1318,19 @@ export default {
|
||||
tailLines: 'End Lines',
|
||||
},
|
||||
exportFile:{
|
||||
fileName:'File Source',
|
||||
fileSource:'File Source',
|
||||
fileSourcePlease:'Please select the source of the document',
|
||||
downTip: "Confirm the download file name is [{fileName}] File?",
|
||||
downTipErr: "Failed to get file",
|
||||
deleteTip: "Confirm the delete file name is [{fileName}] File?",
|
||||
deleteTipErr: "Failed to delete file",
|
||||
selectTip:"Please select File Name",
|
||||
sysloginLog:'System Login Log',
|
||||
sysOperateLog:'System Operation Log',
|
||||
neLog:'NE Log',
|
||||
cdrIMS:'CDR Voice',
|
||||
cdrSMF:'CDR Data',
|
||||
cdrSMSC:'CDR SMS',
|
||||
cdrSGWC:'CDR Roaming Data',
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
@@ -1577,6 +1704,8 @@ export default {
|
||||
userName: 'Nick Name',
|
||||
permission: 'Role',
|
||||
className: 'Department',
|
||||
userType: 'User Type',
|
||||
tenntName:'Tenant Name',
|
||||
loginIp: 'Login Address',
|
||||
loginTime: 'Login Time',
|
||||
status: 'Status',
|
||||
@@ -1602,6 +1731,7 @@ export default {
|
||||
sex:'User Gender',
|
||||
email:'E-mail',
|
||||
fromClass:'Department',
|
||||
fromTenant:'Tenant',
|
||||
userWork:'User position',
|
||||
userWorkPlease: 'Please select user post',
|
||||
userTip:'User Description',
|
||||
@@ -1615,6 +1745,7 @@ export default {
|
||||
lock:'Lock',
|
||||
inactive:'Inactive',
|
||||
active:'Active',
|
||||
permissionTip:'If want to select a tenant role, make sure there is at least one tenant in the tenant management.',
|
||||
},
|
||||
config: {
|
||||
configName: "Config Name",
|
||||
@@ -1639,6 +1770,55 @@ export default {
|
||||
refreshCacheTip: "Are you sure you want to refresh the parameter configuration cache?",
|
||||
refreshCacheOk: "Refresh Cache Successful",
|
||||
},
|
||||
loginSource: {
|
||||
uid: "UID",
|
||||
name: "Name",
|
||||
namePlease: 'Please enter the authentication source name correctly',
|
||||
icon: "Icon",
|
||||
iconPlease: 'You can enter the image link or upload the image path address',
|
||||
type: "Type",
|
||||
activeFlag: "Status",
|
||||
remark: "Remark",
|
||||
createTime: "Create Time",
|
||||
updateTime: "Update Time",
|
||||
ldapUrl: "Server Address",
|
||||
ldapUrlPlease: 'Please enter the LDAP server address correctly',
|
||||
ldapBaseDN: "Base DN",
|
||||
baseDnPlease: 'Please enter the LDAP base DN correctly',
|
||||
ldapUserFilter: "User Filter",
|
||||
userFilterPlease: 'Please enter the LDAP user filter correctly',
|
||||
ldapBindDN: "Bind DN",
|
||||
ldapBindPassword: "Bind Password",
|
||||
smtpHost: 'Server Address',
|
||||
smtpHostPlease: 'Please enter the SMTP server address correctly',
|
||||
smtpPort: 'Port Number',
|
||||
smtpPortPlease: 'Please enter the SMTP port number correctly',
|
||||
oauth2ClientID: 'Client ID',
|
||||
oauth2ClientIDPlease: 'Please enter the OAuth2 client ID correctly',
|
||||
oauth2ClientSecret: 'Client Secret',
|
||||
oauth2ClientSecretPlease: 'Please enter the OAuth2 client secret correctly',
|
||||
oauth2AuthURL: 'Authorization URL',
|
||||
oauth2AuthURLPlease: 'Please enter the OAuth2 authorization URL correctly',
|
||||
oauth2TokenURL: 'Token URL',
|
||||
oauth2TokenURLPlease: 'Please enter the OAuth2 token URL correctly',
|
||||
oauth2UserURL: 'User Info URL',
|
||||
oauth2UserURLPlease: 'Please enter the OAuth2 user info URL correctly',
|
||||
oauth2AccountField: 'Account Field',
|
||||
oauth2AccountFieldPlease: 'Please enter the OAuth2 account field correctly',
|
||||
oauth2Scopes: 'Scopes',
|
||||
oauth2ScopesPlease: 'Please enter the OAuth2 scopes correctly',
|
||||
oauth2RedirectURL: 'Redirect URL',
|
||||
oauth2RedirectURLPlease: 'Please enter the OAuth2 redirect URL correctly',
|
||||
oauth2RedirectURLTip: 'Please jump to the specified path (omchost/#/login/oauth2), redirect with code and state address parameters',
|
||||
uploadFileTip: 'Confirm to upload the authentication source icon?',
|
||||
uploadFileOk: 'Authentication source icon upload successful',
|
||||
uploadFileErr: 'Authentication source icon upload failed',
|
||||
viewInfoErr: "Failed to get authentication source information",
|
||||
addInfo: "Add Authentication Source",
|
||||
editInfo: "Modify Authentication Source",
|
||||
delTip: "Confirm deleting the authentication source number [{num}] data item?",
|
||||
delOk: "Deleted Successfully",
|
||||
},
|
||||
setting: {
|
||||
charMaxLen: 'characters length',
|
||||
saveSubmit: 'Submit&Save',
|
||||
@@ -1698,6 +1878,10 @@ export default {
|
||||
home: 'Home Page',
|
||||
homeTip:'Do you want to submit the current interface as the system interface?',
|
||||
homeSet:'Home Page Settings',
|
||||
backup: 'System Backup',
|
||||
backupInstruction: 'System backup will back up the net element information records and configuration files running on the current system, and can restore the system to the previous state!',
|
||||
backupExportTip: 'Confirm to export system backup?',
|
||||
backupImportTip: 'Confirm to import system backup?',
|
||||
},
|
||||
role:{
|
||||
allScopeOptions:'All data permissions',
|
||||
@@ -1742,6 +1926,7 @@ export default {
|
||||
cancelGive:'Cancel authorization',
|
||||
cancelSure:'Confirm to cancel the authorization of the data item with user number [{userId}]?',
|
||||
batchCancel:'Batch cancellation of authorization',
|
||||
selectUser:'Assign Users',
|
||||
},
|
||||
dept:{
|
||||
classInfo:' Department Information',
|
||||
@@ -1762,6 +1947,37 @@ export default {
|
||||
phone:'Contact Number',
|
||||
email:'Mail',
|
||||
},
|
||||
tenant:{
|
||||
classInfo:' Tenant Information',
|
||||
className:'Name',
|
||||
classId:'Number',
|
||||
classSort:'Sorting',
|
||||
status:'Status',
|
||||
type:' Tenancy Asset',
|
||||
createTime:'Creation Time',
|
||||
highClass:'Root Level',
|
||||
key:'Asset Key',
|
||||
emailTip:'Please input the correct email address',
|
||||
phoneTip:'Please enter the correct phone number',
|
||||
node:'Root Node',
|
||||
delSure:'Are you sure to delete the data item with TenantName: {title}?',
|
||||
delTypeSure:'Are you sure to delete the data item with Tenancy Asset: {tenancyAsset}, Asset Key: {tenancyKey}?',
|
||||
open:'Exhibition',
|
||||
close:'Fold',
|
||||
addClass:'Add Tenant',
|
||||
admin:'Principal',
|
||||
phone:'Contact Number',
|
||||
email:'Mail',
|
||||
SDSST:'Network Slice',
|
||||
APN:'Access Point Name',
|
||||
IMSI:'SIM Card',
|
||||
treeSelectTip:'Please select a tenant from the tenant list on the left for configuration',
|
||||
upfTip:'Please select from the drop-down list of UPF Resource UIDs',
|
||||
imsiTip:'Please select the matching type and fill in the SIM card number fragment',
|
||||
radioTip:'Please select from the drop-down list of radio type and radio ID',
|
||||
defaultTip:'Please select from the drop-down list of Tenancy Asset, and then fill the Asset Key',
|
||||
patternTip:'The Asset Key cannot contain special characters',
|
||||
},
|
||||
post:{
|
||||
positionInfo:'Position Information',
|
||||
positionId:'Position Number',
|
||||
@@ -1784,7 +2000,7 @@ export default {
|
||||
requestMe:'Request Method',
|
||||
host:'Request Host',
|
||||
operStatus:'Status',
|
||||
operDate:'Time',
|
||||
operDate:'Time Stamp',
|
||||
useTime:'Time Lap',
|
||||
logInfo:'Operation Log Information',
|
||||
delSure:'Are you sure to delete the data item with access number [{ids}]?',
|
||||
|
||||
@@ -10,6 +10,8 @@ export default {
|
||||
desc: '核心网管理平台',
|
||||
loading: '请稍等...',
|
||||
inputPlease: '请输入',
|
||||
searchPlease: '搜索菜单...',
|
||||
searchTip: '输入关键词搜索菜单',
|
||||
selectPlease: '请选择',
|
||||
tipTitle: '提示',
|
||||
msgSuccess: '{msg} 成功',
|
||||
@@ -28,6 +30,7 @@ export default {
|
||||
editText: '编辑',
|
||||
deleteText: '删除',
|
||||
downloadText: '下载',
|
||||
CloudServerText:'同步',
|
||||
import:'导入',
|
||||
export:'导出',
|
||||
uploadText: '上传',
|
||||
@@ -38,6 +41,10 @@ export default {
|
||||
columnSetText: '列设置',
|
||||
columnSetTitle: '列展示/排序',
|
||||
sizeText: '密度',
|
||||
exportCustom:'自定义导出',
|
||||
exportColumns:'列定义',
|
||||
resetToDefault:'重置为默认列',
|
||||
exportDefault:'全部导出',
|
||||
size: {
|
||||
default: '默认',
|
||||
middle: '中等',
|
||||
@@ -142,7 +149,7 @@ export default {
|
||||
|
||||
// 静态路由
|
||||
router: {
|
||||
index: "主页",
|
||||
index: "概览",
|
||||
login: "登录",
|
||||
register: '注册',
|
||||
page403: '没有访问权限',
|
||||
@@ -256,9 +263,13 @@ export default {
|
||||
registerBtn: '注册账号',
|
||||
loginBtn: '登录',
|
||||
loginSuccess: '登录成功',
|
||||
loginMethod: '其他登录方式:',
|
||||
loginMethodWX: '微信扫一扫登录',
|
||||
loginMethodQQ: 'QQ扫码登录',
|
||||
otherMethod: '其他方式',
|
||||
backBtn: '返回',
|
||||
backBtnLogin: '返回登录',
|
||||
authorizedNotfound: '授权无效',
|
||||
authorizedFailed: '授权失败',
|
||||
authorizedSuccess: '授权成功',
|
||||
redirectHome: '{i} 秒后跳转主页',
|
||||
},
|
||||
register: {
|
||||
registerBtn: '注册',
|
||||
@@ -354,16 +365,34 @@ export default {
|
||||
title: "核心网系统看板",
|
||||
fullscreen: "点击全屏显示",
|
||||
toRouter: "点击跳转详情页面",
|
||||
Users:"UDM 订阅数据",
|
||||
VoNR:"IMS在线用户",
|
||||
sessions:"SMF的PDU会话",
|
||||
FivegNodeN:"5G在线基站数",
|
||||
Fiveusers:"AMF在线用户数",
|
||||
FourgNodeN:"4G在线基站数",
|
||||
Fourusers:"MME在线用户数",
|
||||
UPFjump:"点击跳转到UPF黄金指标界面",
|
||||
Networkjump:"点击跳转到网元系统拓扑图",
|
||||
Alarmjump:"点击跳转到活动告警",
|
||||
IMSUsers:"点击跳转到 IMS 用户",
|
||||
VoIPUsers:"点击跳转到 VoIP 用户",
|
||||
skim: {
|
||||
users: "用户数",
|
||||
userTitle:'用户信息',
|
||||
imsUeNum: "IMS 会话数",
|
||||
smfUeNum: "Data 会话数",
|
||||
gnbBase: "5G 基站数",
|
||||
gnbSumBase: "5G 基站总数",
|
||||
gnbUeNum:'5G 用户数',
|
||||
enbBase: "4G 基站数",
|
||||
enbSumBase: "4G 基站总数",
|
||||
enbUeNum:'4G 用户数',
|
||||
baseTitle:'在线信息',
|
||||
nodeBInfo: '基站信息',
|
||||
onlineinfo:'User Online Infomation',
|
||||
ims:'IMS',
|
||||
voip:'VoIP'
|
||||
},
|
||||
upfFlow:{
|
||||
title: "用户面吞吐量",
|
||||
@@ -393,6 +422,7 @@ export default {
|
||||
},
|
||||
userActivity: {
|
||||
title: "用户活动",
|
||||
imsTitle: "IMS 活动",
|
||||
type: "类型",
|
||||
duration: "时长",
|
||||
caller: "主叫",
|
||||
@@ -411,15 +441,20 @@ export default {
|
||||
rowInfo: "记录信息",
|
||||
type: "记录类型",
|
||||
duration: "通话时长",
|
||||
mosAverage: "MOS",
|
||||
callConnectionTime: "Call Connection Time",
|
||||
seizureTime: "呼叫开始时间",
|
||||
releaseTime: "挂断结束时间",
|
||||
caller: "主叫",
|
||||
called: "被叫",
|
||||
result: "结果",
|
||||
resultCode: "结果码",
|
||||
resultCause: "结果原因",
|
||||
resultOk: "成功",
|
||||
resultFail: "失败",
|
||||
delTip: "确认删除编号为【{msg}】的数据项?",
|
||||
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)",
|
||||
tenantName: "租户名称",
|
||||
exportTip: "确认导出当前查询条件的话单数据吗?",
|
||||
chargingID: '计费ID',
|
||||
smfSubscriptionIDData: '订阅 ID 数据',
|
||||
smfSubscriptionIDType: '订阅 ID 类型',
|
||||
@@ -443,7 +478,7 @@ export default {
|
||||
result: "结果",
|
||||
resultOk: "成功",
|
||||
delTip: "确认删除编号为【{msg}】的数据项?",
|
||||
exportTip: "确认导出当前查询条件的事件数据吗?(导出最大支持一万条)",
|
||||
exportTip: "确认导出当前查询条件的事件数据吗?",
|
||||
},
|
||||
},
|
||||
ne: {
|
||||
@@ -466,6 +501,8 @@ export default {
|
||||
portTip: "网元服务端口,默认:33030",
|
||||
serialNum: '序列号',
|
||||
expiryDate: '许可证到期日期',
|
||||
ueNumber: '用户数',
|
||||
nbNumber: '基站数',
|
||||
normalcy: '正常',
|
||||
exceptions: '异常',
|
||||
restart: '重启',
|
||||
@@ -613,8 +650,8 @@ export default {
|
||||
change: "变更许可证",
|
||||
reload: "刷新信息",
|
||||
reloadTip: "确认要刷新许可证信息吗?",
|
||||
reloadBatch: "批量刷新",
|
||||
reloadBatchTip: "对勾选的记录进行信息刷新吗?",
|
||||
reloadBatch: "刷新许可证状态",
|
||||
reloadBatchTip: "对当前列表网元进行许可证状态信息刷新吗?",
|
||||
updateTtile: "更新许可证",
|
||||
downCodeTop: "确认要将许可激活码保存到文件吗?",
|
||||
activationRequestCode: "许可激活码",
|
||||
@@ -656,6 +693,19 @@ export default {
|
||||
name: "名称",
|
||||
downTip: '确认要下载备份文件【{txt}】吗?',
|
||||
title: "修改备份信息 {txt}",
|
||||
backupModal: {
|
||||
pushFileOper: "将当前文件发送到远程备份",
|
||||
title: "设置远程备份服务",
|
||||
enable: "启用",
|
||||
toIp: "服务IP",
|
||||
toIpPleace: "请输入远程备份服务器 IP 地址",
|
||||
toPort: "服务端口",
|
||||
username: "登录用户名",
|
||||
usernamePleace: '请输入服务登录用户名',
|
||||
password: "登录密码",
|
||||
dir: "保存目录",
|
||||
dirPleace: '请输入服务地址目标文件目录',
|
||||
}
|
||||
},
|
||||
neQuickSetup: {
|
||||
reloadPara5G: '刷新',
|
||||
@@ -707,10 +757,32 @@ export default {
|
||||
},
|
||||
},
|
||||
neData: {
|
||||
common: {
|
||||
startIMSI: '起始IMSI',
|
||||
imsi: 'IMSI',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
imsiTip1: 'MCC=移动国家号码, 由三位数字组成',
|
||||
imsiTip2: 'MNC=移动网络号,由两位数字组成',
|
||||
imsiTip3: 'MSIN=移动客户识别码,采用等长10位数字构成',
|
||||
imsiPlease: "请正确输入IMSI",
|
||||
msisdn: '移动客户识别码',
|
||||
msisdnPlease: "请正确输入移动客户识别码",
|
||||
loadDataConfirm: '确认要重新加载数据吗?',
|
||||
loadData: '加载数据',
|
||||
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
|
||||
batchOper: '批量操作',
|
||||
batchAddText: '批量新增',
|
||||
batchDelText: '批量删除',
|
||||
batchUpdateText: '批量更新',
|
||||
batchNum: '批量个数',
|
||||
checkDel:'勾选删除',
|
||||
importTemplate: '导入模板',
|
||||
},
|
||||
baseStation: {
|
||||
list: "列表",
|
||||
topology: "拓扑图",
|
||||
nbName: "设备名称",
|
||||
nbId: "设备ID",
|
||||
ueNum: "在线用户数",
|
||||
topologyTitle: "基站状态关系图",
|
||||
name: "基站名称",
|
||||
@@ -729,6 +801,12 @@ export default {
|
||||
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
|
||||
importDataEmpty: "导入数据为空",
|
||||
},
|
||||
backupData: {
|
||||
auth: "UDM鉴权用户",
|
||||
sub: "UDM签约用户",
|
||||
voip: "VOIP鉴权用户",
|
||||
volte: "IMS签约用户",
|
||||
}
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
@@ -760,10 +838,12 @@ export default {
|
||||
opcTip: '鉴权秘钥,OPC是由Ki和OP经过计算得来的,OP为运营商的根秘钥,ki是鉴权秘钥,最大长度为32',
|
||||
delSure:'确认删除IMSI编号为: {imsi} 的用户吗?',
|
||||
imsiConfirm:'IMSI的长度必须为15',
|
||||
startUserName:'起始用户名',
|
||||
},
|
||||
sub: {
|
||||
subInfo:'签约信息',
|
||||
neType: 'UDM网元类型',
|
||||
imsType:'IMS网元类型',
|
||||
export: '导出',
|
||||
exportConfirm: '确认导出全部签约用户数据吗?',
|
||||
checkExport : '勾选导出',
|
||||
@@ -793,6 +873,10 @@ export default {
|
||||
rfspTip:'RFSP 索引,在 NG-RAN 中,特定 RRM 配置的索引,参数介于0到127之间',
|
||||
ueTypeTip: '运营商定义的用户 UE Usage Type,整型,参数介于0到127之间',
|
||||
cnFlag: '是否开启 5G Core Network 服务',
|
||||
cnFlag0: '不允许接入',
|
||||
cnFlag1: '只能接入 5G',
|
||||
cnFlag2: '只能接入 4G',
|
||||
cnFlag3: '允许接入 4G/5G',
|
||||
epsFlagTip: '是否开启 4G EPS 服务',
|
||||
contextIdTip: '签约APN 上下文ID,必须从APN Context list 中选择。',
|
||||
apnContextTip: '手机可用的APN列表,最多六个,在HSS中定义。',
|
||||
@@ -802,6 +886,12 @@ export default {
|
||||
ardTip:'接入控制标志(Access-Restriction-Data),可用于区分2G/3G/LTE用户,便于为2G/3G/LTE网络共存时,对不同类型用户进行区分服务',
|
||||
smDataTip:'sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5中的IP:1.2.3.4为5G用户internet这个APN分配的静态IP,1.2.3.5为5G用户ims这个APN分配的静态IP。如果是动态分配,把IP以及前面一个连接符去掉即可。需支持多个dnn用&连接',
|
||||
smDataArrTip:'SST,DNN/APN为必填项',
|
||||
tenantName:'租户名称',
|
||||
imsiMode:'IMSI匹配模式',
|
||||
fuzzyMatch:'模糊匹配',
|
||||
prefixMatch:'前缀匹配',
|
||||
fullMatch:'全匹配',
|
||||
suffixMatch:'后缀匹配',
|
||||
},
|
||||
pcf: {
|
||||
neType: 'PCF网元对象',
|
||||
@@ -836,6 +926,8 @@ export default {
|
||||
},
|
||||
base5G: {
|
||||
neType: '网元对象',
|
||||
gnb:'5G 基站',
|
||||
enb:'4G 基站',
|
||||
},
|
||||
n3iwf: {
|
||||
neType: 'N3IWF网元对象',
|
||||
@@ -848,7 +940,7 @@ export default {
|
||||
},
|
||||
nssf:{
|
||||
neType: 'NSSF网元对象',
|
||||
},
|
||||
}
|
||||
},
|
||||
perfManage: {
|
||||
taskManage:{
|
||||
@@ -947,8 +1039,13 @@ export default {
|
||||
expression:'计算公式',
|
||||
description:'描述',
|
||||
kpiSet:'统计设置',
|
||||
sixHoursAgo:'6小时前',
|
||||
threeHoursAgo:'3小时前',
|
||||
ago1Hour:'过去1小时',
|
||||
ago3Hour:'过去3小时',
|
||||
ago6Hour:'过去6小时',
|
||||
toDay:'今天',
|
||||
ago1Day:'过去1天',
|
||||
ago7Day:'过去7天',
|
||||
ago15Day:'过去15天',
|
||||
delCustomTip:'确认删除自定义指标项为 {num} 的数据项?',
|
||||
delCustom:'成功删除记录编号为 {num} 自定义指标',
|
||||
addCustom:'添加自定义指标',
|
||||
@@ -965,6 +1062,11 @@ export default {
|
||||
expressionErrorTip:'请检查表达式,错误的指标为{kpiId}',
|
||||
expressionNoIdTip:'请检查表达式,没有找到任何有效的指标',
|
||||
unitSelect:'为更好展示图需选择相同单位,当前单位为:',
|
||||
avg:'(平均)',
|
||||
total:'(累计)',
|
||||
ago1:'近1天值',
|
||||
ago7:'近7天值',
|
||||
ago30:'近30天值',
|
||||
},
|
||||
kpiKeyTarget:{
|
||||
"time":"时间",
|
||||
@@ -996,6 +1098,22 @@ export default {
|
||||
"changeBar":"切换为柱状图",
|
||||
"chooseShowMetrics":"选择需要显示的指标",
|
||||
"chooseMetrics":"选择指标",
|
||||
"tips":"百分比和比率是平均值,计数是统计值。",
|
||||
},
|
||||
voiceOverView:{
|
||||
"voiceTitle":"语音通话仪表盘",
|
||||
"tips":"每分钟数据语音统计",
|
||||
"ne":"网元",
|
||||
"now":"现在",
|
||||
"last":"过去",
|
||||
"calls":"呼叫",
|
||||
"activeCall":"正在通话",
|
||||
"callMOMT":"呼叫 主叫接通率/被叫接通率",
|
||||
"failedcall":"失败呼叫",
|
||||
"registration":"注册",
|
||||
"activeregistration":"主动注册",
|
||||
"registrationsuccess":"注册成功率",
|
||||
"failedregistration":"失败注册",
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
@@ -1033,6 +1151,7 @@ export default {
|
||||
},
|
||||
task: {
|
||||
traceId: '跟踪编号',
|
||||
title: '跟踪标题',
|
||||
trackType: '跟踪类型',
|
||||
trackTypePlease: '请选择跟踪类型',
|
||||
creater: '创建人',
|
||||
@@ -1198,12 +1317,19 @@ export default {
|
||||
tailLines: '末尾行数',
|
||||
},
|
||||
exportFile:{
|
||||
fileName:'文件来源',
|
||||
fileSource:'文件来源',
|
||||
fileSourcePlease:'请选择文件来源',
|
||||
downTip: "确认下载文件名为 【{fileName}】 文件?",
|
||||
downTipErr: "文件获取失败",
|
||||
deleteTip: "确认删除文件名为 【{fileName}】 文件?",
|
||||
deleteTipErr: "文件删除失败",
|
||||
selectTip:"请选择文件名",
|
||||
sysloginLog:'系统登录日志',
|
||||
sysOperateLog:'系统操作日志',
|
||||
neLog:'网元日志',
|
||||
cdrIMS:'语音话单',
|
||||
cdrSMF:'数据话单',
|
||||
cdrSMSC:'短信话单',
|
||||
cdrSGWC:'漫游数据话单',
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
@@ -1575,8 +1701,10 @@ export default {
|
||||
userNum: '用户编号',
|
||||
account: '登录账号',
|
||||
userName: '用户昵称',
|
||||
permission: '用户权限',
|
||||
permission: '用户角色',
|
||||
className: '部门名称',
|
||||
userType: '用户类型',
|
||||
tenntName:'租户名称',
|
||||
loginIp: '登录地址',
|
||||
loginTime: '登录时间',
|
||||
status: '用户状态',
|
||||
@@ -1602,6 +1730,7 @@ export default {
|
||||
sex:'用户性别',
|
||||
email:'电子邮箱',
|
||||
fromClass:'所属部门',
|
||||
fromTenant:'所属租户',
|
||||
userWork:'用户岗位',
|
||||
userWorkPlease: '请选择用户岗位',
|
||||
userTip:'用户说明',
|
||||
@@ -1615,6 +1744,7 @@ export default {
|
||||
lock:'锁定',
|
||||
inactive:'未激活',
|
||||
active:'激活',
|
||||
permissionTip:'若要选择租户角色,需保证起码租户管理中有一个租户',
|
||||
},
|
||||
config: {
|
||||
configName: "参数名称",
|
||||
@@ -1639,6 +1769,55 @@ export default {
|
||||
refreshCacheTip: "确定要刷新参数配置缓存吗?",
|
||||
refreshCacheOk: "刷新缓存成功",
|
||||
},
|
||||
loginSource: {
|
||||
uid: "唯一标识",
|
||||
name: "名称",
|
||||
namePlease: '请正确输入认证源名称',
|
||||
icon: "图标",
|
||||
iconPlease: '可填入图片链接或上传图片路径地址',
|
||||
type: "类型",
|
||||
activeFlag: "状态",
|
||||
remark: "备注说明",
|
||||
createTime: "创建时间",
|
||||
updateTime: "更新时间",
|
||||
ldapUrl: "服务器地址",
|
||||
ldapUrlPlease: '请正确输入LDAP 服务器地址',
|
||||
ldapBaseDN: "基础DN",
|
||||
baseDnPlease: '请正确输入LDAP 基础DN',
|
||||
ldapUserFilter: "用户过滤",
|
||||
userFilterPlease: '请正确输入LDAP 用户过滤',
|
||||
ldapBindDN: "绑定DN",
|
||||
ldapBindPassword: "绑定密码",
|
||||
smtpHost: '服务器地址',
|
||||
smtpHostPlease: '请正确输入SMTP 服务器地址',
|
||||
smtpPort: '端口号',
|
||||
smtpPortPlease: '请正确输入SMTP 端口号',
|
||||
oauth2ClientID: '客户端ID',
|
||||
oauth2ClientIDPlease: '请正确输入OAuth2 客户端ID',
|
||||
oauth2ClientSecret: '客户端密钥',
|
||||
oauth2ClientSecretPlease: '请正确输入OAuth2 客户端密钥',
|
||||
oauth2AuthURL: '授权URL',
|
||||
oauth2AuthURLPlease: '请正确输入OAuth2 授权URL',
|
||||
oauth2TokenURL: '令牌URL',
|
||||
oauth2TokenURLPlease: '请正确输入OAuth2 令牌URL',
|
||||
oauth2UserURL: '用户信息URL',
|
||||
oauth2UserURLPlease: '请正确输入OAuth2 用户信息URL',
|
||||
oauth2AccountField: '账号字段',
|
||||
oauth2AccountFieldPlease: '请正确输入OAuth2 账号字段',
|
||||
oauth2Scopes: '作用域',
|
||||
oauth2ScopesPlease: '请正确输入OAuth2 作用域',
|
||||
oauth2RedirectURL: '重定向URL',
|
||||
oauth2RedirectURLPlease: '请正确输入OAuth2 重定向URL',
|
||||
oauth2RedirectURLTip: '请跳转指定路径(omchost/#/login/oauth2), 重定向携带code和state地址参数',
|
||||
uploadFileTip: '确认要上传认证源图标吗?',
|
||||
uploadFileOk: '认证源图标上传成功',
|
||||
uploadFileErr: '认证源图标上传失败',
|
||||
viewInfoErr: "获取认证源信息失败",
|
||||
addInfo: "添加认证源",
|
||||
editInfo: "修改认证源",
|
||||
delTip: "确认删除认证源编号为 【{num}】 的数据项?",
|
||||
delOk: "删除成功",
|
||||
},
|
||||
setting: {
|
||||
charMaxLen: '位字符长度',
|
||||
saveSubmit: '提交保存',
|
||||
@@ -1698,6 +1877,10 @@ export default {
|
||||
home: '系统首页',
|
||||
homeTip:'确认要提交当前界面为系统界面吗?',
|
||||
homeSet:'系统首页设置',
|
||||
backup: '系统备份',
|
||||
backupInstruction: '系统备份将会对当前系统上运行的网元信息记录及配置文件进行备份,可进行系统的恢复操作!',
|
||||
backupExportTip: '确认要导出系统备份吗?',
|
||||
backupImportTip: '确认要导入系统备份吗?',
|
||||
},
|
||||
role:{
|
||||
allScopeOptions:'全部数据权限',
|
||||
@@ -1742,6 +1925,7 @@ export default {
|
||||
cancelGive:'取消授权',
|
||||
cancelSure:'确认取消用户编号为 【{userId}】 的数据项授权?',
|
||||
batchCancel:'批量取消授权',
|
||||
selectUser:'分配用户',
|
||||
},
|
||||
dept:{
|
||||
classInfo:'部门信息',
|
||||
@@ -1762,6 +1946,37 @@ export default {
|
||||
phone:'联系电话',
|
||||
email:'邮箱',
|
||||
},
|
||||
tenant:{
|
||||
classInfo:'租户信息',
|
||||
className:'名称',
|
||||
classId:'编号',
|
||||
classSort:'排序',
|
||||
status:'状态',
|
||||
type:'租赁类型',
|
||||
createTime:'创建时间',
|
||||
highClass:'根级',
|
||||
key:'租赁标识',
|
||||
emailTip:'请输入正确的邮箱地址',
|
||||
phoneTip:'请输入正确的手机号码',
|
||||
node:'根节点',
|
||||
delSure:'确认删除租户名为 【{title}】 的数据项?',
|
||||
delTypeSure:'确认删除租赁类型为 【{tenancyAsset}】,租赁标识为【{tenancyKey}】 的数据项?',
|
||||
open:'展',
|
||||
close:'折',
|
||||
addClass:'新增租户',
|
||||
admin:'负责人',
|
||||
phone:'联系电话',
|
||||
email:'邮箱',
|
||||
SDSST:'网络切片',
|
||||
APN:'接入点',
|
||||
IMSI:'SIM卡',
|
||||
treeSelectTip:'请从左侧租户列表中选择租户进行配置',
|
||||
upfTip:'请选择下拉列表UPF的资源唯一标识',
|
||||
imsiTip:'请选择匹配类型和填入SIM卡号段',
|
||||
radioTip:'请从下拉列表选择基站类型和基站ID',
|
||||
defaultTip:'请选择租赁资产类别, 然后填写资产标识',
|
||||
patternTip:'租赁标识不能包含特殊字符',
|
||||
},
|
||||
post:{
|
||||
positionInfo:'岗位信息',
|
||||
positionId:'岗位编号',
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import { useRouter } from 'vue-router';
|
||||
import useLayoutStore from '@/store/modules/layout';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useRouterStore from '@/store/modules/router';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
@@ -27,7 +28,7 @@ import useAlarmStore from '@/store/modules/alarm';
|
||||
import { getServerTime } from '@/api';
|
||||
import { MENU_PATH_INLINE } from '@/constants/menu-constants';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { parseDateToStr, YYYY_MM_DD_HH_MM_SSZ } from '@/utils/date-utils';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
const { proConfig, waterMarkContent } = useLayoutStore();
|
||||
const { t, currentLocale } = useI18n();
|
||||
@@ -64,19 +65,28 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 动态路由添加到菜单面板
|
||||
const rootRoute = router.getRoutes().find(r => r.name === 'Root');
|
||||
if (rootRoute) {
|
||||
const children = routerStore.setRootRouterData(rootRoute.children);
|
||||
const buildRouterData = routerStore.buildRouterData;
|
||||
if (buildRouterData.length > 0) {
|
||||
rootRoute.children = children.concat(buildRouterData);
|
||||
} else {
|
||||
rootRoute.children = children;
|
||||
const menuData = computed(() => {
|
||||
// 动态路由添加到菜单面板
|
||||
const rootRoute = router.getRoutes().find(r => r.name === 'Root');
|
||||
if (rootRoute) {
|
||||
const children = routerStore.setRootRouterData(rootRoute.children);
|
||||
const buildRouterData = routerStore.buildRouterData;
|
||||
if (buildRouterData.length > 0) {
|
||||
rootRoute.children = children.concat(buildRouterData);
|
||||
} else {
|
||||
rootRoute.children = children;
|
||||
}
|
||||
// console.log(JSON.parse(JSON.stringify(rootRoute.children)));
|
||||
if (!useUserStore().roles.includes('tenant')) {
|
||||
rootRoute.children = rootRoute.children.filter(
|
||||
item => item.name !== 'Index'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
|
||||
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
|
||||
return menuData;
|
||||
});
|
||||
|
||||
/**面包屑数据对象,排除根节点和首页不显示 */
|
||||
const breadcrumb = computed(() => {
|
||||
@@ -198,6 +208,7 @@ let serverTime = reactive({
|
||||
zone: 'UTC', // 时区 UTC
|
||||
interval: null as any, // 定时器
|
||||
});
|
||||
let activeAlarmRefresh = 0;
|
||||
|
||||
// 获取服务器时间
|
||||
function fnGetServerTime() {
|
||||
@@ -211,10 +222,18 @@ function fnGetServerTime() {
|
||||
serverTime.timestamp = parseInt(res.data.timestamp);
|
||||
serverTime.interval = setInterval(() => {
|
||||
serverTime.timestamp += 1000;
|
||||
activeAlarmRefresh += 1;
|
||||
// serverTimeStr.value = parseDateToStr(serverTime.timestamp);
|
||||
// 用DOM直接修改
|
||||
if (serverTimeDom) {
|
||||
serverTimeDom.innerText = parseDateToStr(serverTime.timestamp);
|
||||
serverTimeDom.innerText = parseDateToStr(
|
||||
serverTime.timestamp,
|
||||
YYYY_MM_DD_HH_MM_SSZ
|
||||
);
|
||||
}
|
||||
if (activeAlarmRefresh === 5) {
|
||||
useAlarmStore().fnGetActiveAlarmInfo();
|
||||
activeAlarmRefresh = 0;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
@@ -255,6 +274,7 @@ onUnmounted(() => {
|
||||
v-bind="proConfig"
|
||||
:iconfont-url="scriptUrl"
|
||||
:locale="fnLocale"
|
||||
:sider-width="256"
|
||||
>
|
||||
<!--插槽-菜单头-->
|
||||
<template #menuHeaderRender>
|
||||
|
||||
@@ -4,6 +4,7 @@ import svgDark from '@/assets/svg/dark.svg';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { viewTransitionTheme } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import SearchMenu from './SearchMenu.vue';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
@@ -14,6 +15,9 @@ import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useAlarmStore from '@/store/modules/alarm';
|
||||
import useMaskStore from '@/store/modules/mask';
|
||||
import { dbClear } from '@/utils/cache-db-utils';
|
||||
import { CACHE_DB_TABLE_DND } from '@/constants/cache-keys-constants';
|
||||
import { TENANTADMIN_ROLE_KEY } from '@/constants/admin-constants';
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
const { t, changeLocale, optionsLocale } = useI18n();
|
||||
const layoutStore = useLayoutStore();
|
||||
@@ -33,7 +37,10 @@ function fnClick({ key }: MenuInfo) {
|
||||
router.push({ name: 'Profile' });
|
||||
break;
|
||||
case 'logout':
|
||||
userStore.fnLogOut().finally(() => router.push({ name: 'Login' }));
|
||||
userStore.fnLogOut().finally(() => {
|
||||
dbClear(CACHE_DB_TABLE_DND);
|
||||
router.push({ name: 'Login' });
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -78,21 +85,33 @@ function fnChangeLocale(e: any) {
|
||||
|
||||
<template>
|
||||
<a-space :size="12" align="center">
|
||||
<a-tooltip placement="bottomRight">
|
||||
<template #title>{{ t('loayouts.rightContent.alarm') }}</template>
|
||||
<a-button type="text" style="color: inherit" @click="fnClickAlarm">
|
||||
<template #icon>
|
||||
<a-badge
|
||||
:count="useAlarmStore().activeAlarmTotal"
|
||||
:overflow-count="99"
|
||||
status="warning"
|
||||
style="color: inherit"
|
||||
>
|
||||
<BellOutlined />
|
||||
</a-badge>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 搜索功能 -->
|
||||
<span >
|
||||
<SearchMenu />
|
||||
</span>
|
||||
|
||||
<span v-roles:has="[TENANTADMIN_ROLE_KEY]">
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ t('loayouts.rightContent.alarm') }}</template>
|
||||
<a-button
|
||||
type="text"
|
||||
style="color: inherit"
|
||||
@click="fnClickAlarm"
|
||||
v-perms:has="['faultManage:active-alarm:index']"
|
||||
>
|
||||
<template #icon>
|
||||
<a-badge
|
||||
:count="useAlarmStore().activeAlarmTotal"
|
||||
:overflow-count="99"
|
||||
status="warning"
|
||||
style="color: inherit"
|
||||
>
|
||||
<BellOutlined />
|
||||
</a-badge>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
|
||||
<!-- 锁屏操作 -->
|
||||
<span v-perms:has="['system:setting:lock']">
|
||||
|
||||
380
src/layouts/components/SearchMenu.vue
Normal file
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<!-- 搜索按钮 -->
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ t('common.search') }}</template>
|
||||
<a-button type="text" style="color: inherit" @click="fnClickSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<!-- 搜索弹窗 -->
|
||||
<ProModal
|
||||
:drag="false"
|
||||
:center-y="true"
|
||||
:width="600"
|
||||
:minHeight="400"
|
||||
:mask-closable="true"
|
||||
v-model:open="searchModalOpen"
|
||||
:title="t('common.search')"
|
||||
:footer="null"
|
||||
@cancel="fnCloseSearch"
|
||||
>
|
||||
<div class="search-modal-content">
|
||||
<a-input
|
||||
ref="searchInputRef"
|
||||
v-model:value="searchKeyword"
|
||||
:placeholder="t('common.searchPlease')"
|
||||
size="large"
|
||||
style="margin-bottom: 16px"
|
||||
@keydown="fnHandleKeydown"
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined style="color: #bfbfbf" />
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<div class="search-results">
|
||||
<div v-if="filteredMenus.length === 0" class="no-results">
|
||||
<a-empty
|
||||
:description="
|
||||
searchKeyword ? t('common.noData') : t('common.searchTip')
|
||||
"
|
||||
:image="false"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="menu-list">
|
||||
<div
|
||||
v-for="menu in filteredMenus"
|
||||
:key="menu.key"
|
||||
class="menu-item"
|
||||
@click="fnSelectMenu(menu)"
|
||||
>
|
||||
<div class="menu-icon">
|
||||
<!-- 处理自定义图标字体 -->
|
||||
<IconFont
|
||||
v-if="menu.icon && menu.icon.startsWith('icon-')"
|
||||
:type="menu.icon"
|
||||
class="icon"
|
||||
/>
|
||||
<!-- 处理Ant Design图标组件 -->
|
||||
<component
|
||||
:is="menu.icon"
|
||||
v-else-if="menu.icon && !menu.icon.startsWith('icon-')"
|
||||
class="icon"
|
||||
/>
|
||||
<!-- 默认图标 -->
|
||||
<FolderOutlined v-else class="icon" />
|
||||
</div>
|
||||
<div class="menu-info">
|
||||
<div class="menu-title">{{ menu.title }}</div>
|
||||
</div>
|
||||
<div class="menu-action">
|
||||
<RightOutlined />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ProModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getMenuData, clearMenuItem } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import IconFont from '@/components/IconFont/index.vue';
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useRouterStore from '@/store/modules/router';
|
||||
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
const routerStore = useRouterStore();
|
||||
const router = useRouter();
|
||||
|
||||
// 搜索相关状态
|
||||
const searchModalOpen = ref<boolean>(false);
|
||||
const searchKeyword = ref<string>('');
|
||||
const searchInputRef = ref();
|
||||
|
||||
// 获取所有可搜索的菜单项
|
||||
const searchableMenus = computed(() => {
|
||||
const menus: Array<{
|
||||
title: string;
|
||||
path: string;
|
||||
icon?: string;
|
||||
key: string;
|
||||
routeName?: string;
|
||||
}> = [];
|
||||
|
||||
// 使用和BasicLayout完全相同的菜单数据获取逻辑
|
||||
const getMenuDataForSearch = () => {
|
||||
// 动态路由添加到菜单面板
|
||||
const rootRoute = router.getRoutes().find(r => r.name === 'Root');
|
||||
if (rootRoute) {
|
||||
const children = routerStore.setRootRouterData(rootRoute.children);
|
||||
const buildRouterData = routerStore.buildRouterData;
|
||||
if (buildRouterData.length > 0) {
|
||||
rootRoute.children = children.concat(buildRouterData);
|
||||
} else {
|
||||
rootRoute.children = children;
|
||||
}
|
||||
|
||||
// 根据用户角色过滤
|
||||
if (!userStore.roles.includes('tenant')) {
|
||||
rootRoute.children = rootRoute.children.filter(
|
||||
item => item.name !== 'Index'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用和BasicLayout相同的处理方法
|
||||
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
|
||||
return menuData;
|
||||
};
|
||||
|
||||
// 递归获取所有路由项
|
||||
const getRouteItems = (routes: any[], parentPath = '') => {
|
||||
routes.forEach((route: any) => {
|
||||
// 跳过根路径和不显示的菜单
|
||||
if (route.path === '/' || route.path === '' || route.meta?.hideInMenu) {
|
||||
// 继续处理子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
getRouteItems(route.children, route.path === '/' ? '' : route.path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建完整路径
|
||||
let fullPath = route.path;
|
||||
if (!route.path.startsWith('/') && parentPath) {
|
||||
fullPath = `${parentPath}/${route.path}`;
|
||||
}
|
||||
|
||||
// 只添加有meta.title的路由项
|
||||
if (route.meta && route.meta.title) {
|
||||
try {
|
||||
const title = t(route.meta.title);
|
||||
// 避免重复添加已存在的路由
|
||||
const exists = menus.find(
|
||||
m => m.routeName === route.name || m.path === fullPath
|
||||
);
|
||||
if (!exists) {
|
||||
menus.push({
|
||||
title: title,
|
||||
path: fullPath,
|
||||
icon: route.meta.icon,
|
||||
key: route.name || fullPath,
|
||||
routeName: route.name,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果翻译失败,使用原始标题
|
||||
const exists = menus.find(
|
||||
m => m.routeName === route.name || m.path === fullPath
|
||||
);
|
||||
if (!exists) {
|
||||
menus.push({
|
||||
title: route.meta.title,
|
||||
path: fullPath,
|
||||
icon: route.meta.icon,
|
||||
key: route.name || fullPath,
|
||||
routeName: route.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
getRouteItems(route.children, fullPath);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 使用和菜单面板相同的数据源
|
||||
const menuRoutes = getMenuDataForSearch();
|
||||
|
||||
if (menuRoutes && menuRoutes.length > 0) {
|
||||
getRouteItems(menuRoutes);
|
||||
}
|
||||
|
||||
return menus;
|
||||
});
|
||||
|
||||
// 过滤的菜单项
|
||||
const filteredMenus = computed(() => {
|
||||
if (!searchKeyword.value.trim()) {
|
||||
return searchableMenus.value.slice(0, 10); // 默认显示前10个
|
||||
}
|
||||
|
||||
// return searchableMenus.value.filter(menu =>
|
||||
// menu.title.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
// menu.path.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
// ).slice(0, 10);
|
||||
const value = searchKeyword.value.toLowerCase();
|
||||
return searchableMenus.value
|
||||
.filter(menu => menu.title.toLowerCase().includes(value))
|
||||
.slice(0, 10);
|
||||
});
|
||||
|
||||
/**打开搜索弹窗 */
|
||||
function fnClickSearch() {
|
||||
searchModalOpen.value = true;
|
||||
searchKeyword.value = '';
|
||||
|
||||
nextTick(() => {
|
||||
searchInputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**关闭搜索弹窗 */
|
||||
function fnCloseSearch() {
|
||||
searchModalOpen.value = false;
|
||||
searchKeyword.value = '';
|
||||
}
|
||||
|
||||
/**选择菜单项并跳转 */
|
||||
function fnSelectMenu(menu: any) {
|
||||
try {
|
||||
// 优先使用路由名称跳转
|
||||
if (menu.routeName) {
|
||||
router.push({ name: menu.routeName });
|
||||
} else {
|
||||
router.push(menu.path);
|
||||
}
|
||||
fnCloseSearch();
|
||||
} catch (error) {
|
||||
// 如果跳转失败,尝试直接使用路径
|
||||
try {
|
||||
router.push(menu.path);
|
||||
fnCloseSearch();
|
||||
} catch (secondError) {
|
||||
// 可以在这里添加错误提示
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**键盘事件处理 */
|
||||
function fnHandleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
fnCloseSearch();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search-modal-content {
|
||||
.search-results {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.menu-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 6px;
|
||||
|
||||
.icon {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.menu-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
margin-bottom: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu-path {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-action {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
color: #bfbfbf;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暗黑主题支持
|
||||
[data-theme='dark'] {
|
||||
.search-modal-content {
|
||||
.search-results {
|
||||
.menu-list {
|
||||
.menu-item {
|
||||
&:hover {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
background-color: #262626;
|
||||
|
||||
.icon {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-info {
|
||||
.menu-title {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.menu-path {
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -61,7 +61,7 @@ type OptionsType = {
|
||||
/**请求地址 */
|
||||
url: string;
|
||||
/**请求方法 */
|
||||
method: 'get' | 'post' | 'put' | 'delete' | 'PATCH';
|
||||
method: 'get' | 'post' | 'put' | 'delete' | 'PATCH' | 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
/**请求头 */
|
||||
headers?: HeadersInit;
|
||||
/**地址栏参数 */
|
||||
|
||||
@@ -13,6 +13,8 @@ import { validHttp } from '@/utils/regular-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useRouterStore from '@/store/modules/router';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useNeListStore from '@/store/modules/ne_list';
|
||||
|
||||
// NProgress Configuration
|
||||
NProgress.configure({ showSpinner: false });
|
||||
@@ -30,12 +32,15 @@ const constantRoutes: RouteRecordRaw[] = [
|
||||
name: 'Root',
|
||||
component: BasicLayout,
|
||||
redirect: '/index',
|
||||
// redirect: '/monitor/dashboard',
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
name: 'Index',
|
||||
meta: { title: 'router.index', icon: 'icon-pcduan' },
|
||||
component: () => import('@/views/index.vue'),
|
||||
// meta: { title: 'router.index', icon: 'icon-pcduan', hideInMenu: true },
|
||||
// redirect: '/monitor/dashboard',
|
||||
},
|
||||
{
|
||||
path: '/account',
|
||||
@@ -63,11 +68,16 @@ const constantRoutes: RouteRecordRaw[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/login/oauth2',
|
||||
name: 'LoginOAuth2', // 第三方认证重定向
|
||||
component: () => import('@/views/login/oauth2.vue'),
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
meta: { title: 'router.login' },
|
||||
component: () => import('@/views/login.vue'),
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
@@ -156,7 +166,7 @@ router.afterEach((to, from, failure) => {
|
||||
/**无Token可访问页面地址白名单 */
|
||||
const WHITE_LIST: string[] = [
|
||||
'/login',
|
||||
'/auth-redirect',
|
||||
'/login/oauth2',
|
||||
'/help',
|
||||
'/register',
|
||||
'/quick-start',
|
||||
@@ -210,6 +220,9 @@ router.beforeEach(async (to, from, next) => {
|
||||
const user = useUserStore();
|
||||
if (user.roles && user.roles.length === 0) {
|
||||
try {
|
||||
useNeInfoStore().fnRefreshNelist();
|
||||
useNeListStore().fnNelistRefresh();
|
||||
|
||||
// 获取用户信息
|
||||
await user.fnGetInfo();
|
||||
// 获取路由信息
|
||||
|
||||
123
src/store/modules/ne_list.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import {
|
||||
RESULT_CODE_SUCCESS,
|
||||
RESULT_MSG_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { parseDataToOptions } from '@/utils/parse-tree-utils';
|
||||
|
||||
/**网元列表信息类型 */
|
||||
type NeList = {
|
||||
/**网元列表 */
|
||||
neList: Record<string, any>[];
|
||||
/**级联options树结构 */
|
||||
neCascaderOptions: Record<string, any>[];
|
||||
/**选择器单级父类型 */
|
||||
neSelectOtions: Record<string, any>[];
|
||||
};
|
||||
|
||||
const useNeListStore = defineStore('ne_list', {
|
||||
state: (): NeList => ({
|
||||
neList: [],
|
||||
neCascaderOptions: [],
|
||||
neSelectOtions: [],
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
* 网元列表
|
||||
* @param state 内部属性不用传入
|
||||
* @returns 级联options
|
||||
*/
|
||||
getNeList(state) {
|
||||
return state.neList;
|
||||
},
|
||||
/**
|
||||
* 获取级联options树结构
|
||||
* @param state 内部属性不用传入
|
||||
* @returns 级联options
|
||||
*/
|
||||
getNeCascaderOptions(state) {
|
||||
return state.neCascaderOptions;
|
||||
},
|
||||
/**
|
||||
* 选择器单级父类型
|
||||
* @param state 内部属性不用传入
|
||||
* @returns 选择options
|
||||
*/
|
||||
getNeSelectOtions(state) {
|
||||
return state.neSelectOtions;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 刷新网元列表
|
||||
async fnNelistRefresh() {
|
||||
this.neList = [];
|
||||
return await this.fnNelist();
|
||||
},
|
||||
// 获取网元列表
|
||||
async fnNelist() {
|
||||
// 有数据不请求
|
||||
if (this.neList.length > 0) {
|
||||
return {
|
||||
code: RESULT_CODE_SUCCESS,
|
||||
msg: RESULT_MSG_SUCCESS['en_US'],
|
||||
data: this.neList,
|
||||
};
|
||||
}
|
||||
const res = await listAllNeInfo({
|
||||
bandStatus: false,
|
||||
bandHost: false,
|
||||
});
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 原始列表
|
||||
this.neList = JSON.parse(JSON.stringify(res.data));
|
||||
|
||||
// 转级联数据
|
||||
const options = parseDataToOptions(
|
||||
res.data,
|
||||
'neType',
|
||||
'neName',
|
||||
'neId'
|
||||
);
|
||||
this.neCascaderOptions = options;
|
||||
|
||||
// 转选择器单级父类型
|
||||
this.neSelectOtions = options.map(item => {
|
||||
return {
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
};
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
/**
|
||||
* 含有网元
|
||||
* @param metaNeType ['udm', 'ims', 'udm+ims', 'SGWC'] 支持大小写
|
||||
* @returns boolean
|
||||
*/
|
||||
fnHasNe(metaNeType: string[]) {
|
||||
if (this.neList.length > 0) {
|
||||
const neTypes = this.neSelectOtions.map(item => item.value);
|
||||
let match = false; // 匹配
|
||||
for (const netype of metaNeType) {
|
||||
if (netype.indexOf('+') > -1) {
|
||||
metaNeType = netype.split('+');
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// 同时匹配
|
||||
return metaNeType.every(item => neTypes.includes(item.toUpperCase()));
|
||||
}
|
||||
// 有一种
|
||||
return metaNeType.some(item => neTypes.includes(item.toUpperCase()));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default useNeListStore;
|
||||
@@ -27,8 +27,8 @@ type UserInfo = {
|
||||
email: string;
|
||||
/**用户性别 */
|
||||
sex: string | undefined;
|
||||
/**其他信息 */
|
||||
profile: Record<string, any>;
|
||||
/**用户类型 */
|
||||
userType: string;
|
||||
};
|
||||
|
||||
const useUserStore = defineStore('user', {
|
||||
@@ -42,7 +42,7 @@ const useUserStore = defineStore('user', {
|
||||
phonenumber: '',
|
||||
email: '',
|
||||
sex: undefined,
|
||||
profile: {},
|
||||
userType: 'System',
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
@@ -122,6 +122,7 @@ const useUserStore = defineStore('user', {
|
||||
this.phonenumber = user.phonenumber;
|
||||
this.email = user.email;
|
||||
this.sex = user.sex;
|
||||
this.userType = user.userType;
|
||||
|
||||
// 验证返回的roles是否是一个非空数组
|
||||
if (Array.isArray(roles) && roles.length > 0) {
|
||||
|
||||
@@ -37,6 +37,24 @@ export async function dbGet(storeName: string, key: string) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**数据级缓存全部移除 */
|
||||
export async function dbClear(storeName: string) {
|
||||
if (!storeName ) {
|
||||
return false;
|
||||
}
|
||||
localforage.config({
|
||||
name: import.meta.env.VITE_APP_CODE,
|
||||
storeName: storeName,
|
||||
});
|
||||
try {
|
||||
await localforage.clear();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**数据级缓存移除 */
|
||||
export async function dbRemove(storeName: string, key: string) {
|
||||
if (!storeName || !key) {
|
||||
|
||||
@@ -20,8 +20,12 @@ export const YYYYMMDDHHMMSS = 'YYYYMMDDHHmmss';
|
||||
/**年-月-日 时:分:秒 列如:2022-12-30 01:01:59 */
|
||||
export const YYYY_MM_DD_HH_MM_SS = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
/**年-月-日 时:分:秒 列如:2022-12-30T01:01:59+08:00 */
|
||||
export const YYYY_MM_DD_HH_MM_SSZ = 'YYYY-MM-DDTHH:mm:ssZZ';
|
||||
/**特殊 列如:2025-04-28 08:00:46 GMT+08:00 */
|
||||
export const YYYY_MM_DD_HH_MM_SSZ = 'YYYY-MM-DD HH:mm:ss [GMT]Z';
|
||||
/**国际时间 列如:2022-12-30T01:01:59+08:00 */
|
||||
export const RFC3339 = 'YYYY-MM-DDTHH:mm:ssZ';
|
||||
/**国际时间 列如:Thu, Nov 14 2024 10:19 GMT+08:00 */
|
||||
export const RFC822Z = 'ddd, MMM DD YYYY HH:mm [GMT]Z';
|
||||
|
||||
/**
|
||||
* 格式时间字符串
|
||||
|
||||
@@ -170,25 +170,6 @@ export function parseSizeFromKbs(sizeByte: number, timeInterval: number): any {
|
||||
return [(realBit / 1000 / 1000).toFixed(2), ' Mbits/sec'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 位数据转换单位
|
||||
* @param bits 位Bit大小 64009540 = 512.08 MB
|
||||
* @returns xx B / KB / MB / GB / TB / PB / EB / ZB / YB
|
||||
*/
|
||||
export function parseSizeFromBits(bits: number | string): string {
|
||||
bits = Number(bits) || 0;
|
||||
if (bits <= 0) return '0 B';
|
||||
bits = bits * 8;
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const unitIndex = Math.floor(Math.log2(bits) / 10);
|
||||
const value = bits / Math.pow(1000, unitIndex);
|
||||
const unti = units[unitIndex];
|
||||
if (unitIndex > 0) {
|
||||
return `${value.toFixed(2)} ${unti}`;
|
||||
}
|
||||
return `${value} ${unti}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数转换单位
|
||||
* @param byte 字节Byte大小 64009540 = 512.08 MB
|
||||
|
||||
@@ -7,7 +7,9 @@ import StyleLayout from './components/style-layout.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -29,8 +31,7 @@ onActivated(() => {
|
||||
// 调用时机为首次挂载
|
||||
// 以及每次从缓存中被重新插入时
|
||||
fnLocale();
|
||||
})
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -43,12 +44,15 @@ onActivated(() => {
|
||||
<a-tab-pane
|
||||
key="reset-passwd"
|
||||
:tab="t('views.account.settings.resetPasswd')"
|
||||
v-if="userStore.userType === 'System'"
|
||||
>
|
||||
<ResetPasswd></ResetPasswd>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane
|
||||
key="style-layout"
|
||||
:tab="t('views.account.settings.styleLayout')"
|
||||
v-if="false"
|
||||
>
|
||||
<StyleLayout></StyleLayout>
|
||||
</a-tab-pane>
|
||||
|
||||
@@ -149,14 +149,6 @@ async function fnGetList(reload: boolean = false) {
|
||||
|
||||
// 初始
|
||||
if (!reload) {
|
||||
// 选择第一个
|
||||
if (tableState.data.length > 0) {
|
||||
const id = tableState.data[0].id;
|
||||
fnTableSelectedRowKeys([id]);
|
||||
} else {
|
||||
fnTableSelectedRowKeys(tableState.selectedRowKeys);
|
||||
}
|
||||
|
||||
if (statusBar.value) {
|
||||
fnDesign(statusBar.value, rightNum, errorNum);
|
||||
}
|
||||
@@ -216,44 +208,6 @@ function fnDesign(container: HTMLElement, rightNum: number, errorNum: number) {
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
if (keys.length <= 0) return;
|
||||
const id = keys[0];
|
||||
const row: any = tableState.data.find((item: any) => item.id === id);
|
||||
if (!row) {
|
||||
message.error(t('views.index.neStatus'), 2);
|
||||
return;
|
||||
}
|
||||
const neState = row.serverState;
|
||||
if (!neState?.online) {
|
||||
message.error(t('views.index.neStatus'), 2);
|
||||
return;
|
||||
}
|
||||
tableState.selectedRowKeys = keys;
|
||||
// Mem 将KB转换为MB
|
||||
const totalMemInKB = neState.mem?.totalMem;
|
||||
const nfUsedMemInKB = neState.mem?.nfUsedMem;
|
||||
const sysMemUsageInKB = neState.mem?.sysMemUsage;
|
||||
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
|
||||
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
|
||||
const sysMemUsageInMB = Math.round((sysMemUsageInKB / 1024) * 100) / 100;
|
||||
|
||||
// CPU
|
||||
const nfCpu = neState.cpu?.nfCpuUsage;
|
||||
const sysCpu = neState.cpu?.sysCpuUsage;
|
||||
const nfCpuP = Math.round(nfCpu) / 100;
|
||||
const sysCpuP = Math.round(sysCpu) / 100;
|
||||
|
||||
serverState.value = Object.assign(
|
||||
{
|
||||
cpuUse: `NE:${nfCpuP}%; SYS:${sysCpuP}%`,
|
||||
memoryUse: `Total: ${totalMemInMB}MB; NE: ${nfUsedMemInMB}MB; SYS: ${sysMemUsageInMB}MB`,
|
||||
},
|
||||
neState
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化翻译转换
|
||||
*/
|
||||
@@ -277,6 +231,7 @@ let dict: {
|
||||
});
|
||||
|
||||
let timer: any;
|
||||
let timerFlag: boolean = false;
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('ne_info_status'), getDict('index_status')])
|
||||
@@ -292,7 +247,7 @@ onMounted(() => {
|
||||
fnLocale();
|
||||
await fnGetList(false);
|
||||
timer = setInterval(() => {
|
||||
if (!timer) return;
|
||||
if (timerFlag) return;
|
||||
fnGetList(true);
|
||||
}, 10_000); // 每隔10秒执行一次
|
||||
});
|
||||
@@ -302,6 +257,7 @@ onMounted(() => {
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
timerFlag = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -319,12 +275,6 @@ onBeforeUnmount(() => {
|
||||
:loading="tableState.loading"
|
||||
:pagination="false"
|
||||
:scroll="{ x: true }"
|
||||
:row-selection="{
|
||||
type: 'radio',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
@@ -341,46 +291,6 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<div style="width: 100%; min-height: 200px" ref="statusBar"></div>
|
||||
</a-card>
|
||||
<a-card
|
||||
:loading="tableState.loading"
|
||||
:title="`${t('views.index.mark')} - ${serverState.neName || 'OMC'}`"
|
||||
style="margin-top: 16px"
|
||||
size="small"
|
||||
>
|
||||
<a-descriptions
|
||||
bordered
|
||||
:column="1"
|
||||
:label-style="{ width: '160px' }"
|
||||
>
|
||||
<a-descriptions-item :label="t('views.index.hostName')">
|
||||
{{ serverState.hostname }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.osInfo')">
|
||||
{{ serverState.os }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.ipAddress')">
|
||||
{{ serverState.neIP }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.version')">
|
||||
{{ serverState.version }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.capability')">
|
||||
{{ serverState.capability }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.cpuUse')">
|
||||
{{ serverState.cpuUse }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.memoryUse')">
|
||||
{{ serverState.memoryUse }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.serialNum')">
|
||||
{{ serverState.sn }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.expiryDate')">
|
||||
{{ serverState.expire }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { message, Modal } from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
@@ -18,6 +19,9 @@ import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import saveAs from 'file-saver';
|
||||
import PQueue from 'p-queue';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { TENANTADMIN_ROLE_KEY } from '@/constants/admin-constants';
|
||||
import { listTenant } from '@/api/system/tenant';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
@@ -49,11 +53,13 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
tenantNameArr: <Record<string, any>[]>[],
|
||||
/**网元类型 */
|
||||
neType: 'AMF',
|
||||
neId: '001',
|
||||
eventType: '',
|
||||
imsi: '',
|
||||
tenantName: '',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**开始时间 */
|
||||
@@ -72,8 +78,9 @@ function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
eventType: '',
|
||||
imsi: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
tenantName: '',
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
@@ -116,14 +123,17 @@ let tableState: TabeStateType = reactive({
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列排序 */
|
||||
let tableColumnsDnd = ref<ColumnsType>([]);
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
// {
|
||||
// title: t('common.rowId'),
|
||||
// dataIndex: 'id',
|
||||
// align: 'left',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: 'IMSI',
|
||||
dataIndex: 'eventJSON',
|
||||
@@ -152,15 +162,26 @@ let tableColumns: ColumnsType = [
|
||||
title: t('views.dashboard.ue.time'),
|
||||
dataIndex: 'eventJSON',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
width: 250,
|
||||
customRender(opt) {
|
||||
const record = opt.value;
|
||||
console.log(record);
|
||||
if (record?.time) {
|
||||
return record.time;
|
||||
return parseDateToStr(record.time);
|
||||
}
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
if (record?.timestamp) {
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.tenantName'),
|
||||
dataIndex: 'tenantName',
|
||||
align: 'center',
|
||||
key: 'tenantName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -317,14 +338,18 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading) return;
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.ue.exportTip'),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageSize = 10000;
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
exportAMFDataUE(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -472,8 +497,33 @@ onMounted(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
if (useUserStore().roles.includes('tenant')) {
|
||||
const operateColumnIndex = tableColumns.findIndex(
|
||||
column => column.key === 'tenantName'
|
||||
);
|
||||
|
||||
if (operateColumnIndex !== -1) {
|
||||
tableColumns.splice(operateColumnIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fnGetList();
|
||||
});
|
||||
|
||||
//查询租户
|
||||
listTenant({ parentId: 0 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
|
||||
res.data.forEach((item: any) => {
|
||||
if (item.parentId === '0') {
|
||||
queryParams.tenantNameArr.push({
|
||||
value: item.tenantName,
|
||||
label: item.tenantName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -526,6 +576,17 @@ onBeforeUnmount(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24" v-roles:has="[TENANTADMIN_ROLE_KEY]">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.sub.tenantName')"
|
||||
name="tenantName "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.tenantName"
|
||||
:options="queryParams.tenantNameArr"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
@@ -575,7 +636,11 @@ onBeforeUnmount(() => {
|
||||
cancel-text="No"
|
||||
@confirm="fnRealTime()"
|
||||
>
|
||||
<a-button type="primary" :danger="realTimeData">
|
||||
<a-button
|
||||
type="primary"
|
||||
:danger="realTimeData"
|
||||
v-roles:has="[TENANTADMIN_ROLE_KEY]"
|
||||
>
|
||||
<template #icon><FundOutlined /> </template>
|
||||
{{
|
||||
!realTimeData
|
||||
@@ -623,6 +688,11 @@ onBeforeUnmount(() => {
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
cache-id="amfUeData"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
@@ -654,7 +724,7 @@ onBeforeUnmount(() => {
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:columns="tableColumnsDnd"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
|
||||
@@ -21,8 +21,10 @@ import { parseDateToStr, parseDuration } from '@/utils/date-utils';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import saveAs from 'file-saver';
|
||||
import PQueue from 'p-queue';
|
||||
import { listTenant } from '@/api/system/tenant';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import { dayjsRanges } from '@/hooks/useRangePicker';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
@@ -35,9 +37,12 @@ let dict: {
|
||||
cdrSipCode: DictType[];
|
||||
/**CDR 呼叫类型 */
|
||||
cdrCallType: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
/**网元可选 */
|
||||
@@ -48,30 +53,17 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
dayjs().startOf('hour'),
|
||||
dayjs().endOf('hour'),
|
||||
]);
|
||||
/**时间范围 */
|
||||
let rangePickerPresets = ref([
|
||||
{
|
||||
label: 'Now hour',
|
||||
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
|
||||
},
|
||||
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
|
||||
{
|
||||
label: 'Yesterday',
|
||||
value: [
|
||||
dayjs().subtract(1, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
tenantNameArr: <Record<string, any>[]>[],
|
||||
/**网元类型 */
|
||||
neType: 'IMS',
|
||||
neId: '001',
|
||||
recordType: '',
|
||||
callerParty: '',
|
||||
calledParty: '',
|
||||
tenantName: '',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**开始时间 */
|
||||
@@ -91,6 +83,7 @@ function fnQueryReset() {
|
||||
recordType: '',
|
||||
callerParty: '',
|
||||
calledParty: '',
|
||||
tenantName: '',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
pageNum: 1,
|
||||
@@ -137,12 +130,12 @@ let tableState: TabeStateType = reactive({
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
// {
|
||||
// title: t('common.rowId'),
|
||||
// dataIndex: 'id',
|
||||
// align: 'left',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: t('views.dashboard.cdr.recordType'),
|
||||
dataIndex: 'cdrJSON',
|
||||
@@ -182,13 +175,6 @@ let tableColumns: ColumnsType = [
|
||||
return cdrJSON.calledParty;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.result'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'cause',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.duration'),
|
||||
dataIndex: 'cdrJSON',
|
||||
@@ -202,32 +188,77 @@ let tableColumns: ColumnsType = [
|
||||
: parseDuration(cdrJSON.callDuration);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.resultCode'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'code',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.resultCause'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'cause',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.mosAverage'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'mosAverage',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.mosAverage;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.callConnectionTime'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'callConnectionTime',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.callType === 'sms'
|
||||
? '-'
|
||||
: parseDuration(cdrJSON.callConnectionTime);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.seizureTime'),
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
width: 250,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
if (typeof cdrJSON.seizureTime === 'number') {
|
||||
return parseDateToStr(+cdrJSON.seizureTime * 1000);
|
||||
}
|
||||
return cdrJSON.seizureTime;
|
||||
return parseDateToStr(cdrJSON.seizureTime);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.releaseTime'),
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
width: 250,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
if (typeof cdrJSON.releaseTime === 'number') {
|
||||
return parseDateToStr(+cdrJSON.releaseTime * 1000);
|
||||
}
|
||||
return cdrJSON.releaseTime;
|
||||
return parseDateToStr(cdrJSON.releaseTime);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.tenantName'),
|
||||
dataIndex: 'tenantName',
|
||||
align: 'left',
|
||||
key: 'tenantName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -388,20 +419,25 @@ function fnGetList(pageNum?: number) {
|
||||
modalState.maxId = Number(res.rows[0].id);
|
||||
}
|
||||
}
|
||||
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading) return;
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.cdr.exportTip'),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageSize = 10000;
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
exportIMSDataCDR(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -494,16 +530,21 @@ function wsMessage(res: Record<string, any>) {
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('cdr_sip_code'), getDict('cdr_call_type')]).then(
|
||||
resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
Promise.allSettled([
|
||||
getDict('cdr_sip_code'),
|
||||
getDict('cdr_call_type'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
}
|
||||
);
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[2].value;
|
||||
}
|
||||
});
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore()
|
||||
.fnNelist()
|
||||
@@ -532,6 +573,21 @@ onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
|
||||
//查询租户
|
||||
listTenant({ parentId: 0 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
|
||||
res.data.forEach((item: any) => {
|
||||
if (item.parentId === '0') {
|
||||
queryParams.tenantNameArr.push({
|
||||
value: item.tenantName,
|
||||
label: item.tenantName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -551,7 +607,7 @@ onBeforeUnmount(() => {
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item label="IMS" name="neId ">
|
||||
<a-select
|
||||
v-model:value="queryParams.neId"
|
||||
@@ -561,7 +617,7 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.called')"
|
||||
name="calledParty"
|
||||
@@ -573,7 +629,7 @@ onBeforeUnmount(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.caller')"
|
||||
name="callerParty "
|
||||
@@ -585,20 +641,6 @@ onBeforeUnmount(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.recordType')"
|
||||
@@ -620,7 +662,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:presets="rangePickerPresets"
|
||||
:presets="dayjsRanges()"
|
||||
:bordered="true"
|
||||
:allow-clear="false"
|
||||
style="width: 100%"
|
||||
@@ -629,6 +671,31 @@ onBeforeUnmount(() => {
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.sub.tenantName')"
|
||||
name="tenantName "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.tenantName"
|
||||
:options="queryParams.tenantNameArr"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
@@ -732,7 +799,7 @@ onBeforeUnmount(() => {
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
|
||||
:scroll="{ x: tableColumns.length * 160, y: 'calc(100vh - 480px)' }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
@@ -747,7 +814,7 @@ onBeforeUnmount(() => {
|
||||
:value="record.cdrJSON.callType"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'cause'">
|
||||
<template v-if="column.key === 'code'">
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCode"
|
||||
@@ -759,6 +826,16 @@ onBeforeUnmount(() => {
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'cause'">
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCodeCause"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else> Call failure for other reason </span>
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
@@ -787,93 +864,6 @@ onBeforeUnmount(() => {
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="8" :md="12" :xs="24" :offset="2">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.cdrInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.neName') }}: </span>
|
||||
<span>{{ record.neName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.rmUid') }}: </span>
|
||||
<span>{{ record.rmUID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||
<span>
|
||||
{{
|
||||
typeof record.cdrJSON.releaseTime === 'number'
|
||||
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
|
||||
: record.cdrJSON.releaseTime
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.type') }}: </span>
|
||||
<DictTag
|
||||
:options="dict.cdrCallType"
|
||||
:value="record.cdrJSON.callType"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.duration') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
{{ parseDuration(record.cdrJSON.callDuration) }}
|
||||
</span>
|
||||
<span v-else> - </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
|
||||
<span>{{ record.cdrJSON.callerParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.called') }}: </span>
|
||||
<span>{{ record.cdrJSON.calledParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.seizureTime') }}: </span>
|
||||
<span>
|
||||
{{
|
||||
typeof record.cdrJSON.seizureTime === 'number'
|
||||
? parseDateToStr(+record.cdrJSON.seizureTime * 1000)
|
||||
: record.cdrJSON.seizureTime
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.releaseTime') }}: </span>
|
||||
<span>
|
||||
{{
|
||||
typeof record.cdrJSON.releaseTime === 'number'
|
||||
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
|
||||
: record.cdrJSON.releaseTime
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import saveAs from 'file-saver';
|
||||
import PQueue from 'p-queue';
|
||||
import { listTenant } from '@/api/system/tenant';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
@@ -50,11 +51,13 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
tenantNameArr: <Record<string, any>[]>[],
|
||||
/**网元类型 */
|
||||
neType: 'MME',
|
||||
neId: '001',
|
||||
eventType: '',
|
||||
imsi: '',
|
||||
tenantName: '',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**开始时间 */
|
||||
@@ -73,8 +76,9 @@ function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
eventType: '',
|
||||
imsi: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
tenantName: '',
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
@@ -119,12 +123,12 @@ let tableState: TabeStateType = reactive({
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
// {
|
||||
// title: t('common.rowId'),
|
||||
// dataIndex: 'id',
|
||||
// align: 'left',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: 'IMSI',
|
||||
dataIndex: 'eventJSON',
|
||||
@@ -153,15 +157,25 @@ let tableColumns: ColumnsType = [
|
||||
title: t('views.dashboard.ue.time'),
|
||||
dataIndex: 'eventJSON',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
width: 250,
|
||||
customRender(opt) {
|
||||
const record = opt.value;
|
||||
if (record?.time) {
|
||||
return record.time;
|
||||
return parseDateToStr(record.time);
|
||||
}
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
if (record?.timestamp) {
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.tenantName'),
|
||||
dataIndex: 'tenantName',
|
||||
align: 'center',
|
||||
key: 'tenantName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -318,14 +332,18 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading) return;
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.ue.exportTip'),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageSize = 10000;
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
exportMMEDataUE(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -481,6 +499,21 @@ onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
|
||||
//查询租户
|
||||
listTenant({ parentId: 0 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
|
||||
res.data.forEach((item: any) => {
|
||||
if (item.parentId === '0') {
|
||||
queryParams.tenantNameArr.push({
|
||||
value: item.tenantName,
|
||||
label: item.tenantName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -533,6 +566,17 @@ onBeforeUnmount(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.sub.tenantName')"
|
||||
name="tenantName "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.tenantName"
|
||||
:options="queryParams.tenantNameArr"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
|
||||
@@ -236,8 +236,11 @@ function handleRanderChart(
|
||||
}
|
||||
|
||||
function fnChangeData(data: any[], itemID: string) {
|
||||
let info = data.find((item: any) => item.id === itemID);
|
||||
if (!info || !info.neState.online) return;
|
||||
const neType=itemID.split('_')[0];
|
||||
const neID=itemID.split('_')[1];
|
||||
let info = data.find((item: any) => item.id === neType);
|
||||
|
||||
if (!info || !info.neStateMap[neID]?.online) return;
|
||||
// if (!info.neState.online) {
|
||||
// info = data.find((item: any) => item.id === itemID);
|
||||
// graphNodeClickID.value = itemID;
|
||||
@@ -249,16 +252,16 @@ function fnChangeData(data: any[], itemID: string) {
|
||||
// console.log(info.neState.disk);
|
||||
let sysCpuUsage = 0;
|
||||
let nfCpuUsage = 0;
|
||||
if (info.neState.cpu) {
|
||||
nfCpuUsage = info.neState.cpu.nfCpuUsage;
|
||||
const nfCpu = +(info.neState.cpu.nfCpuUsage / 100);
|
||||
if (info.neStateMap[neID].cpu) {
|
||||
nfCpuUsage = info.neStateMap[neID].cpu.nfCpuUsage;
|
||||
const nfCpu = +(info.neStateMap[neID].cpu.nfCpuUsage / 100);
|
||||
nfCpuUsage = +nfCpu.toFixed(2);
|
||||
if (nfCpuUsage > 100) {
|
||||
nfCpuUsage = 100;
|
||||
}
|
||||
|
||||
sysCpuUsage = info.neState.cpu.sysCpuUsage;
|
||||
let sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
|
||||
sysCpuUsage = info.neStateMap[neID].cpu.sysCpuUsage;
|
||||
let sysCpu = +(info.neStateMap[neID].cpu.sysCpuUsage / 100);
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
if (sysCpuUsage > 100) {
|
||||
sysCpuUsage = 100;
|
||||
@@ -266,8 +269,8 @@ function fnChangeData(data: any[], itemID: string) {
|
||||
}
|
||||
|
||||
let sysMemUsage = 0;
|
||||
if (info.neState.mem) {
|
||||
const men = info.neState.mem.sysMemUsage;
|
||||
if (info.neStateMap[neID].mem) {
|
||||
const men = info.neStateMap[neID].mem.sysMemUsage;
|
||||
sysMemUsage = +(men / 100).toFixed(2);
|
||||
if (sysMemUsage > 100) {
|
||||
sysMemUsage = 100;
|
||||
@@ -275,8 +278,8 @@ function fnChangeData(data: any[], itemID: string) {
|
||||
}
|
||||
|
||||
let sysDiskUsage = 0;
|
||||
if (info.neState.disk && Array.isArray(info.neState.disk.partitionInfo)) {
|
||||
let disks: any[] = info.neState.disk.partitionInfo;
|
||||
if (info.neStateMap[neID].disk && Array.isArray(info.neStateMap[neID].disk.partitionInfo)) {
|
||||
let disks: any[] = info.neStateMap[neID].disk.partitionInfo;
|
||||
disks = disks.sort((a, b) => +b.used - +a.used);
|
||||
if (disks.length > 0) {
|
||||
const { total, used } = disks[0];
|
||||
|
||||
@@ -22,18 +22,25 @@ const graphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图节点展示 */
|
||||
const graphNodeTooltip = new Tooltip({
|
||||
offsetX: 20,
|
||||
offsetX: 10,
|
||||
offsetY: 20,
|
||||
getContent(evt) {
|
||||
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
|
||||
const { id, label, neState }: any = evt.item?.getModel();
|
||||
const { id, label, neState, neInfoList, neStateMap }: any = evt.item?.getModel();
|
||||
//console.log(neInfoList,neState,neInfoList);
|
||||
if (notNeNodes.includes(id)) {
|
||||
return `<div><span>${label || id}</span></div>`;
|
||||
}
|
||||
if (!neState) {
|
||||
return `<div><span>${label || id}</span></div>`;
|
||||
}
|
||||
return `
|
||||
|
||||
// 获取同类型网元列表
|
||||
const sameTypeNes = neInfoList || [];
|
||||
|
||||
// 如果没有网元或只有一个网元,显示原来的信息
|
||||
if (sameTypeNes.length <= 1) {
|
||||
return `
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
@@ -56,7 +63,7 @@ const graphNodeTooltip = new Tooltip({
|
||||
<div><strong>${t('views.monitor.topology.name')}:</strong><span>
|
||||
${neState.neName ?? '--'}
|
||||
</span></div>
|
||||
<div><strong>IP:</strong><span>${neState.neIP}</span></div>
|
||||
<div><strong>IP:</strong><span>${neState.neIP ?? '--'}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||
${neState.version ?? '--'}
|
||||
</span></div>
|
||||
@@ -65,24 +72,71 @@ const graphNodeTooltip = new Tooltip({
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||
${neState.expire ?? '--'}
|
||||
</span></div>
|
||||
</span></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 如果有多个网元,聚合显示
|
||||
let content = `
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
"
|
||||
>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neState.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions')
|
||||
}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||
${neState.refreshTime ?? '--'}
|
||||
</span></div>
|
||||
<div>========================</div>`;
|
||||
|
||||
// 为每个同类型网元添加信息
|
||||
sameTypeNes.forEach((ne: any, index: number) => {
|
||||
// 获取该网元的状态信息
|
||||
const neStateInfo = neStateMap?.[ne.neId] ||
|
||||
(ne.neId === neState.neId ? neState : {});
|
||||
|
||||
content += `
|
||||
<div style="margin-top: 8px;"><strong>${t('views.monitor.topology.name')}:${ne.neName || id + '_' + ne.neId}</strong></div>
|
||||
<div><strong>ID:</strong><span>${ne.neId || '--'}</span></div>
|
||||
<div><strong>IP:</strong><span>${neStateInfo.neIP || ne.neIP || '--'}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||
${neStateInfo.version || ne.version || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||
${neStateInfo.sn || ne.sn || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||
${neStateInfo.expire || ne.expire || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neStateInfo.online !== undefined
|
||||
? (neStateInfo.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions'))
|
||||
: 'undefined'
|
||||
}
|
||||
</span></div>
|
||||
${index < sameTypeNes.length - 1 ? '<div>------------------------</div>' : ''}
|
||||
`;
|
||||
});
|
||||
|
||||
content += '</div>';
|
||||
return content;
|
||||
},
|
||||
itemTypes: ['node'],
|
||||
});
|
||||
|
||||
/**图绑定事件 */
|
||||
function fnGraphEvent(graph: Graph) {
|
||||
// 节点点击
|
||||
graph.on('node:click', evt => {
|
||||
// 获得鼠标当前目标节点
|
||||
const node = evt.item?.getModel();
|
||||
if (node && node.id && !notNeNodes.includes(node.id)) {
|
||||
graphNodeClickID.value = node.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**图数据渲染 */
|
||||
function handleRanderGraph(
|
||||
@@ -104,7 +158,7 @@ function handleRanderGraph(
|
||||
fitViewPadding: [20],
|
||||
autoPaint: true,
|
||||
modes: {
|
||||
default: ['drag-canvas', 'zoom-canvas'],
|
||||
// default: ['drag-canvas', 'zoom-canvas'],
|
||||
},
|
||||
groupByTypes: false,
|
||||
nodeStateStyles: {
|
||||
@@ -122,7 +176,6 @@ function handleRanderGraph(
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
|
||||
fnGraphEvent(graph);
|
||||
|
||||
graphG6.value = graph;
|
||||
|
||||
@@ -150,14 +203,14 @@ function handleRanderGraph(
|
||||
* 获取图组数据渲染到画布
|
||||
* @param reload 是否重载数据
|
||||
*/
|
||||
function fnGraphDataLoad(reload: boolean = false) {
|
||||
function fnGraphDataLoad(reload: boolean = false) {
|
||||
Promise.all([
|
||||
getGraphData(graphState.group),
|
||||
listAllNeInfo({
|
||||
bandStatus: false,
|
||||
}),
|
||||
])
|
||||
.then(resArr => {
|
||||
.then(resArr => {
|
||||
const graphRes = resArr[0];
|
||||
const neRes = resArr[1];
|
||||
if (
|
||||
@@ -183,27 +236,45 @@ function fnGraphDataLoad(reload: boolean = false) {
|
||||
if (!res) return;
|
||||
const { combos, edges, nodes } = res.graphData;
|
||||
|
||||
// 按网元类型分组
|
||||
const neTypeMap = new Map();
|
||||
res.neList.forEach(ne => {
|
||||
if (!ne.neType) return;
|
||||
if (!neTypeMap.has(ne.neType)) {
|
||||
neTypeMap.set(ne.neType, []);
|
||||
}
|
||||
neTypeMap.get(ne.neType).push(ne);
|
||||
});
|
||||
|
||||
// 节点过滤
|
||||
const nf: Record<string, any>[] = nodes.filter(
|
||||
(node: Record<string, any>) => {
|
||||
Reflect.set(node, 'neState', { online: false });
|
||||
Reflect.set(node, 'neStateMap', {}); // 初始化状态映射
|
||||
|
||||
// 图片路径处理
|
||||
if (node.img) node.img = parseBasePath(node.img);
|
||||
if (node.icon.show && node.icon?.img) {
|
||||
if (node.icon.show && node.icon?.img){
|
||||
node.icon.img = parseBasePath(node.icon.img);
|
||||
}
|
||||
|
||||
// 遍历是否有网元数据
|
||||
const nodeID: string = node.id;
|
||||
const hasNe = res.neList.some(ne => {
|
||||
Reflect.set(node, 'neInfo', ne.neType === nodeID ? ne : {});
|
||||
return ne.neType === nodeID;
|
||||
});
|
||||
if (hasNe) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理非网元节点
|
||||
if (notNeNodes.includes(nodeID)) {
|
||||
return true;
|
||||
}
|
||||
//(neTypeMap.get(nodeID),nodeID,node.neState)
|
||||
// 处理网元节点
|
||||
if (neTypeMap.has(nodeID)) {
|
||||
// all NeInfo
|
||||
Reflect.set(node, 'neInfoList', neTypeMap.get(nodeID));
|
||||
|
||||
Reflect.set(node, 'neInfo', neTypeMap.get(nodeID)[0] || {});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
@@ -215,7 +286,6 @@ function fnGraphDataLoad(reload: boolean = false) {
|
||||
const edgeTarget: string = edge.target;
|
||||
const hasNeS = nf.some(n => n.id === edgeSource);
|
||||
const hasNeT = nf.some(n => n.id === edgeTarget);
|
||||
// console.log(hasNeS, edgeSource, hasNeT, edgeTarget);
|
||||
if (hasNeS && hasNeT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import { UniversalTransition } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { markRaw } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow';
|
||||
import { upfWhoId } from '../../hooks/useWS';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -151,7 +151,7 @@ function handleRanderChart() {
|
||||
top: '14%',
|
||||
left: '4%',
|
||||
right: '4%',
|
||||
bottom: '12%',
|
||||
bottom: '16%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
@@ -160,7 +160,7 @@ function handleRanderChart() {
|
||||
data: lineXTime,
|
||||
axisLabel: {
|
||||
formatter: function (params: any) {
|
||||
return params.split(' ')[1];
|
||||
return params;
|
||||
},
|
||||
fontSize: 14,
|
||||
},
|
||||
@@ -208,9 +208,10 @@ function fnGetInitData() {
|
||||
|
||||
listKPIData({
|
||||
neType: 'UPF',
|
||||
neId: '001',
|
||||
neId: upfWhoId.value,
|
||||
startTime: nowDate - 5 * 60 * 1000,
|
||||
endTime: nowDate,
|
||||
|
||||
interval: 5, // 5秒
|
||||
sortField: 'timeGroup',
|
||||
sortOrder: 'asc',
|
||||
|
||||
@@ -19,12 +19,15 @@ let dict: {
|
||||
ueEventType: DictType[];
|
||||
/**UE 事件CM状态 */
|
||||
ueEventCmState: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
ueAauthCode: [],
|
||||
ueEventType: [],
|
||||
ueEventCmState: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
@@ -35,6 +38,7 @@ onMounted(() => {
|
||||
getDict('ue_auth_code'),
|
||||
getDict('ue_event_type'),
|
||||
getDict('ue_event_cm_state'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
@@ -51,6 +55,9 @@ onMounted(() => {
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[4].value;
|
||||
}
|
||||
if (resArr[5].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[5].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -75,16 +82,7 @@ onMounted(() => {
|
||||
</span>
|
||||
</div>
|
||||
<div></div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<span :title="item.data.releaseTime">
|
||||
{{
|
||||
typeof item.data.releaseTime === 'number'
|
||||
? parseDateToStr(+item.data.releaseTime * 1000)
|
||||
: item.data.releaseTime
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="card-cdr-item">
|
||||
<div>
|
||||
@@ -105,6 +103,16 @@ onMounted(() => {
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<span :title="item.data.releaseTime">
|
||||
{{
|
||||
typeof item.data.releaseTime === 'number'
|
||||
? parseDateToStr(+item.data.releaseTime * 1000)
|
||||
: parseDateToStr(item.data.releaseTime)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span v-if="item.data.callType !== 'sms'">
|
||||
@@ -112,6 +120,11 @@ onMounted(() => {
|
||||
:options="dict.cdrSipCode"
|
||||
:value="item.data.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
<DictTag
|
||||
:options="dict.cdrSipCodeCause"
|
||||
:value="item.data.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
@@ -135,15 +148,7 @@ onMounted(() => {
|
||||
<div>
|
||||
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ item.data.time }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ parseDateToStr(+item.data.timestamp * 1000) }}
|
||||
</template>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
@@ -157,7 +162,16 @@ onMounted(() => {
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{ parseDateToStr(+item.data.timestamp * 1000) }}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
@@ -187,7 +201,7 @@ onMounted(() => {
|
||||
<span v-if="item.type === 'cm-state'">
|
||||
{{
|
||||
dict.ueEventType
|
||||
.find(s => s.value === item.type)
|
||||
.find((s: any) => s.value === item.type)
|
||||
?.label.replace('CM', 'ECM')
|
||||
}}
|
||||
</span>
|
||||
@@ -198,16 +212,7 @@ onMounted(() => {
|
||||
<div>
|
||||
IMSI: <span :title="item.data?.imsi">{{ item.data?.imsi }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<span :title="item.data?.timestamp">
|
||||
{{
|
||||
typeof item.data?.timestamp === 'number'
|
||||
? parseDateToStr(+item.data?.timestamp * 1000)
|
||||
: item.data?.timestamp
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
@@ -221,6 +226,20 @@ onMounted(() => {
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{
|
||||
typeof item.data?.timestamp === 'number'
|
||||
? parseDateToStr(+item.data?.timestamp * 1000)
|
||||
: parseDateToStr(item.data?.timestamp)
|
||||
}}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
|
||||
@@ -128,6 +128,19 @@
|
||||
align-items: baseline;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
/* Subscriber Information部分的宽度调整 */
|
||||
.skim .inner .data .item:nth-child(1) {
|
||||
width: 38%; /* Total Subscriber Base - 稍微宽一点 */
|
||||
}
|
||||
|
||||
.skim .inner .data .item:nth-child(2) {
|
||||
width: 33%; /* VoLTE - 保持不变 */
|
||||
}
|
||||
|
||||
.skim .inner .data .item:nth-child(3) {
|
||||
width: 29%; /* VoIP - 稍微窄一点 */
|
||||
}
|
||||
.skim .inner .data .item div {
|
||||
font-size: 1.467rem;
|
||||
color: #fff;
|
||||
|
||||
@@ -35,16 +35,20 @@ export const graphState = reactive<Record<string, any>>({
|
||||
export const graphG6 = ref<any>(null);
|
||||
|
||||
/**图点击选择 */
|
||||
export const graphNodeClickID = ref<string>('UPF');
|
||||
export const graphNodeClickID = ref<string>('UPF_001');
|
||||
|
||||
/**图节点网元信息状态 */
|
||||
export const graphNodeState = computed(() =>
|
||||
graphState.data.nodes.map((item: any) => ({
|
||||
export const graphNodeState = computed(() =>{
|
||||
return graphState.data.nodes.map((item: any) => ({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
neInfo: item.neInfo,
|
||||
neState: item.neState,
|
||||
neInfoList:item.neInfoList,
|
||||
neStateMap: item.neStateMap,
|
||||
}))
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
/**图节点网元状态数量 */
|
||||
@@ -68,48 +72,69 @@ export const graphNodeStateNum = computed(() => {
|
||||
export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
|
||||
|
||||
/**neStateParse 网元状态 数据解析 */
|
||||
export function neStateParse(neType: string, data: Record<string, any>) {
|
||||
export function neStateParse(neType: string, data: Record<string, any>,neId: string) {
|
||||
// console.log('neStateParse',neType, data, neId);
|
||||
|
||||
const { combos, edges, nodes } = graphState.data;
|
||||
|
||||
const node = nodes.find((item: Record<string, any>) => item.id === neType);
|
||||
//console.log('neStateParse',node);
|
||||
|
||||
if (!node) return;
|
||||
|
||||
// 初始化状态映射
|
||||
if (!node.neStateMap) node.neStateMap = {};
|
||||
|
||||
// 更新网元状态
|
||||
const newNeState = Object.assign(node.neState, data, {
|
||||
const newNeState :any = {
|
||||
...data, // 先展开data对象
|
||||
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
|
||||
online: !!data.cpu,
|
||||
});
|
||||
neId: neId
|
||||
};
|
||||
// 如果是001,更新节点状态。neInfo为主要的网元信息
|
||||
if (node.neInfo && node.neInfo.neId === neId) {
|
||||
Object.assign(node.neState, newNeState);
|
||||
}
|
||||
|
||||
// 通过 ID 查询节点实例
|
||||
//console.log(node.neState)
|
||||
// 无论是否为主要网元,都更新状态映射
|
||||
node.neStateMap[neId] = {...newNeState};
|
||||
// 通过 ID 查询节点实例
|
||||
const item = graphG6.value.findById(node.id);
|
||||
if (item) {
|
||||
const stateColor = newNeState.online ? '#52c41a' : '#f5222d'; // 状态颜色
|
||||
// 图片类型不能填充
|
||||
if (node.type.startsWith('image')) {
|
||||
// 更新节点
|
||||
if (node.label !== newNeState.neName) {
|
||||
graphG6.value.updateItem(item, {
|
||||
label: newNeState.neName,
|
||||
});
|
||||
}
|
||||
// 设置状态
|
||||
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
|
||||
} else {
|
||||
// 更新节点
|
||||
graphG6.value.updateItem(item, {
|
||||
label: newNeState.neName,
|
||||
// neState: newNeState,
|
||||
style: {
|
||||
fill: stateColor, // 填充色
|
||||
stroke: stateColor, // 填充色
|
||||
},
|
||||
// labelCfg: {
|
||||
// style: {
|
||||
// fill: '#ffffff', // 标签文本色
|
||||
// },
|
||||
// },
|
||||
});
|
||||
// 设置状态
|
||||
graphG6.value.setItemState(item, 'stroke', newNeState.online);
|
||||
// 检查当前节点下所有网元的状态
|
||||
const allStates = Object.values(node.neStateMap);
|
||||
// 判断状态颜色
|
||||
let stateColor = '#52c41a'; // 默认绿色(所有网元都正常)
|
||||
if (allStates.some((state: any) => !state.online)) {
|
||||
// 如果有任何一个网元不正常
|
||||
stateColor = allStates.every((state: any) => !state.online) ? '#f5222d' : '#faad14'; // 红色(全部不正常)或黄色(部分不正常)
|
||||
}
|
||||
|
||||
// 图片类型不能填充
|
||||
if (node.type && node.type.startsWith('image')) {
|
||||
// 更新节点
|
||||
if (node.label !== newNeState.neType) {
|
||||
graphG6.value.updateItem(item, {
|
||||
label: newNeState.neType,
|
||||
});
|
||||
}
|
||||
// 设置状态
|
||||
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
|
||||
} else {
|
||||
// 更新节点
|
||||
graphG6.value.updateItem(item, {
|
||||
label: newNeState.neType,
|
||||
style: {
|
||||
fill: stateColor, // 填充色
|
||||
stroke: stateColor, // 填充色
|
||||
},
|
||||
});
|
||||
// 设置状态
|
||||
graphG6.value.setItemState(item, 'stroke', newNeState.online);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 设置边状态
|
||||
@@ -167,6 +192,6 @@ export function topologyReset() {
|
||||
nodes: [],
|
||||
};
|
||||
graphG6.value = null;
|
||||
graphNodeClickID.value = 'UPF';
|
||||
graphNodeClickID.value = 'UPF_001';
|
||||
neStateRequestMap.value = new Map();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
|
||||
import { parseSizeFromByte, parseSizeFromKbs } from '@/utils/parse-utils';
|
||||
import { ref } from 'vue';
|
||||
|
||||
type FDType = {
|
||||
@@ -23,7 +23,7 @@ export const upfFlowData = ref<FDType>({
|
||||
|
||||
/**UPF-流量数据 数据解析 */
|
||||
export function upfFlowParse(data: Record<string, string>) {
|
||||
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup']));
|
||||
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup'], 'HH:mm:ss'));
|
||||
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
|
||||
upfFlowData.value.lineYUp.push(upN3[0]);
|
||||
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
|
||||
@@ -79,9 +79,9 @@ export function upfTFParse(day: string, data: Record<string, number>) {
|
||||
let { up, down } = data;
|
||||
upfTotalFlow.value[day] = {
|
||||
up: up,
|
||||
upFrom: parseSizeFromBits(up),
|
||||
upFrom: parseSizeFromByte(up),
|
||||
down: down,
|
||||
downFrom: parseSizeFromBits(down),
|
||||
downFrom: parseSizeFromByte(down),
|
||||
requestFlag: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,11 +96,7 @@ export function eventListParse(
|
||||
eventData.value.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
// 有数据进行排序
|
||||
if (eventData.value.length > 5) {
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
// 激活选中
|
||||
if (eventData.value.length > 0) {
|
||||
eventId.value = eventData.value[0].eId;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import {
|
||||
eventData,
|
||||
eventListParse,
|
||||
eventItemParseAndPush,
|
||||
userActivityReset,
|
||||
@@ -16,10 +17,7 @@ import { topologyReset, neStateParse, neStateRequestMap } from './useTopology';
|
||||
import PQueue from 'p-queue';
|
||||
|
||||
/**UPF-的Id */
|
||||
export const upfWhoId = ref<any>('');
|
||||
|
||||
/**UPF-的RmUid */
|
||||
export const upfWhoRmUid = ref<any>('');
|
||||
export const upfWhoId = ref<any>('001');
|
||||
|
||||
/**websocket连接 */
|
||||
export default function useWS() {
|
||||
@@ -33,7 +31,6 @@ export default function useWS() {
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
//console.log(res);
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
@@ -42,7 +39,8 @@ export default function useWS() {
|
||||
// 网元状态
|
||||
if (requestId && requestId.startsWith('neState')) {
|
||||
const neType = requestId.split('_')[1];
|
||||
neStateParse(neType, data);
|
||||
const neId = requestId.split('_')[2];
|
||||
neStateParse(neType, data, neId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,28 +50,31 @@ export default function useWS() {
|
||||
case 'amf_1010_001':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('amf_ue', data);
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
break;
|
||||
// MME_UE会话事件
|
||||
case 'mme_1011_001':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('mme_ue', data);
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
break;
|
||||
// IMS_CDR会话事件
|
||||
case 'ims_1005_001':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('ims_cdr', data);
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
break;
|
||||
//UPF-总流量数
|
||||
case 'upf_001_0':
|
||||
case `upf_${upfWhoId.value}_0`:
|
||||
upfTFParse('0', data);
|
||||
break;
|
||||
case 'upf_001_7':
|
||||
case `upf_${upfWhoId.value}_7`:
|
||||
upfTFParse('7', data);
|
||||
break;
|
||||
case 'upf_001_30':
|
||||
case `upf_${upfWhoId.value}_30`:
|
||||
upfTFParse('30', data);
|
||||
break;
|
||||
}
|
||||
@@ -83,7 +84,7 @@ export default function useWS() {
|
||||
}
|
||||
switch (data.groupId) {
|
||||
// kpiEvent 指标UPF
|
||||
case '10_UPF_' + upfWhoId.value:
|
||||
case `10_UPF_${upfWhoId.value}`:
|
||||
if (data.data) {
|
||||
upfFlowParse(data.data);
|
||||
}
|
||||
@@ -118,11 +119,11 @@ export default function useWS() {
|
||||
upfTotalFlow.value[day].requestFlag = true;
|
||||
|
||||
ws.send({
|
||||
requestId: `upf_001_${day}`,
|
||||
requestId: `upf_${upfWhoId.value}_${day}`,
|
||||
type: 'upf_tf',
|
||||
data: {
|
||||
neType: 'UPF',
|
||||
neId: '001',
|
||||
neId: upfWhoId.value,
|
||||
day: Number(day),
|
||||
},
|
||||
});
|
||||
@@ -193,7 +194,7 @@ export default function useWS() {
|
||||
* MME_UE会话事件(GroupID:1011_neId)
|
||||
* IMS_CDR会话事件(GroupID:1005_neId)
|
||||
*/
|
||||
subGroupID: '10_UPF_' + neId + ',1010_001,1011_001,1005_001',
|
||||
subGroupID: `10_UPF_${neId},1010_001,1011_001,1005_001`,
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, nextTick } from 'vue';
|
||||
import svgBase from '@/assets/svg/base.svg';
|
||||
import svgUserIMS from '@/assets/svg/userIMS.svg';
|
||||
import svgUserSMF from '@/assets/svg/userSMF.svg';
|
||||
@@ -13,6 +13,8 @@ import { listUDMSub } from '@/api/neData/udm_sub';
|
||||
import { listUENumBySMF } from '@/api/neUser/smf';
|
||||
import { listUENumByIMS } from '@/api/neUser/ims';
|
||||
import { listBase5G } from '@/api/neUser/base5G';
|
||||
import { listIMSSub } from '@/api/neData/ims_sub';
|
||||
import { listUDMAuth } from '@/api/neData/voip_auth';
|
||||
import {
|
||||
graphNodeClickID,
|
||||
graphState,
|
||||
@@ -36,6 +38,43 @@ const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { wsSend, userActivitySend, upfTFSend, reSendUPF } = useWS();
|
||||
|
||||
// const viewportDom = ref<HTMLElement | null>(null);
|
||||
//
|
||||
// // 定义 resize 处理函数,确保能正确移除事件监听器
|
||||
// const handleResize = () => {
|
||||
// setTimeout(calculateScale, 100);
|
||||
// };
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// // 计算缩放比例的函数
|
||||
// const calculateScale = () => {
|
||||
// const container = document.querySelector('.dashboard-container') as HTMLElement;
|
||||
// const wrapper = document.querySelector('.dashboard-wrapper') as HTMLElement;
|
||||
//
|
||||
// if (!container || !wrapper) return;
|
||||
//
|
||||
// const containerWidth = 1400; // 设计宽度
|
||||
// const containerHeight = 900; // 设计高度
|
||||
//
|
||||
// // 获取可用空间(减去padding)
|
||||
// const availableWidth = wrapper.clientWidth - 40;
|
||||
// const availableHeight = wrapper.clientHeight - 40;
|
||||
//
|
||||
// // 计算缩放比例
|
||||
// const scaleX = availableWidth / containerWidth;
|
||||
// const scaleY = availableHeight / containerHeight;
|
||||
//
|
||||
// // 选择较小的比例,确保内容完全可见
|
||||
// const scale = Math.min(scaleX, scaleY);
|
||||
//
|
||||
//
|
||||
// container.style.transform = `scale(${scale})`;
|
||||
// container.style.transformOrigin = 'center center';
|
||||
// };
|
||||
|
||||
/**概览状态类型 */
|
||||
type SkimStateType = {
|
||||
/**UDM签约用户数量 */
|
||||
@@ -52,6 +91,10 @@ type SkimStateType = {
|
||||
enbNum: number;
|
||||
/**4G在线用户数量 */
|
||||
enbUeNum: number;
|
||||
/**IMS用户数量 */
|
||||
imsUserNum: number;
|
||||
/**VOIP用户数量 */
|
||||
voipUserNum: number;
|
||||
};
|
||||
|
||||
/**概览状态信息 */
|
||||
@@ -63,6 +106,8 @@ let skimState: SkimStateType = reactive({
|
||||
gnbUeNum: 0,
|
||||
enbNum: 0,
|
||||
enbUeNum: 0,
|
||||
imsUserNum: 0,
|
||||
voipUserNum: 0,
|
||||
});
|
||||
|
||||
/**网元参数 */
|
||||
@@ -84,20 +129,22 @@ function fnGetNeState() {
|
||||
// 获取节点状态
|
||||
for (const node of graphState.data.nodes) {
|
||||
if (notNeNodes.includes(node.id)) continue;
|
||||
const { neType, neId } = node.neInfo;
|
||||
if (!neType || !neId) continue;
|
||||
// 请求标记检查避免重复发送
|
||||
if (neStateRequestMap.value.get(neType)) continue;
|
||||
neStateRequestMap.value.set(neType, true);
|
||||
|
||||
wsSend({
|
||||
requestId: `neState_${neType}_${neId}`,
|
||||
type: 'ne_state',
|
||||
data: {
|
||||
neType: neType,
|
||||
neId: neId,
|
||||
},
|
||||
});
|
||||
const neInfoList = node.neInfoList || [];
|
||||
if (neInfoList.length === 0) continue;
|
||||
|
||||
for (const neInfo of neInfoList) {
|
||||
if (!neInfo.neType || !neInfo.neId) continue;
|
||||
|
||||
wsSend({
|
||||
requestId: `neState_${neInfo.neType}_${neInfo.neId}`,
|
||||
type: 'ne_state',
|
||||
data: {
|
||||
neType: neInfo.neType,
|
||||
neId: neInfo.neId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,9 +216,9 @@ async function fnGetSkim() {
|
||||
const handler = neHandlers.get(child.neType);
|
||||
return handler
|
||||
? {
|
||||
promise: handler.request(child.neId),
|
||||
process: handler.process,
|
||||
}
|
||||
promise: handler.request(child.neId),
|
||||
process: handler.process,
|
||||
}
|
||||
: null;
|
||||
})
|
||||
.filter(Boolean) || []
|
||||
@@ -181,17 +228,21 @@ async function fnGetSkim() {
|
||||
|
||||
// 重置
|
||||
Object.assign(skimState, {
|
||||
udmSubNum: 0,
|
||||
// udmSubNum: 0,
|
||||
smfUeNum: 0,
|
||||
imsUeNum: 0,
|
||||
gnbNum: 0,
|
||||
gnbUeNum: 0,
|
||||
enbNum: 0,
|
||||
enbUeNum: 0,
|
||||
// imsUserNum: 0,
|
||||
// voipUserNum: 0,
|
||||
});
|
||||
results.forEach((result, index) => {
|
||||
results.forEach((result: any, index: any) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
requests[index].process(result.value);
|
||||
} else {
|
||||
requests[index].process(0);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -201,6 +252,20 @@ async function fnGetSkim() {
|
||||
skimState.udmSubNum = res.total;
|
||||
}
|
||||
});
|
||||
|
||||
// IMS用户数
|
||||
listIMSSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.imsUserNum = res.total;
|
||||
}
|
||||
});
|
||||
|
||||
// VOIP用户数
|
||||
listUDMAuth({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.voipUserNum = res.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**初始数据函数 */
|
||||
@@ -215,15 +280,15 @@ function loadData() {
|
||||
interval10s.value = setInterval(() => {
|
||||
if (!interval10s.value) return;
|
||||
if (upfTFActive.value === '0') {
|
||||
upfTFSend('7');
|
||||
upfTFActive.value = '7';
|
||||
} else if (upfTFActive.value === '7') {
|
||||
upfTFSend('30');
|
||||
upfTFActive.value = '30';
|
||||
} else if (upfTFActive.value === '30') {
|
||||
upfTFSend('0');
|
||||
upfTFActive.value = '0';
|
||||
}
|
||||
upfTFSend('0');
|
||||
upfTFSend('7');
|
||||
upfTFSend('30');
|
||||
}, 10_000);
|
||||
|
||||
clearInterval(interval5s.value);
|
||||
@@ -231,7 +296,7 @@ function loadData() {
|
||||
if (!interval5s.value || !initFlag) return;
|
||||
fnGetSkim(); // 获取概览信息
|
||||
fnGetNeState(); // 获取网元状态
|
||||
}, 5_000);
|
||||
}, 10_000);
|
||||
}
|
||||
|
||||
/**栏目信息跳转 */
|
||||
@@ -258,6 +323,8 @@ function fnSelectNe(value: any, option: any) {
|
||||
|
||||
let udmNeId = ref<string>('001');
|
||||
let udmOtions = ref<Record<string, any>[]>([]);
|
||||
let onlineOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**用户数量-选择UDM */
|
||||
function fnSelectUDM(e: any) {
|
||||
udmNeId.value = e.key;
|
||||
@@ -266,17 +333,49 @@ function fnSelectUDM(e: any) {
|
||||
skimState.udmSubNum = res.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 更新IMS用户数
|
||||
listIMSSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.imsUserNum = res.total;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新VOIP用户数
|
||||
listUDMAuth({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.voipUserNum = res.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
/**资源控制-选择NE */
|
||||
function fnSelectNeRe(e: any) {
|
||||
graphNodeClickID.value = e.key;
|
||||
}
|
||||
//
|
||||
// 定义一个方法返回 views 容器
|
||||
const getPopupContainer = () => {
|
||||
// 使用 ref 或其他方式来引用你的 views 容器
|
||||
// 如果 views 容器直接在这个组件内部,你可以使用 ref
|
||||
// 但在这个例子中,我们假设它是通过类名来获取的
|
||||
// return document.querySelector('.dashboard-wrapper');
|
||||
return document.querySelector('.viewport');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
|
||||
// // 使用自定义缩放方案
|
||||
// nextTick(() => {
|
||||
// // 确保DOM完全渲染
|
||||
// setTimeout(() => {
|
||||
// calculateScale();
|
||||
// }, 100);
|
||||
//
|
||||
// // 监听窗口大小变化
|
||||
// window.addEventListener('resize', handleResize);
|
||||
// });
|
||||
|
||||
neInfoStore
|
||||
.fnNelist()
|
||||
.then(res => {
|
||||
@@ -294,19 +393,47 @@ onMounted(() => {
|
||||
//queryParams.neRealId = arr[0].value;
|
||||
fnSelectNe(arr[0].value, arr[0]);
|
||||
}
|
||||
//online Ne
|
||||
let onlineArr: Record<string, any>[] = [];
|
||||
|
||||
// UDM
|
||||
let arr1: Record<string, any>[] = [];
|
||||
res.data.forEach((v: any) => {
|
||||
if (
|
||||
v.status &&
|
||||
[
|
||||
'UDM',
|
||||
'UPF',
|
||||
'AUSF',
|
||||
'PCF',
|
||||
'SMF',
|
||||
'AMF',
|
||||
'OMC',
|
||||
'SMSC',
|
||||
'IMS',
|
||||
'MME',
|
||||
].includes(v.neType)
|
||||
) {
|
||||
onlineArr.push({
|
||||
value: v.neType + '_' + v.neId,
|
||||
label: v.neName,
|
||||
rmUid: v.rmUid,
|
||||
});
|
||||
}
|
||||
if (v.neType === 'UDM') {
|
||||
arr1.push({ value: v.neId, label: v.neName, rmUid: v.rmUid });
|
||||
}
|
||||
});
|
||||
udmOtions.value = arr1;
|
||||
onlineOtions.value = onlineArr;
|
||||
if (arr1.length > 0) {
|
||||
fnSelectUDM({ key: arr1[0].value });
|
||||
}
|
||||
|
||||
if (onlineArr.length > 0) {
|
||||
fnSelectNeRe({ key: onlineArr[0].value });
|
||||
}
|
||||
|
||||
// 过滤不可用的网元
|
||||
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter(
|
||||
(item: any) => {
|
||||
@@ -342,36 +469,61 @@ onBeforeUnmount(() => {
|
||||
clearInterval(interval5s.value);
|
||||
interval5s.value = null;
|
||||
initFlag = false;
|
||||
// // 清理事件监听和样式
|
||||
// window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="viewport" ref="viewportDom">
|
||||
<div class="brand">
|
||||
<div
|
||||
class="brand-title"
|
||||
@click="toggle"
|
||||
:title="t('views.dashboard.overview.fullscreen')"
|
||||
>
|
||||
{{ t('views.dashboard.overview.title') }}
|
||||
<FullscreenExitOutlined v-if="isFullscreen" />
|
||||
<FullscreenOutlined v-else />
|
||||
</div>
|
||||
<div class="viewport" ref="viewportDom">
|
||||
<div class="brand">
|
||||
<div
|
||||
class="brand-title"
|
||||
@click="toggle"
|
||||
:title="t('views.dashboard.overview.fullscreen')"
|
||||
>
|
||||
{{ t('views.dashboard.overview.title') }}
|
||||
<FullscreenExitOutlined v-if="isFullscreen" />
|
||||
<FullscreenOutlined v-else />
|
||||
</div>
|
||||
<div class="brand-desc">{{ appStore.appName }}</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<!--概览-->
|
||||
<div class="skim panel">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<div class="inner" style="padding: 0.8rem 1.5rem">
|
||||
<h3 style="display: flex; align-items: center; justify-content: space-between; margin: 0 0 0.3rem 0; padding: 0; line-height: 1; height: 20px; font-size: 0.833rem">
|
||||
<span style="display: flex; align-items: center">
|
||||
<IdcardOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.skim.userTitle') }}
|
||||
</span>
|
||||
<a-dropdown
|
||||
:trigger="['click']"
|
||||
:get-Popup-Container="getPopupContainer"
|
||||
style="margin-left: 8px"
|
||||
>
|
||||
<div class="toDeep-text" style="padding: 2px 6px; font-size: 0.7rem; line-height: 1; height: 16px; display: inline-flex; align-items: center">
|
||||
{{ udmOtions.find(item => item.value === udmNeId)?.label || 'UDM' }}
|
||||
<DownOutlined style="margin-left: 4px; font-size: 9px" />
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnSelectUDM">
|
||||
<a-menu-item
|
||||
v-for="v in udmOtions"
|
||||
:key="v.value"
|
||||
:disabled="udmNeId === v.value"
|
||||
>
|
||||
{{ v.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</h3>
|
||||
<div class="data">
|
||||
<div
|
||||
class="item toRouter"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
:title="t('views.dashboard.overview.Users')"
|
||||
>
|
||||
<div @click="fnToRouter('Sub_2010')">
|
||||
<UserOutlined
|
||||
@@ -379,54 +531,64 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
{{ skimState.udmSubNum }}
|
||||
</div>
|
||||
<span>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<div class="toDeep-text">
|
||||
{{ t('views.dashboard.overview.skim.users') }}
|
||||
<DownOutlined style="margin-left: 12px; font-size: 12px" />
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnSelectUDM">
|
||||
<a-menu-item v-for="v in udmOtions" :key="v.value" :disabled="udmNeId === v.value">
|
||||
{{ v.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<span>{{ t('views.dashboard.overview.skim.users') }}
|
||||
<!-- <a-dropdown-->
|
||||
<!-- :trigger="['click']"-->
|
||||
<!-- :get-Popup-Container="getPopupContainer"-->
|
||||
<!-- >-->
|
||||
<!-- <div class="toDeep-text">-->
|
||||
<!-- {{ t('views.dashboard.overview.skim.users') }}-->
|
||||
<!-- <DownOutlined style="margin-left: 12px; font-size: 12px" />-->
|
||||
<!-- </div>-->
|
||||
<!-- <template #overlay>-->
|
||||
<!-- <a-menu @click="fnSelectUDM">-->
|
||||
<!-- <a-menu-item-->
|
||||
<!-- v-for="v in udmOtions"-->
|
||||
<!-- :key="v.value"-->
|
||||
<!-- :disabled="udmNeId === v.value"-->
|
||||
<!-- >-->
|
||||
<!-- {{ v.label }}-->
|
||||
<!-- </a-menu-item>-->
|
||||
<!-- </a-menu>-->
|
||||
<!-- </template>-->
|
||||
<!-- </a-dropdown>-->
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ims_2080')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
@click="fnToRouter('ImsUDM_2012')"
|
||||
:title="t('views.dashboard.overview.IMSUsers')"
|
||||
style="margin: 0 12px"
|
||||
v-perms:has="['dashboard:overview:imsUeNum']"
|
||||
>
|
||||
<div>
|
||||
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px" />
|
||||
{{ skimState.imsUeNum }}
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.imsUserNum }}
|
||||
</div>
|
||||
<span>
|
||||
{{ t('views.dashboard.overview.skim.imsUeNum') }}
|
||||
{{t('views.dashboard.overview.skim.ims')}}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ue_2081')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
v-perms:has="['dashboard:overview:smfUeNum']"
|
||||
@click="fnToRouter('Voip_2011')"
|
||||
:title="t('views.dashboard.overview.VoIPUsers')"
|
||||
>
|
||||
<div>
|
||||
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px" />
|
||||
{{ skimState.smfUeNum }}
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.voipUserNum }}
|
||||
</div>
|
||||
<span>
|
||||
{{ t('views.dashboard.overview.skim.smfUeNum') }}
|
||||
{{t('views.dashboard.overview.skim.voip')}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="skim panel base" v-perms:has="['dashboard:overview:gnbBase']">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
@@ -437,7 +599,7 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
:title="t('views.dashboard.overview.FivegNodeN')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img
|
||||
@@ -451,7 +613,7 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
:title="t('views.dashboard.overview.Fiveusers')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<UserOutlined
|
||||
@@ -474,7 +636,7 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
:title="t('views.dashboard.overview.FourgNodeN')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img
|
||||
@@ -488,7 +650,7 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
:title="t('views.dashboard.overview.Fourusers')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<UserOutlined
|
||||
@@ -502,6 +664,41 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Online Information -->
|
||||
<div class="skim panel base" v-perms:has="['dashboard:overview:onlineInfo']">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<IdcardOutlined style="color: #68d8fe" /> {{ t('views.dashboard.overview.skim.onlineinfo') }}
|
||||
</h3>
|
||||
<div class="data">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ims_2080')"
|
||||
:title="t('views.dashboard.overview.VoNR')"
|
||||
v-perms:has="['dashboard:overview:imsUeNum']"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px; height: 2rem" />
|
||||
{{ skimState.imsUeNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.imsUeNum') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ue_2081')"
|
||||
:title="t('views.dashboard.overview.sessions')"
|
||||
v-perms:has="['dashboard:overview:smfUeNum']"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px; height: 2rem" />
|
||||
{{ skimState.smfUeNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.smfUeNum') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户行为 -->
|
||||
<div class="userActivity panel">
|
||||
<div class="inner">
|
||||
@@ -514,6 +711,33 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="userActivity panel">-->
|
||||
<!-- <div class="inner">-->
|
||||
<!-- <h3 style="display: flex; align-items: center">-->
|
||||
<!-- <DashboardOutlined style="color: #68d8fe" /> -->
|
||||
<!-- {{ t('views.dashboard.overview.resources.title') }}:-->
|
||||
<!-- <a-dropdown-->
|
||||
<!-- :trigger="['click']"-->
|
||||
<!-- :get-Popup-Container="getPopupContainer"-->
|
||||
<!-- >-->
|
||||
<!-- <div class="toDeep-text">-->
|
||||
<!-- {{ graphNodeClickID }}-->
|
||||
<!-- <DownOutlined style="margin-left: 12px; font-size: 12px" />-->
|
||||
<!-- </div>-->
|
||||
<!-- <template #overlay>-->
|
||||
<!-- <a-menu @click="fnSelectNeRe">-->
|
||||
<!-- <a-menu-item v-for="v in onlineOtions" :key="v.value">-->
|
||||
<!-- {{ v.label }}-->
|
||||
<!-- </a-menu-item>-->
|
||||
<!-- </a-menu>-->
|
||||
<!-- </template>-->
|
||||
<!-- </a-dropdown>-->
|
||||
<!-- </h3>-->
|
||||
<!-- <div class="chart">-->
|
||||
<!-- <NeResources />-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0">
|
||||
<!-- 实时流量 -->
|
||||
@@ -521,13 +745,13 @@ onBeforeUnmount(() => {
|
||||
<div class="inner">
|
||||
<h3
|
||||
class="toRouter"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
:title="t('views.dashboard.overview.UPFjump')"
|
||||
style="display: flex; align-items: center"
|
||||
>
|
||||
<AreaChartOutlined style="color: #68d8fe" />
|
||||
<span @click="fnToRouter('GoldTarget_2104')">{{
|
||||
t('views.dashboard.overview.upfFlow.title')
|
||||
}}</span>
|
||||
t('views.dashboard.overview.upfFlow.title')
|
||||
}}</span>
|
||||
<a-select
|
||||
v-model:value="upfWhoId"
|
||||
:options="neOtions"
|
||||
@@ -549,7 +773,7 @@ onBeforeUnmount(() => {
|
||||
<h3
|
||||
class="toRouter"
|
||||
@click="fnToRouter('TopologyArchitecture_2128')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
:title="t('views.dashboard.overview.Networkjump')"
|
||||
>
|
||||
<span>
|
||||
<ApartmentOutlined style="color: #68d8fe" />
|
||||
@@ -625,8 +849,8 @@ onBeforeUnmount(() => {
|
||||
<div class="inner">
|
||||
<h3
|
||||
class="toRouter"
|
||||
@click="fnToRouter('HistoryAlarm_2097')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
@click="fnToRouter('ActiveAlarm_2088')"
|
||||
:title="t('views.dashboard.overview.Alarmjump')"
|
||||
>
|
||||
<PieChartOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
|
||||
@@ -639,22 +863,62 @@ onBeforeUnmount(() => {
|
||||
<!-- 资源情况 -->
|
||||
<div class="resources panel">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<h3 style="display: flex; align-items: center">
|
||||
<DashboardOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.resources.title') }}:
|
||||
{{ graphNodeClickID }}
|
||||
<a-dropdown
|
||||
:trigger="['click']"
|
||||
:get-Popup-Container="getPopupContainer"
|
||||
>
|
||||
<div class="toDeep-text">
|
||||
{{ graphNodeClickID }}
|
||||
<DownOutlined style="margin-left: 12px; font-size: 12px" />
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnSelectNeRe">
|
||||
<a-menu-item v-for="v in onlineOtions" :key="v.value">
|
||||
{{ v.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</h3>
|
||||
<div class="chart">
|
||||
<NeResources />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('./css/index.css');
|
||||
|
||||
/* Dashboard 页面专用包装器,不影响其他页面 */
|
||||
//.dashboard-wrapper {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// background: #0b1023;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// padding: 20px;
|
||||
// overflow: hidden;
|
||||
// box-sizing: border-box;
|
||||
//}
|
||||
//
|
||||
//.dashboard-container {
|
||||
// width: 1400px;
|
||||
// height: 900px;
|
||||
// background: #0b1023;
|
||||
// position: relative;
|
||||
// overflow: visible;
|
||||
// flex-shrink: 0;
|
||||
//}
|
||||
|
||||
.toDeep {
|
||||
--editor-background-color: blue;
|
||||
}
|
||||
@@ -671,6 +935,7 @@ onBeforeUnmount(() => {
|
||||
.toDeep :deep(.ant-select-selection-item) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.toDeep-text {
|
||||
color: #4c9bfd !important;
|
||||
font-size: 0.844rem !important;
|
||||
|
||||
305
src/views/dashboard/overview2/components/AlarnTypeBar/index.vue
Normal file
@@ -0,0 +1,305 @@
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TitleComponent,
|
||||
TitleComponentOption,
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
GridComponent,
|
||||
GridComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
} from 'echarts/components';
|
||||
import {
|
||||
PieChart,
|
||||
PieSeriesOption,
|
||||
BarChart,
|
||||
BarSeriesOption,
|
||||
} from 'echarts/charts';
|
||||
import { LabelLayout } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
|
||||
import { markRaw, onMounted, ref } from 'vue';
|
||||
import { origGet, top3Sel } from '@/api/faultManage/actAlarm';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
echarts.use([
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
PieChart,
|
||||
BarChart,
|
||||
CanvasRenderer,
|
||||
LabelLayout,
|
||||
]);
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<
|
||||
| TitleComponentOption
|
||||
| TooltipComponentOption
|
||||
| GridComponentOption
|
||||
| LegendComponentOption
|
||||
| PieSeriesOption
|
||||
| BarSeriesOption
|
||||
>;
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const alarmTypeBar = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图实例对象 */
|
||||
const alarmTypeBarChart = ref<any>(null);
|
||||
|
||||
/**告警类型数据 */
|
||||
const alarmTypeType = ref<any>([
|
||||
{
|
||||
value: 0,
|
||||
name: t('views.index.Critical'),
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
name: t('views.index.Major'),
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
name: t('views.index.Minor'),
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
name: t('views.index.Warning'),
|
||||
},
|
||||
// {
|
||||
// value: 0,
|
||||
// name: t('views.index.Event'),
|
||||
// },
|
||||
]);
|
||||
|
||||
/**告警类型Top数据 */
|
||||
const alarmTypeTypeTop = ref<any>([
|
||||
{ name: 'AMF', value: 0 },
|
||||
{ name: 'UDM', value: 0 },
|
||||
{ name: 'SMF', value: 0 },
|
||||
]);
|
||||
|
||||
//
|
||||
function initPicture() {
|
||||
Promise.allSettled([origGet(), top3Sel()])
|
||||
.then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
const res0 = resArr[0].value;
|
||||
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
|
||||
for (const item of res0.data) {
|
||||
let index = 0;
|
||||
switch (item.name) {
|
||||
case 'Critical':
|
||||
index = 0;
|
||||
break;
|
||||
case 'Major':
|
||||
index = 1;
|
||||
break;
|
||||
case 'Minor':
|
||||
index = 2;
|
||||
break;
|
||||
case 'Warning':
|
||||
index = 3;
|
||||
break;
|
||||
// case 'Event':
|
||||
// index = 4;
|
||||
// break;
|
||||
}
|
||||
alarmTypeType.value[index].value = Number(item.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
const res1 = resArr[1].value;
|
||||
if (res1.code === RESULT_CODE_SUCCESS && Array.isArray(res1.data)) {
|
||||
alarmTypeTypeTop.value = alarmTypeTypeTop.value
|
||||
.concat(res1.data)
|
||||
.sort((a: any, b: any) => {
|
||||
return b.value - a.value;
|
||||
})
|
||||
.slice(0, 3);
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
const optionData: EChartsOption = {
|
||||
title: [
|
||||
{
|
||||
text: 'Top3',
|
||||
left: 'center',
|
||||
top: '36%',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
],
|
||||
grid: [
|
||||
{ // 主图
|
||||
top: '5%',
|
||||
left: '20%',
|
||||
right: '10%',
|
||||
height: '35%'
|
||||
},
|
||||
{ // Top3
|
||||
top: '50%',
|
||||
left: '20%',
|
||||
right: '10%',
|
||||
height: '30%'
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
|
||||
axisPointer: { type: 'shadow' },
|
||||
formatter: '{b} : {c}',
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
gridIndex: 0,
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
gridIndex: 1,
|
||||
show: false,
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
gridIndex: 0,
|
||||
data: alarmTypeType.value.map((item: any) => item.name),
|
||||
axisLabel: { color: '#fff', fontSize: 14,fontWeight: 'bold' },
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
inverse: true
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
gridIndex: 1,
|
||||
data: alarmTypeTypeTop.value.map((item: any) => item.name),
|
||||
axisLabel: { color: '#fff', fontSize: 14,fontWeight: 'bold' },
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
inverse: true
|
||||
}
|
||||
],
|
||||
series: [
|
||||
// 四类型告警横向柱状图
|
||||
{
|
||||
type: 'bar',
|
||||
xAxisIndex: 0,
|
||||
yAxisIndex: 0,
|
||||
barWidth: 18,
|
||||
itemStyle: {
|
||||
borderRadius: [0, 8, 8, 0],
|
||||
color: function (params: any) {
|
||||
// 渐变色
|
||||
const colorArr = [
|
||||
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#f5222d' },
|
||||
{ offset: 1, color: '#fa8c16' }
|
||||
]),
|
||||
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#fa8c16' },
|
||||
{ offset: 1, color: '#fadb14' }
|
||||
]),
|
||||
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#fadb14' },
|
||||
{ offset: 1, color: '#1677ff' }
|
||||
]),
|
||||
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#1677ff' },
|
||||
{ offset: 1, color: '#00fcff' }
|
||||
])
|
||||
];
|
||||
return colorArr[params.dataIndex] || colorArr[3];
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: '#fff', //淡蓝色
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
formatter: (params: any) => {
|
||||
if (!params.value) return '';
|
||||
return `${params.value}`;
|
||||
},
|
||||
},
|
||||
data: alarmTypeType.value.map((item: any) => item.value),
|
||||
zlevel: 2
|
||||
},
|
||||
// Top3横向柱状图
|
||||
{
|
||||
name: 'Top3',
|
||||
type: 'bar',
|
||||
xAxisIndex: 1,
|
||||
yAxisIndex: 1,
|
||||
barWidth: 18,
|
||||
itemStyle: {
|
||||
borderRadius: [0, 20, 20, 0], // 圆角(左上、右上、右下、左下)
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#f0f5ff' },
|
||||
{ offset: 0.5, color: '#adc6ff' },
|
||||
{ offset: 1, color: '#2f54eb' },
|
||||
]), // 渐变
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: '#fff', //淡蓝色
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
formatter: '{c}'
|
||||
},
|
||||
data: alarmTypeTypeTop.value.map((item: any) => item.value),
|
||||
zlevel: 1
|
||||
}
|
||||
]
|
||||
};
|
||||
fnDesign(alarmTypeBar.value, optionData);
|
||||
});
|
||||
}
|
||||
|
||||
function fnDesign(container: HTMLElement | undefined, option: any) {
|
||||
if (!container) return;
|
||||
|
||||
alarmTypeBarChart.value = markRaw(echarts.init(container, 'light'));
|
||||
option && alarmTypeBarChart.value.setOption(option);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (alarmTypeBarChart.value) {
|
||||
alarmTypeBarChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initPicture();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="alarmTypeBar" class="chart-container"></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-container {
|
||||
/* 设置图表容器大小和位置 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
268
src/views/dashboard/overview2/components/IMSActivity/index.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<script setup lang="ts">
|
||||
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
|
||||
import { eventData, eventId } from '../../hooks/useUserActivity';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { onMounted, reactive } from 'vue';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCode: DictType[];
|
||||
/**CDR 呼叫类型 */
|
||||
cdrCallType: DictType[];
|
||||
/**UE 事件认证代码类型 */
|
||||
ueAauthCode: DictType[];
|
||||
/**UE 事件类型 */
|
||||
ueEventType: DictType[];
|
||||
/**UE 事件CM状态 */
|
||||
ueEventCmState: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
ueAauthCode: [],
|
||||
ueEventType: [],
|
||||
ueEventCmState: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('cdr_sip_code'),
|
||||
getDict('cdr_call_type'),
|
||||
getDict('ue_auth_code'),
|
||||
getDict('ue_event_type'),
|
||||
getDict('ue_event_cm_state'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.ueAauthCode = resArr[2].value;
|
||||
}
|
||||
if (resArr[3].status === 'fulfilled') {
|
||||
dict.ueEventType = resArr[3].value;
|
||||
}
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[4].value;
|
||||
}
|
||||
if (resArr[5].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[5].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="activty">
|
||||
<template v-for="item in eventData" :key="item.eId">
|
||||
<!-- CDR事件IMS -->
|
||||
<div class="card-cdr" :class="{ active: item.eId === eventId }" v-if="item.eType === 'ims_cdr'">
|
||||
<div class="card-cdr-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.cdrCallType" :value="item.data.callType" />
|
||||
</span>
|
||||
</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="card-cdr-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.caller') }}:
|
||||
<span :title="item.data.callerParty">
|
||||
{{ item.data.callerParty }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.called') }}:
|
||||
<span :title="item.data.calledParty">
|
||||
{{ item.data.calledParty }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.data.callType !== 'sms'">
|
||||
{{ t('views.dashboard.overview.userActivity.duration') }}:
|
||||
<span>{{ parseDuration(item.data.callDuration) }}</span>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<span :title="item.data.releaseTime">
|
||||
{{
|
||||
typeof item.data.releaseTime === 'number'
|
||||
? parseDateToStr(+item.data.releaseTime * 1000)
|
||||
: parseDateToStr(item.data.releaseTime)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span v-if="item.data.callType !== 'sms'">
|
||||
<DictTag :options="dict.cdrSipCode" :value="item.data.cause" value-default="0" />
|
||||
<DictTag :options="dict.cdrSipCodeCause" :value="item.data.cause" value-default="0" />
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.overview.userActivity.resultOK') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.activty {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 94%;
|
||||
color: #61a8ff;
|
||||
font-size: 0.75rem;
|
||||
|
||||
& .card-ue {
|
||||
border: 1px #61a8ff solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
line-height: 1rem;
|
||||
|
||||
& span {
|
||||
color: #68d8fe;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
&>div {
|
||||
width: 50%;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&-w33 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
&>div {
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .card-cdr {
|
||||
border: 1px #61a8ff solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
line-height: 1rem;
|
||||
|
||||
& span {
|
||||
color: #68d8fe;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
&>div {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .active {
|
||||
color: #faad14;
|
||||
border: 1px #faad14 solid;
|
||||
animation: backInRight 0.3s alternate;
|
||||
|
||||
& span {
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
|
||||
/* 兼容当行显示字内容 */
|
||||
@media (max-width: 1720px) {
|
||||
& .card-cdr {
|
||||
&-item {
|
||||
display: block;
|
||||
|
||||
&>div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .card-ue {
|
||||
&-item {
|
||||
display: block;
|
||||
|
||||
&>div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改滚动条的样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
/* 设置滚动条宽度 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: #101129;
|
||||
/* 设置滚动条轨道背景颜色 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #28293f;
|
||||
/* 设置滚动条滑块颜色 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #68d8fe;
|
||||
/* 设置鼠标悬停时滚动条滑块颜色 */
|
||||
}
|
||||
|
||||
@keyframes backInRight {
|
||||
0% {
|
||||
opacity: 0.7;
|
||||
-webkit-transform: translateX(2000px) scale(0.7);
|
||||
transform: translateX(2000px) scale(0.7);
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 0.7;
|
||||
-webkit-transform: translateX(0) scale(0.7);
|
||||
transform: translateX(0) scale(0.7);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
352
src/views/dashboard/overview2/components/NeResources/index.vue
Normal file
@@ -0,0 +1,352 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, nextTick, watch } from 'vue';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { GridComponent, GridComponentOption, TooltipComponent } from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { graphNodeClickID, graphNodeState } from '../../hooks/useTopology';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { markRaw } from 'vue';
|
||||
// 引入液体填充图表
|
||||
import 'echarts-liquidfill';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
echarts.use([GridComponent, TooltipComponent, CanvasRenderer]);
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<GridComponentOption>;
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const neResourcesDom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图实例对象 */
|
||||
const neResourcesChart = ref<any>(null);
|
||||
|
||||
// 当前选中的网元ID
|
||||
const currentNeId = ref('');
|
||||
|
||||
// 资源数据
|
||||
const resourceData = ref({
|
||||
neCpu: 1,
|
||||
sysCpu: 1,
|
||||
sysMem: 1,
|
||||
sysDisk: 1
|
||||
});
|
||||
|
||||
// 获取颜色
|
||||
function getColorByValue(value: number) {
|
||||
if (value >= 70) {
|
||||
return ['#f5222d', '#ff7875']; // 红色
|
||||
} else if (value >= 30) {
|
||||
return ['#2f54eb', '#597ef7']; // 蓝色
|
||||
} else {
|
||||
return ['#52c41a', '#95de64']; // 绿色
|
||||
}
|
||||
}
|
||||
|
||||
/**图数据 */
|
||||
const optionData: any = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c}%'
|
||||
},
|
||||
grid: {
|
||||
top: '10%',
|
||||
bottom: '5%',
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
containLabel: true
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'liquidFill',
|
||||
radius: '50%',
|
||||
center: ['15%', '35%'],
|
||||
data: [resourceData.value.neCpu / 100],
|
||||
name: t('views.dashboard.overview.resources.neCpu'),
|
||||
color: getColorByValue(resourceData.value.neCpu),
|
||||
backgroundStyle: {
|
||||
color: 'rgba(10, 60, 160, 0.1)'
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
show: true,
|
||||
borderDistance: 2,
|
||||
itemStyle: {
|
||||
borderColor: '#0a3ca0',
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'liquidFill',
|
||||
radius: '50%',
|
||||
center: ['85%', '35%'],
|
||||
data: [resourceData.value.sysCpu / 100],
|
||||
name: t('views.dashboard.overview.resources.sysCpu'),
|
||||
color: getColorByValue(resourceData.value.sysCpu),
|
||||
backgroundStyle: {
|
||||
color: 'rgba(10, 60, 160, 0.1)'
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
show: true,
|
||||
borderDistance: 2,
|
||||
itemStyle: {
|
||||
borderColor: '#0a3ca0',
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'liquidFill',
|
||||
radius: '50%',
|
||||
center: ['35%', '65%'],
|
||||
data: [resourceData.value.sysMem / 100],
|
||||
name: t('views.dashboard.overview.resources.sysMem'),
|
||||
color: getColorByValue(resourceData.value.sysMem),
|
||||
backgroundStyle: {
|
||||
color: 'rgba(10, 60, 160, 0.1)'
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
show: true,
|
||||
borderDistance: 2,
|
||||
itemStyle: {
|
||||
borderColor: '#0a3ca0',
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'liquidFill',
|
||||
radius: '50%',
|
||||
center: ['65%', '65%'],
|
||||
data: [resourceData.value.sysDisk / 100],
|
||||
name: t('views.dashboard.overview.resources.sysDisk'),
|
||||
color: getColorByValue(resourceData.value.sysDisk),
|
||||
backgroundStyle: {
|
||||
color: 'rgba(10, 60, 160, 0.1)'
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
show: true,
|
||||
borderDistance: 2,
|
||||
itemStyle: {
|
||||
borderColor: '#0a3ca0',
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/**图数据渲染 */
|
||||
function handleRanderChart(
|
||||
container: HTMLElement | undefined,
|
||||
option: any
|
||||
) {
|
||||
if (!container) return;
|
||||
neResourcesChart.value = markRaw(echarts.init(container));
|
||||
option && neResourcesChart.value.setOption(option);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (neResourcesChart.value) {
|
||||
neResourcesChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
function fnChangeData(data: any[], itemID: string) {
|
||||
const neType = itemID.split('_')[0];
|
||||
const neID = itemID.split('_')[1];
|
||||
currentNeId.value = neID;
|
||||
|
||||
let info = data.find((item: any) => item.id === neType);
|
||||
if (!info || !info.neStateMap[neID]?.online) return;
|
||||
|
||||
let sysCpuUsage = 0;
|
||||
let nfCpuUsage = 0;
|
||||
if (info.neStateMap[neID].cpu) {
|
||||
nfCpuUsage = info.neStateMap[neID].cpu.nfCpuUsage;
|
||||
const nfCpu = +(info.neStateMap[neID].cpu.nfCpuUsage / 100);
|
||||
nfCpuUsage = +nfCpu.toFixed(2);
|
||||
if (nfCpuUsage > 100) {
|
||||
nfCpuUsage = 100;
|
||||
}
|
||||
|
||||
sysCpuUsage = info.neStateMap[neID].cpu.sysCpuUsage;
|
||||
let sysCpu = +(info.neStateMap[neID].cpu.sysCpuUsage / 100);
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
if (sysCpuUsage > 100) {
|
||||
sysCpuUsage = 100;
|
||||
}
|
||||
}
|
||||
|
||||
let sysMemUsage = 0;
|
||||
if (info.neStateMap[neID].mem) {
|
||||
const men = info.neStateMap[neID].mem.sysMemUsage;
|
||||
sysMemUsage = +(men / 100).toFixed(2);
|
||||
if (sysMemUsage > 100) {
|
||||
sysMemUsage = 100;
|
||||
}
|
||||
}
|
||||
|
||||
let sysDiskUsage = 0;
|
||||
if (info.neStateMap[neID].disk && Array.isArray(info.neStateMap[neID].disk.partitionInfo)) {
|
||||
let disks: any[] = info.neStateMap[neID].disk.partitionInfo;
|
||||
disks = disks.sort((a, b) => +b.used - +a.used);
|
||||
if (disks.length > 0) {
|
||||
const { total, used } = disks[0];
|
||||
sysDiskUsage = +((used / total) * 100).toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
resourceData.value = {
|
||||
neCpu: nfCpuUsage,
|
||||
sysCpu: sysCpuUsage,
|
||||
sysMem: sysMemUsage,
|
||||
sysDisk: sysDiskUsage
|
||||
};
|
||||
|
||||
// 更新图表数据
|
||||
neResourcesChart.value.setOption({
|
||||
series: [
|
||||
{
|
||||
data: [resourceData.value.neCpu / 100],
|
||||
color: getColorByValue(resourceData.value.neCpu),
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
data: [resourceData.value.sysCpu / 100],
|
||||
color: getColorByValue(resourceData.value.sysCpu),
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
data: [resourceData.value.sysMem / 100],
|
||||
color: getColorByValue(resourceData.value.sysMem),
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
data: [resourceData.value.sysDisk / 100],
|
||||
color: getColorByValue(resourceData.value.sysDisk),
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
graphNodeState,
|
||||
v => {
|
||||
fnChangeData(v, graphNodeClickID.value);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(graphNodeClickID, v => {
|
||||
fnChangeData(graphNodeState.value, v);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
handleRanderChart(neResourcesDom.value, optionData);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="resource-panel">
|
||||
<div ref="neResourcesDom" class="chart"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.resource-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: -32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.panel-title {
|
||||
font-size: 14px;
|
||||
color: #00fcff;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid rgba(10, 115, 255, 0.3);
|
||||
}
|
||||
|
||||
.chart {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
337
src/views/dashboard/overview2/components/Topology/index.vue
Normal file
@@ -0,0 +1,337 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { getGraphData } from '@/api/monitor/topology';
|
||||
import { Graph, GraphData, Tooltip } from '@antv/g6';
|
||||
import { parseBasePath } from '@/plugins/file-static-url';
|
||||
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
|
||||
import { nodeImageAnimateState } from '@/views/monitor/topologyBuild/hooks/registerNode';
|
||||
import {
|
||||
graphG6,
|
||||
graphState,
|
||||
graphNodeClickID,
|
||||
notNeNodes,
|
||||
} from '../../hooks/useTopology';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图节点展示 */
|
||||
const graphNodeTooltip = new Tooltip({
|
||||
offsetX: 10,
|
||||
offsetY: 20,
|
||||
getContent(evt) {
|
||||
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
|
||||
const { id, label, neState, neInfoList, neStateMap }: any = evt.item?.getModel();
|
||||
//console.log(neInfoList,neState,neInfoList);
|
||||
if (notNeNodes.includes(id)) {
|
||||
return `<div><span>${label || id}</span></div>`;
|
||||
}
|
||||
if (!neState) {
|
||||
return `<div><span>${label || id}</span></div>`;
|
||||
}
|
||||
|
||||
// 获取同类型网元列表
|
||||
const sameTypeNes = neInfoList || [];
|
||||
|
||||
// 如果没有网元或只有一个网元,显示原来的信息
|
||||
if (sameTypeNes.length <= 1) {
|
||||
return `
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 200px;
|
||||
"
|
||||
>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neState.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions')
|
||||
}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||
${neState.refreshTime ?? '--'}
|
||||
</span></div>
|
||||
<div>========================</div>
|
||||
<div><strong>ID:</strong><span>${neState.neId}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.name')}:</strong><span>
|
||||
${neState.neName ?? '--'}
|
||||
</span></div>
|
||||
<div><strong>IP:</strong><span>${neState.neIP ?? '--'}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||
${neState.version ?? '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||
${neState.sn ?? '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||
${neState.expire ?? '--'}
|
||||
</span></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 如果有多个网元,聚合显示
|
||||
let content = `
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
"
|
||||
>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neState.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions')
|
||||
}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||
${neState.refreshTime ?? '--'}
|
||||
</span></div>
|
||||
<div>========================</div>`;
|
||||
|
||||
// 为每个同类型网元添加信息
|
||||
sameTypeNes.forEach((ne: any, index: number) => {
|
||||
// 获取该网元的状态信息
|
||||
const neStateInfo = neStateMap?.[ne.neId] ||
|
||||
(ne.neId === neState.neId ? neState : {});
|
||||
|
||||
content += `
|
||||
<div style="margin-top: 8px;"><strong>${t('views.monitor.topology.name')}:${ne.neName || id + '_' + ne.neId}</strong></div>
|
||||
<div><strong>ID:</strong><span>${ne.neId || '--'}</span></div>
|
||||
<div><strong>IP:</strong><span>${neStateInfo.neIP || ne.neIP || '--'}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||
${neStateInfo.version || ne.version || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||
${neStateInfo.sn || ne.sn || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||
${neStateInfo.expire || ne.expire || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neStateInfo.online !== undefined
|
||||
? (neStateInfo.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions'))
|
||||
: 'undefined'
|
||||
}
|
||||
</span></div>
|
||||
${index < sameTypeNes.length - 1 ? '<div>------------------------</div>' : ''}
|
||||
`;
|
||||
});
|
||||
|
||||
content += '</div>';
|
||||
return content;
|
||||
},
|
||||
itemTypes: ['node'],
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**图数据渲染 */
|
||||
function handleRanderGraph(
|
||||
container: HTMLElement | undefined,
|
||||
data: GraphData
|
||||
) {
|
||||
if (!container) return;
|
||||
const { clientHeight, clientWidth } = container;
|
||||
|
||||
edgeLineAnimateState();
|
||||
nodeImageAnimateState();
|
||||
|
||||
const graph = new Graph({
|
||||
container: container,
|
||||
width: clientWidth,
|
||||
height: clientHeight - 36,
|
||||
fitCenter: true,
|
||||
fitView: true,
|
||||
fitViewPadding: [20],
|
||||
autoPaint: true,
|
||||
modes: {
|
||||
// default: ['drag-canvas', 'zoom-canvas'],
|
||||
},
|
||||
groupByTypes: false,
|
||||
nodeStateStyles: {
|
||||
selected: {
|
||||
fill: 'transparent',
|
||||
},
|
||||
},
|
||||
plugins: [graphNodeTooltip],
|
||||
animate: true, // 是否使用动画过度,默认为 false
|
||||
animateCfg: {
|
||||
duration: 500, // Number,一次动画的时长
|
||||
easing: 'linearEasing', // String,动画函数
|
||||
},
|
||||
});
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
|
||||
|
||||
graphG6.value = graph;
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(function (entries) {
|
||||
// 当元素大小发生变化时触发回调函数
|
||||
entries.forEach(function (entry) {
|
||||
if (!graphG6.value) {
|
||||
return;
|
||||
}
|
||||
graphG6.value.changeSize(
|
||||
entry.contentRect.width,
|
||||
entry.contentRect.height - 20
|
||||
);
|
||||
graphG6.value.fitCenter();
|
||||
});
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图组数据渲染到画布
|
||||
* @param reload 是否重载数据
|
||||
*/
|
||||
function fnGraphDataLoad(reload: boolean = false) {
|
||||
Promise.all([
|
||||
getGraphData(graphState.group),
|
||||
listAllNeInfo({
|
||||
bandStatus: false,
|
||||
}),
|
||||
])
|
||||
.then(resArr => {
|
||||
const graphRes = resArr[0];
|
||||
const neRes = resArr[1];
|
||||
if (
|
||||
graphRes.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(graphRes.data.nodes) &&
|
||||
graphRes.data.nodes.length > 0 &&
|
||||
neRes.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(neRes.data) &&
|
||||
neRes.data.length > 0
|
||||
) {
|
||||
return {
|
||||
graphData: graphRes.data,
|
||||
neList: neRes.data,
|
||||
};
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.monitor.topology.noData'),
|
||||
duration: 5,
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
if (!res) return;
|
||||
const { combos, edges, nodes } = res.graphData;
|
||||
|
||||
// 按网元类型分组
|
||||
const neTypeMap = new Map();
|
||||
res.neList.forEach(ne => {
|
||||
if (!ne.neType) return;
|
||||
if (!neTypeMap.has(ne.neType)) {
|
||||
neTypeMap.set(ne.neType, []);
|
||||
}
|
||||
neTypeMap.get(ne.neType).push(ne);
|
||||
});
|
||||
|
||||
// 节点过滤
|
||||
const nf: Record<string, any>[] = nodes.filter(
|
||||
(node: Record<string, any>) => {
|
||||
Reflect.set(node, 'neState', { online: false });
|
||||
Reflect.set(node, 'neStateMap', {}); // 初始化状态映射
|
||||
|
||||
// 图片路径处理
|
||||
if (node.img) node.img = parseBasePath(node.img);
|
||||
if (node.icon.show && node.icon?.img){
|
||||
node.icon.img = parseBasePath(node.icon.img);
|
||||
}
|
||||
|
||||
// 遍历是否有网元数据
|
||||
const nodeID: string = node.id;
|
||||
|
||||
// 处理非网元节点
|
||||
if (notNeNodes.includes(nodeID)) {
|
||||
return true;
|
||||
}
|
||||
//(neTypeMap.get(nodeID),nodeID,node.neState)
|
||||
// 处理网元节点
|
||||
if (neTypeMap.has(nodeID)) {
|
||||
// all NeInfo
|
||||
Reflect.set(node, 'neInfoList', neTypeMap.get(nodeID));
|
||||
|
||||
Reflect.set(node, 'neInfo', neTypeMap.get(nodeID)[0] || {});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// 边过滤
|
||||
const ef: Record<string, any>[] = edges.filter(
|
||||
(edge: Record<string, any>) => {
|
||||
const edgeSource: string = edge.source;
|
||||
const edgeTarget: string = edge.target;
|
||||
const hasNeS = nf.some(n => n.id === edgeSource);
|
||||
const hasNeT = nf.some(n => n.id === edgeTarget);
|
||||
if (hasNeS && hasNeT) {
|
||||
return true;
|
||||
}
|
||||
if (hasNeS && notNeNodes.includes(edgeTarget)) {
|
||||
return true;
|
||||
}
|
||||
if (hasNeT && notNeNodes.includes(edgeSource)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// 分组过滤
|
||||
combos.forEach((combo: Record<string, any>) => {
|
||||
const comboChildren: Record<string, any>[] = combo.children;
|
||||
combo.children = comboChildren.filter(c => nf.some(n => n.id === c.id));
|
||||
return combo;
|
||||
});
|
||||
|
||||
// 图数据
|
||||
graphState.data = { combos, edges: ef, nodes: nf };
|
||||
})
|
||||
.finally(() => {
|
||||
if (graphState.data.length < 0) return;
|
||||
// 重载数据
|
||||
if (reload) {
|
||||
graphG6.value.read(graphState.data);
|
||||
} else {
|
||||
handleRanderGraph(graphG6Dom.value, graphState.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnGraphDataLoad(false);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="graphG6Dom" class="chart"></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
291
src/views/dashboard/overview2/components/UPFFlow/index.vue
Normal file
@@ -0,0 +1,291 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { listKPIData } from '@/api/perfManage/goldTarget';
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
GridComponent,
|
||||
GridComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
} from 'echarts/components';
|
||||
import { LineChart, LineSeriesOption } from 'echarts/charts';
|
||||
import { UniversalTransition } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { markRaw } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow';
|
||||
import { upfWhoId } from '../../hooks/useWS';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
echarts.use([
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
LineChart,
|
||||
CanvasRenderer,
|
||||
UniversalTransition,
|
||||
]);
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<
|
||||
| TooltipComponentOption
|
||||
| GridComponentOption
|
||||
| LegendComponentOption
|
||||
| LineSeriesOption
|
||||
>;
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const upfFlow = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图实例对象 */
|
||||
const upfFlowChart = ref<any>(null);
|
||||
|
||||
function fnDesign(container: HTMLElement | undefined, option: EChartsOption) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
if (!upfFlowChart.value) {
|
||||
upfFlowChart.value = markRaw(echarts.init(container, 'light'));
|
||||
}
|
||||
|
||||
option && upfFlowChart.value.setOption(option);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (upfFlowChart.value) {
|
||||
upfFlowChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
//渲染速率图
|
||||
function handleRanderChart() {
|
||||
const { lineXTime, lineYUp, lineYDown } = upfFlowData.value;
|
||||
|
||||
var yAxisSeries: any = [
|
||||
{
|
||||
name: t('views.dashboard.overview.upfFlow.up'),
|
||||
type: 'line',
|
||||
color: 'rgba(250, 219, 20)',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(250, 219, 20, .5)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(250, 219, 20, 0.5)',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10,
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
formatter: '{b}',
|
||||
data: lineYUp,
|
||||
},
|
||||
{
|
||||
name: t('views.dashboard.overview.upfFlow.down'),
|
||||
type: 'line',
|
||||
color: 'rgba(92, 123, 217)',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(92, 123, 217, .5)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(92, 123, 217, 0.5)',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10,
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
formatter: '{b}',
|
||||
data: lineYDown,
|
||||
},
|
||||
];
|
||||
|
||||
const optionData: EChartsOption = {
|
||||
tooltip: {
|
||||
show: true, //是否显示提示框组件
|
||||
trigger: 'axis',
|
||||
//formatter:'{a0}:{c0}<br>{a1}:{c1}'
|
||||
formatter: function (param: any) {
|
||||
var tip = '';
|
||||
if (param !== null && param.length > 0) {
|
||||
tip += param[0].name + '<br />';
|
||||
for (var i = 0; i < param.length; i++) {
|
||||
tip +=
|
||||
param[i].marker +
|
||||
param[i].seriesName +
|
||||
': ' +
|
||||
param[i].value +
|
||||
'<br />';
|
||||
}
|
||||
}
|
||||
return tip;
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
data: yAxisSeries.map((s: any) => s.name),
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: 'rgb(0,253,255,0.6)',
|
||||
},
|
||||
left: 'center', // 设置图例居中
|
||||
},
|
||||
grid: {
|
||||
top: '14%',
|
||||
left: '4%',
|
||||
right: '4%',
|
||||
bottom: '16%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: lineXTime,
|
||||
axisLabel: {
|
||||
formatter: function (params: any) {
|
||||
return params;
|
||||
},
|
||||
fontSize: 14,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(0,253,255,0.6)',
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
name: '(Mbps)',
|
||||
nameTextStyle: {
|
||||
fontSize: 12, // 设置文字距离x轴的距离
|
||||
padding: [0, -10, 0, 0], // 设置名称在x轴方向上的偏移
|
||||
},
|
||||
type: 'value',
|
||||
// splitNumber: 4,
|
||||
min: 0,
|
||||
//max: 300,
|
||||
axisLabel: {
|
||||
formatter: '{value}',
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(23,255,243,0.3)',
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(0,253,255,0.6)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
series: yAxisSeries,
|
||||
};
|
||||
fnDesign(upfFlow.value, optionData);
|
||||
}
|
||||
|
||||
/**查询初始UPF数据 */
|
||||
function fnGetInitData() {
|
||||
// 查询5分钟前的
|
||||
const nowDate = new Date().getTime();
|
||||
|
||||
listKPIData({
|
||||
neType: 'UPF',
|
||||
neId: upfWhoId.value,
|
||||
startTime: nowDate - 5 * 60 * 1000,
|
||||
endTime: nowDate,
|
||||
|
||||
interval: 5, // 5秒
|
||||
sortField: 'timeGroup',
|
||||
sortOrder: 'asc',
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
for (const item of res.data) {
|
||||
upfFlowParse(item);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
handleRanderChart();
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => upfFlowData.value,
|
||||
v => {
|
||||
if (upfFlowChart.value == null) return;
|
||||
upfFlowChart.value.setOption({
|
||||
xAxis: {
|
||||
data: v.lineXTime,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: v.lineYUp,
|
||||
},
|
||||
{
|
||||
data: v.lineYDown,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
fnGetInitData();
|
||||
|
||||
// setInterval(() => {
|
||||
// upfFlowData.value.lineXTime.push(parseDateToStr(new Date()));
|
||||
// const upN3 = parseSizeFromKbs(+145452, 5);
|
||||
// upfFlowData.value.lineYUp.push(upN3[0]);
|
||||
// const downN6 = parseSizeFromKbs(+232343, 5);
|
||||
// upfFlowData.value.lineYDown.push(downN6[0]);
|
||||
|
||||
// upfFlowChart.value.setOption({
|
||||
// xAxis: {
|
||||
// data: upfFlowData.value.lineXTime,
|
||||
// },
|
||||
// series: [
|
||||
// {
|
||||
// data: upfFlowData.value.lineYUp,
|
||||
// },
|
||||
// {
|
||||
// data: upfFlowData.value.lineYDown,
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }, 5000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="upfFlow" class="chart-container"></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-container {
|
||||
/* 设置图表容器大小和位置 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
324
src/views/dashboard/overview2/components/UserActivity/index.vue
Normal file
@@ -0,0 +1,324 @@
|
||||
<script setup lang="ts">
|
||||
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
|
||||
import { eventData, eventId } from '../../hooks/useUserActivity';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { onMounted, reactive } from 'vue';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCode: DictType[];
|
||||
/**CDR 呼叫类型 */
|
||||
cdrCallType: DictType[];
|
||||
/**UE 事件认证代码类型 */
|
||||
ueAauthCode: DictType[];
|
||||
/**UE 事件类型 */
|
||||
ueEventType: DictType[];
|
||||
/**UE 事件CM状态 */
|
||||
ueEventCmState: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
ueAauthCode: [],
|
||||
ueEventType: [],
|
||||
ueEventCmState: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('cdr_sip_code'),
|
||||
getDict('cdr_call_type'),
|
||||
getDict('ue_auth_code'),
|
||||
getDict('ue_event_type'),
|
||||
getDict('ue_event_cm_state'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.ueAauthCode = resArr[2].value;
|
||||
}
|
||||
if (resArr[3].status === 'fulfilled') {
|
||||
dict.ueEventType = resArr[3].value;
|
||||
}
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[4].value;
|
||||
}
|
||||
if (resArr[5].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[5].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="activty">
|
||||
<template v-for="item in eventData" :key="item.eId">
|
||||
<!-- UE事件AMF -->
|
||||
<div
|
||||
class="card-ue"
|
||||
:class="{ active: item.eId === eventId }"
|
||||
v-if="item.eType === 'amf_ue'"
|
||||
>
|
||||
<div class="card-ue-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueEventType" :value="item.type" />
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
<div>
|
||||
GNB ID: <span>{{ item.data.gNBID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{ parseDateToStr(+item.data.timestamp * 1000) }}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueAauthCode" :value="item.data.result" />
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.type === 'detach'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
|
||||
</div>
|
||||
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- UE事件MME -->
|
||||
<div
|
||||
class="card-ue"
|
||||
:class="{ active: item.eId === eventId }"
|
||||
v-if="item.eType === 'mme_ue'"
|
||||
>
|
||||
<div class="card-ue-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||
<span v-if="item.type === 'cm-state'">
|
||||
{{
|
||||
dict.ueEventType
|
||||
.find(s => s.value === item.type)
|
||||
?.label.replace('CM', 'ECM')
|
||||
}}
|
||||
</span>
|
||||
<span v-else>
|
||||
<DictTag :options="dict.ueEventType" :value="item.type" />
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
IMSI: <span :title="item.data?.imsi">{{ item.data?.imsi }}</span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
<div>
|
||||
ENB ID: <span>{{ item.data.eNBID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{
|
||||
typeof item.data?.timestamp === 'number'
|
||||
? parseDateToStr(+item.data?.timestamp * 1000)
|
||||
: parseDateToStr(item.data?.timestamp)
|
||||
}}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueAauthCode" :value="item.data.result" />
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.type === 'detach'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
|
||||
</div>
|
||||
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.activty {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 94%;
|
||||
color: #61a8ff;
|
||||
font-size: 0.75rem;
|
||||
& .card-ue {
|
||||
border: 1px #61a8ff solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
line-height: 1rem;
|
||||
& span {
|
||||
color: #68d8fe;
|
||||
}
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
& > div {
|
||||
width: 50%;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
&-w33 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
& > div {
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .card-cdr {
|
||||
border: 1px #61a8ff solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
line-height: 1rem;
|
||||
& span {
|
||||
color: #68d8fe;
|
||||
}
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
& > div {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .active {
|
||||
color: #faad14;
|
||||
border: 1px #faad14 solid;
|
||||
animation: backInRight 0.3s alternate;
|
||||
& span {
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
|
||||
/* 兼容当行显示字内容 */
|
||||
@media (max-width: 1720px) {
|
||||
& .card-cdr {
|
||||
&-item {
|
||||
display: block;
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
& .card-ue {
|
||||
&-item {
|
||||
display: block;
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改滚动条的样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px; /* 设置滚动条宽度 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: #101129; /* 设置滚动条轨道背景颜色 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #28293f; /* 设置滚动条滑块颜色 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #68d8fe; /* 设置鼠标悬停时滚动条滑块颜色 */
|
||||
}
|
||||
|
||||
@keyframes backInRight {
|
||||
0% {
|
||||
opacity: 0.7;
|
||||
-webkit-transform: translateX(2000px) scale(0.7);
|
||||
transform: translateX(2000px) scale(0.7);
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 0.7;
|
||||
-webkit-transform: translateX(0) scale(0.7);
|
||||
transform: translateX(0) scale(0.7);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
470
src/views/dashboard/overview2/css/index.css
Normal file
@@ -0,0 +1,470 @@
|
||||
.viewport {
|
||||
/* 限定大小 */
|
||||
min-width: 1024px;
|
||||
max-width: 1920px;
|
||||
min-height: 780px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 5rem 0.833rem 0;
|
||||
line-height: 1.15;
|
||||
background-image:
|
||||
linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75)),
|
||||
url(../images/bj.png);
|
||||
height: 100vh;
|
||||
margin-bottom: -20px;
|
||||
background-size:80% 80%;
|
||||
background-attachment:fixed;
|
||||
-webkit-background-size: cover;
|
||||
}
|
||||
.column {
|
||||
flex: 3;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 边框 */
|
||||
.panel {
|
||||
box-sizing: border-box;
|
||||
border: 2px solid rgba(252, 252, 252, 0);
|
||||
border-width: 2.125rem 1.583rem 0.875rem 5.5rem;
|
||||
position: relative;
|
||||
margin-bottom: 0.833rem;
|
||||
|
||||
}
|
||||
/* 使用伪元素创建L形角框 */
|
||||
/* 添加L形角框边框效果 */
|
||||
.panel::before,
|
||||
.panel::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 左上角L形 - 相对于inner的实际边界 */
|
||||
.panel::before {
|
||||
top: -2.125rem;
|
||||
left: -5.5rem;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-top: 3px solid #4c9bfd;
|
||||
border-left: 3px solid #4c9bfd;
|
||||
}
|
||||
|
||||
/* 右下角L形 - 相对于inner的实际边界 */
|
||||
.panel::after {
|
||||
bottom: -0.875rem;
|
||||
right: -1.583rem;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-bottom: 3px solid #4c9bfd;
|
||||
border-right: 3px solid #4c9bfd;
|
||||
}
|
||||
|
||||
/* 右上角L形 - 相对于inner的实际边界 */
|
||||
.panel .inner::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-top: 3px solid #4c9bfd;
|
||||
border-right: 3px solid #4c9bfd;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 左下角L形 - 相对于inner的实际边界 */
|
||||
.panel .inner::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-bottom: 3px solid #4c9bfd;
|
||||
border-left: 3px solid #4c9bfd;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 为base模块调整角框显示 */
|
||||
/* 第一个base模块:只显示上方角框(左上角和右上角) */
|
||||
.skim.panel.base:first-of-type::after,
|
||||
.skim.panel.base:first-of-type .inner::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 第二个base模块:只显示下方角框(左下角和右下角) */
|
||||
.skim.panel.base:not(:first-of-type)::before,
|
||||
.skim.panel.base:not(:first-of-type) .inner::before {
|
||||
display: none;
|
||||
}
|
||||
.panel .inner {
|
||||
/* 装内容 */
|
||||
/* height: 60px; */
|
||||
position: absolute;
|
||||
top: -2.125rem;
|
||||
right: -1.583rem;
|
||||
bottom: -0.875rem;
|
||||
left: -5.5rem;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
.panel h3 {
|
||||
font-size: 0.833rem;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.leftright {
|
||||
width: 100%;
|
||||
min-height: 2.5rem;
|
||||
/*background: url(../images/title.png) no-repeat center center;*/
|
||||
background-size: 100%;
|
||||
color: #4c9bfd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
padding: 0.5rem 1.2rem;
|
||||
border-radius: 0.7rem 0.7rem 0 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
text-shadow: 0 1px 4px #000a;
|
||||
flex-wrap: nowrap;
|
||||
/* 保证内容不换行且居中 */
|
||||
}
|
||||
|
||||
.leftright .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
font-size: 1.2rem;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.centerStyle {
|
||||
width: 100%;
|
||||
min-height: 2.5rem;
|
||||
/*background: url(../images/title.png) no-repeat center center;*/
|
||||
background-size: 90%;
|
||||
color: #4c9bfd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
padding: 0.5rem 1.2rem;
|
||||
border-radius: 0.7rem 0.7rem 0 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
text-shadow: 0 1px 4px #000a;
|
||||
flex-wrap: nowrap;
|
||||
/* 保证内容不换行且居中 */
|
||||
}
|
||||
|
||||
.centerStyle .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
font-size: 1.2rem;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* 总览标题 */
|
||||
.brand {
|
||||
background-image: url(../images/newBrand.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
position: absolute;
|
||||
top: 0.833rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.brand .brand-title {
|
||||
color: #ffffff;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
.brand .brand-desc {
|
||||
color: #d9d9d9;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 实时流量 */
|
||||
.upfFlow {
|
||||
/* min-height: 16rem; */
|
||||
height: 40%;
|
||||
}
|
||||
.upfFlow .inner .chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: 0rem;
|
||||
}
|
||||
|
||||
/* 网络拓扑 */
|
||||
.topology {
|
||||
/* min-height: 27.8rem; */
|
||||
height: 46.4%;
|
||||
flex: 1;
|
||||
}
|
||||
.topology .inner h3 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
.topology .inner h3 .normal {
|
||||
color: #52c41a;
|
||||
font-size: 1.1rem;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.topology .inner h3 .abnormal {
|
||||
color: #f5222d;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.topology .inner .chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* 概览区域 */
|
||||
.skim {
|
||||
/* min-height: 7.78rem; */
|
||||
height: 14.4%;
|
||||
}
|
||||
.skim .inner .data {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 90%;
|
||||
}
|
||||
.skim .inner .data .item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
width: 33%;
|
||||
}
|
||||
.skim .inner .data .item div {
|
||||
font-size: 1.467rem;
|
||||
color: #fff;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
line-height: 2rem;
|
||||
}
|
||||
.skim .inner .data .item span {
|
||||
color: #4c9bfd;
|
||||
font-size: 0.833rem;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
line-height: 2rem;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.skim .inner .data .item span::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
background-image: linear-gradient(to right, #fff, #fff0);
|
||||
height: 1px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 概览区域 衍生基站信息 */
|
||||
.skim.base {
|
||||
height: 11.25%;
|
||||
}
|
||||
|
||||
.skim.base .inner .data {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 75%;
|
||||
}
|
||||
.skim.base .inner .data .item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
/* 用户行为 */
|
||||
.userActivity {
|
||||
/* min-height: 35.8rem; */
|
||||
height: 54.6%;
|
||||
flex: 1;
|
||||
}
|
||||
.userActivity .inner .chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* 流量统计 */
|
||||
.upfFlowTotal1 {
|
||||
/* min-height: 7.5rem; */
|
||||
height: 14.4%;
|
||||
}
|
||||
.upfFlowTotal1 .inner h3 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.upfFlowTotal1 .inner h3 .filter {
|
||||
display: flex;
|
||||
}
|
||||
.upfFlowTotal1 .inner h3 .filter span {
|
||||
display: block;
|
||||
height: 0.75rem;
|
||||
line-height: 1;
|
||||
padding: 0 0.75rem;
|
||||
color: #1950c4;
|
||||
font-size: 0.75rem;
|
||||
border-right: 0.083rem solid #00f2f1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.upfFlowTotal1 .inner h3 .filter span:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
.upfFlowTotal1 .inner h3 .filter span:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
.upfFlowTotal1 .inner h3 .filter span.active {
|
||||
color: #fff;
|
||||
font-size: 0.833rem;
|
||||
}
|
||||
.upfFlowTotal1 .inner .chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
.upfFlowTotal1 .inner .chart .data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
height: 60%;
|
||||
}
|
||||
.upfFlowTotal1 .inner .chart .data .item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
.upfFlowTotal1 .inner .chart .data .item h4 {
|
||||
font-size: 1.467rem;
|
||||
color: #fff;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.upfFlowTotal1 .inner .chart .data .item span {
|
||||
color: #4c9bfd;
|
||||
font-size: 0.867rem;
|
||||
}
|
||||
|
||||
|
||||
/* 流量统计 */
|
||||
.upfFlowTotal {
|
||||
/* min-height: 7.5rem; */
|
||||
height: 14.4%;
|
||||
}
|
||||
.upfFlowTotal .inner h3 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.upfFlowTotal .inner h3 .filter {
|
||||
display: flex;
|
||||
}
|
||||
.upfFlowTotal .inner h3 .filter span {
|
||||
display: block;
|
||||
height: 0.75rem;
|
||||
line-height: 1;
|
||||
padding: 0 0.75rem;
|
||||
color: #1950c4;
|
||||
font-size: 0.75rem;
|
||||
border-right: 0.083rem solid #00f2f1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.upfFlowTotal .inner h3 .filter span:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
.upfFlowTotal .inner h3 .filter span:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
.upfFlowTotal .inner h3 .filter span.active {
|
||||
color: #fff;
|
||||
font-size: 0.833rem;
|
||||
}
|
||||
.upfFlowTotal .inner .chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
.upfFlowTotal .inner .chart .data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
height: 60%;
|
||||
}
|
||||
.upfFlowTotal .inner .chart .data .item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
.upfFlowTotal .inner .chart .data .item h4 {
|
||||
font-size: 1.467rem;
|
||||
color: #fff;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.upfFlowTotal .inner .chart .data .item span {
|
||||
color: #4c9bfd;
|
||||
font-size: 0.867rem;
|
||||
}
|
||||
|
||||
/* 资源情况 */
|
||||
.resources {
|
||||
/* min-height: 18rem; */
|
||||
height: 24.4%;
|
||||
}
|
||||
.resources .inner .chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
||||
/* 告警统计 */
|
||||
.alarmType {
|
||||
/* min-height: 25rem; */
|
||||
height: 35%;
|
||||
}
|
||||
.alarmType .inner .chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 跳转鼠标悬浮 */
|
||||
.toRouter:hover {
|
||||
cursor: pointer;
|
||||
color: #fff !important;
|
||||
}
|
||||
.toRouter:hover > *,
|
||||
.toRouter:hover > * > * {
|
||||
color: #fff !important;
|
||||
}
|
||||
197
src/views/dashboard/overview2/hooks/useTopology.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
|
||||
/**非网元元素 */
|
||||
export const notNeNodes = [
|
||||
'5GC',
|
||||
'DN',
|
||||
'UE',
|
||||
'Base',
|
||||
'lan',
|
||||
'lan1',
|
||||
'lan2',
|
||||
'lan3',
|
||||
'lan4',
|
||||
'lan5',
|
||||
'lan6',
|
||||
'lan7',
|
||||
'LAN',
|
||||
'NR',
|
||||
];
|
||||
|
||||
/**图状态 */
|
||||
export const graphState = reactive<Record<string, any>>({
|
||||
/**当前图组名 */
|
||||
group: '5GC System Architecture',
|
||||
/**图数据 */
|
||||
data: {
|
||||
combos: [],
|
||||
edges: [],
|
||||
nodes: [],
|
||||
},
|
||||
});
|
||||
|
||||
/**图实例对象 */
|
||||
export const graphG6 = ref<any>(null);
|
||||
|
||||
/**图点击选择 */
|
||||
export const graphNodeClickID = ref<string>('UPF_001');
|
||||
|
||||
/**图节点网元信息状态 */
|
||||
export const graphNodeState = computed(() =>{
|
||||
return graphState.data.nodes.map((item: any) => ({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
neInfo: item.neInfo,
|
||||
neState: item.neState,
|
||||
neInfoList:item.neInfoList,
|
||||
neStateMap: item.neStateMap,
|
||||
}))
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
/**图节点网元状态数量 */
|
||||
export const graphNodeStateNum = computed(() => {
|
||||
let normal = 0;
|
||||
let abnormal = 0;
|
||||
for (const item of graphState.data.nodes) {
|
||||
const neId = item.neState.neId;
|
||||
if (neId) {
|
||||
if (item.neState.online) {
|
||||
normal += 1;
|
||||
} else {
|
||||
abnormal += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [normal, abnormal];
|
||||
});
|
||||
|
||||
/**网元状态请求标记 */
|
||||
export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
|
||||
|
||||
/**neStateParse 网元状态 数据解析 */
|
||||
export function neStateParse(neType: string, data: Record<string, any>,neId: string) {
|
||||
// console.log('neStateParse',neType, data, neId);
|
||||
|
||||
const { combos, edges, nodes } = graphState.data;
|
||||
|
||||
const node = nodes.find((item: Record<string, any>) => item.id === neType);
|
||||
//console.log('neStateParse',node);
|
||||
|
||||
if (!node) return;
|
||||
|
||||
// 初始化状态映射
|
||||
if (!node.neStateMap) node.neStateMap = {};
|
||||
|
||||
// 更新网元状态
|
||||
const newNeState :any = {
|
||||
...data, // 先展开data对象
|
||||
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
|
||||
online: !!data.cpu,
|
||||
neId: neId
|
||||
};
|
||||
// 如果是001,更新节点状态。neInfo为主要的网元信息
|
||||
if (node.neInfo && node.neInfo.neId === neId) {
|
||||
Object.assign(node.neState, newNeState);
|
||||
}
|
||||
|
||||
//console.log(node.neState)
|
||||
// 无论是否为主要网元,都更新状态映射
|
||||
node.neStateMap[neId] = {...newNeState};
|
||||
// 通过 ID 查询节点实例
|
||||
const item = graphG6.value.findById(node.id);
|
||||
if (item) {
|
||||
// 检查当前节点下所有网元的状态
|
||||
const allStates = Object.values(node.neStateMap);
|
||||
// 判断状态颜色
|
||||
let stateColor = '#52c41a'; // 默认绿色(所有网元都正常)
|
||||
if (allStates.some((state: any) => !state.online)) {
|
||||
// 如果有任何一个网元不正常
|
||||
stateColor = allStates.every((state: any) => !state.online) ? '#f5222d' : '#faad14'; // 红色(全部不正常)或黄色(部分不正常)
|
||||
}
|
||||
|
||||
// 图片类型不能填充
|
||||
if (node.type && node.type.startsWith('image')) {
|
||||
// 更新节点
|
||||
if (node.label !== newNeState.neType) {
|
||||
graphG6.value.updateItem(item, {
|
||||
label: newNeState.neType,
|
||||
});
|
||||
}
|
||||
// 设置状态
|
||||
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
|
||||
} else {
|
||||
// 更新节点
|
||||
graphG6.value.updateItem(item, {
|
||||
label: newNeState.neType,
|
||||
style: {
|
||||
fill: stateColor, // 填充色
|
||||
stroke: stateColor, // 填充色
|
||||
},
|
||||
});
|
||||
// 设置状态
|
||||
graphG6.value.setItemState(item, 'stroke', newNeState.online);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 设置边状态
|
||||
for (const edge of edges) {
|
||||
const edgeSource: string = edge.source;
|
||||
const edgeTarget: string = edge.target;
|
||||
const neS = nodes.find((n: any) => n.id === edgeSource);
|
||||
const neT = nodes.find((n: any) => n.id === edgeTarget);
|
||||
// console.log(neS, edgeSource, neT, edgeTarget);
|
||||
|
||||
if (neS && neT) {
|
||||
// 通过 ID 查询节点实例
|
||||
// const item = graphG6.value.findById(edge.id);
|
||||
// console.log(
|
||||
// `${edgeSource} - ${edgeTarget}`,
|
||||
// neS.neState.online && neT.neState.online
|
||||
// );
|
||||
// const stateColor = neS.neState.online && neT.neState.online ? '#000000' : '#ff4d4f'; // 状态颜色
|
||||
// 更新边
|
||||
// graphG6.value.updateItem(item, {
|
||||
// label: `${edgeSource} - ${edgeTarget}`,
|
||||
// style: {
|
||||
// stroke: stateColor, // 填充色
|
||||
// },
|
||||
// labelCfg: {
|
||||
// style: {
|
||||
// fill: '#ffffff', // 标签文本色
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// 设置状态
|
||||
graphG6.value.setItemState(
|
||||
edge.id,
|
||||
'circle-move',
|
||||
neS.neState.online && neT.neState.online
|
||||
);
|
||||
}
|
||||
if (neS && notNeNodes.includes(edgeTarget)) {
|
||||
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
|
||||
}
|
||||
if (neT && notNeNodes.includes(edgeSource)) {
|
||||
graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
|
||||
}
|
||||
}
|
||||
|
||||
// 请求标记复位
|
||||
neStateRequestMap.value.set(neType, false);
|
||||
}
|
||||
|
||||
/**属性复位 */
|
||||
export function topologyReset() {
|
||||
graphState.data = {
|
||||
combos: [],
|
||||
edges: [],
|
||||
nodes: [],
|
||||
};
|
||||
graphG6.value = null;
|
||||
graphNodeClickID.value = 'UPF_001';
|
||||
neStateRequestMap.value = new Map();
|
||||
}
|
||||
110
src/views/dashboard/overview2/hooks/useUPFTotalFlow.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { parseSizeFromByte, parseSizeFromKbs } from '@/utils/parse-utils';
|
||||
import { ref } from 'vue';
|
||||
|
||||
type FDType = {
|
||||
/**时间 */
|
||||
lineXTime: string[];
|
||||
/**上行 N3 */
|
||||
lineYUp: number[];
|
||||
/**下行 N6 */
|
||||
lineYDown: number[];
|
||||
/**容量 */
|
||||
cap: number;
|
||||
};
|
||||
|
||||
/**UPF-流量数据 */
|
||||
export const upfFlowData = ref<FDType>({
|
||||
lineXTime: [],
|
||||
lineYUp: [],
|
||||
lineYDown: [],
|
||||
cap: 0,
|
||||
});
|
||||
|
||||
/**UPF-流量数据 数据解析 */
|
||||
export function upfFlowParse(data: Record<string, string>) {
|
||||
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup'], 'HH:mm:ss'));
|
||||
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
|
||||
upfFlowData.value.lineYUp.push(upN3[0]);
|
||||
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
|
||||
upfFlowData.value.lineYDown.push(downN6[0]);
|
||||
upfFlowData.value.cap += 1;
|
||||
// 超过 25 弹出
|
||||
if (upfFlowData.value.cap > 25) {
|
||||
upfFlowData.value.lineXTime.shift();
|
||||
upfFlowData.value.lineYUp.shift();
|
||||
upfFlowData.value.lineYDown.shift();
|
||||
upfFlowData.value.cap -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
type TFType = {
|
||||
/**上行 N3 */
|
||||
up: number;
|
||||
upFrom: string;
|
||||
/**下行 N6 */
|
||||
down: number;
|
||||
downFrom: string;
|
||||
/**请求标记 */
|
||||
requestFlag: boolean;
|
||||
};
|
||||
|
||||
/**UPF-总流量数 */
|
||||
export const upfTotalFlow = ref<Record<string, TFType>>({
|
||||
'0': {
|
||||
up: 0,
|
||||
upFrom: '0 B',
|
||||
down: 0,
|
||||
downFrom: '0 B',
|
||||
requestFlag: false,
|
||||
},
|
||||
'7': {
|
||||
up: 0,
|
||||
upFrom: '0 B',
|
||||
down: 0,
|
||||
downFrom: '0 B',
|
||||
requestFlag: false,
|
||||
},
|
||||
'30': {
|
||||
up: 0,
|
||||
upFrom: '0 B',
|
||||
down: 0,
|
||||
downFrom: '0 B',
|
||||
requestFlag: false,
|
||||
},
|
||||
});
|
||||
|
||||
/**UPF-总流量数 数据解析 */
|
||||
export function upfTFParse(day: string, data: Record<string, number>) {
|
||||
let { up, down } = data;
|
||||
upfTotalFlow.value[day] = {
|
||||
up: up,
|
||||
upFrom: parseSizeFromByte(up),
|
||||
down: down,
|
||||
downFrom: parseSizeFromByte(down),
|
||||
requestFlag: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**UPF-总流量数 选中 */
|
||||
export const upfTFActive = ref<string>('0');
|
||||
|
||||
/**属性复位 */
|
||||
export function upfTotalFlowReset() {
|
||||
upfFlowData.value = {
|
||||
lineXTime: [],
|
||||
lineYUp: [],
|
||||
lineYDown: [],
|
||||
cap: 0,
|
||||
};
|
||||
for (const key of Object.keys(upfTotalFlow.value)) {
|
||||
upfTotalFlow.value[key] = {
|
||||
up: 0,
|
||||
upFrom: '0 B',
|
||||
down: 0,
|
||||
downFrom: '0 B',
|
||||
requestFlag: false,
|
||||
};
|
||||
}
|
||||
upfTFActive.value = '0';
|
||||
}
|
||||
144
src/views/dashboard/overview2/hooks/useUserActivity.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
/**ueEventAMFParse UE会话事件AMF 数据解析 */
|
||||
function ueEventAMFParse(
|
||||
item: Record<string, any>
|
||||
): false | Record<string, any> {
|
||||
let evData: Record<string, any> = item.eventJSON;
|
||||
if (typeof evData === 'string') {
|
||||
try {
|
||||
evData = JSON.parse(evData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
eType: 'amf_ue',
|
||||
eId: `amf_ue_${item.id}_${Date.now()}`,
|
||||
eTime: +item.timestamp,
|
||||
id: item.id,
|
||||
type: item.eventType,
|
||||
data: evData,
|
||||
};
|
||||
}
|
||||
|
||||
/**ueEventMMEParse UE会话事件MME 数据解析 */
|
||||
function ueEventMMEParse(
|
||||
item: Record<string, any>
|
||||
): false | Record<string, any> {
|
||||
let evData: Record<string, any> = item.eventJSON;
|
||||
if (typeof evData === 'string') {
|
||||
try {
|
||||
evData = JSON.parse(evData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
eType: 'mme_ue',
|
||||
eId: `mme_ue_${item.id}_${Date.now()}`,
|
||||
eTime: +item.timestamp,
|
||||
id: item.id,
|
||||
type: item.eventType,
|
||||
data: evData,
|
||||
};
|
||||
}
|
||||
|
||||
/**cdrEventIMSParse CDR会话事件IMS 数据解析 */
|
||||
function cdrEventIMSParse(
|
||||
item: Record<string, any>
|
||||
): false | Record<string, any> {
|
||||
let evData: Record<string, any> = item.cdrJSON || item.CDR;
|
||||
if (typeof evData === 'string') {
|
||||
try {
|
||||
evData = JSON.parse(evData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 指定显示CDR类型MOC/MTSM
|
||||
if (!['MOC', 'MTSM'].includes(evData.recordType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
eType: 'ims_cdr',
|
||||
eId: `ims_cdr_${item.id}_${Date.now()}`,
|
||||
eTime: +item.timestamp,
|
||||
id: item.id,
|
||||
data: evData,
|
||||
};
|
||||
}
|
||||
|
||||
/**eventListParse 事件列表解析 */
|
||||
export function eventListParse(
|
||||
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
|
||||
data: any
|
||||
) {
|
||||
eventTotal.value += data.total;
|
||||
for (const item of data.rows) {
|
||||
let v: false | Record<string, any> = false;
|
||||
if (type === 'ims_cdr') {
|
||||
v = cdrEventIMSParse(item);
|
||||
}
|
||||
if (type === 'amf_ue') {
|
||||
v = ueEventAMFParse(item);
|
||||
}
|
||||
if (type === 'mme_ue') {
|
||||
v = ueEventMMEParse(item);
|
||||
}
|
||||
|
||||
if (v) {
|
||||
eventData.value.push(v);
|
||||
}
|
||||
}
|
||||
// 激活选中
|
||||
if (eventData.value.length > 0) {
|
||||
eventId.value = eventData.value[0].eId;
|
||||
}
|
||||
}
|
||||
|
||||
/**eventItemParseAndPush 事件项解析并添加 */
|
||||
export async function eventItemParseAndPush(
|
||||
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
|
||||
item: any
|
||||
) {
|
||||
let v: false | Record<string, any> = false;
|
||||
if (type === 'ims_cdr') {
|
||||
v = cdrEventIMSParse(item);
|
||||
}
|
||||
if (type === 'amf_ue') {
|
||||
v = ueEventAMFParse(item);
|
||||
}
|
||||
if (type === 'mme_ue') {
|
||||
v = ueEventMMEParse(item);
|
||||
}
|
||||
|
||||
if (v) {
|
||||
eventData.value.unshift(v);
|
||||
eventTotal.value += 1;
|
||||
eventId.value = v.eId;
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
if (eventData.value.length > 20) {
|
||||
eventData.value.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**CDR+UE事件数据 */
|
||||
export const eventData = ref<Record<string, any>[]>([]);
|
||||
/**CDR+UE事件总量 */
|
||||
export const eventTotal = ref<number>(0);
|
||||
/**CDR/UE事件推送id */
|
||||
export const eventId = ref<string>('');
|
||||
|
||||
/**属性复位 */
|
||||
export function userActivityReset() {
|
||||
eventData.value = [];
|
||||
eventTotal.value = 0;
|
||||
eventId.value = '';
|
||||
}
|
||||
221
src/views/dashboard/overview2/hooks/useWS.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import {
|
||||
eventData,
|
||||
eventListParse,
|
||||
eventItemParseAndPush,
|
||||
userActivityReset,
|
||||
} from './useUserActivity';
|
||||
import {
|
||||
upfTotalFlow,
|
||||
upfTFParse,
|
||||
upfFlowParse,
|
||||
upfTotalFlowReset,
|
||||
} from './useUPFTotalFlow';
|
||||
import { topologyReset, neStateParse, neStateRequestMap } from './useTopology';
|
||||
import PQueue from 'p-queue';
|
||||
|
||||
/**UPF-的Id */
|
||||
export const upfWhoId = ref<any>('');
|
||||
|
||||
/**websocket连接 */
|
||||
export default function useWS() {
|
||||
const ws = new WS();
|
||||
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||
|
||||
/**发消息 */
|
||||
function wsSend(data: Record<string, any>) {
|
||||
ws.send(data);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
// 网元状态
|
||||
if (requestId && requestId.startsWith('neState')) {
|
||||
const neType = requestId.split('_')[1];
|
||||
const neId = requestId.split('_')[2];
|
||||
neStateParse(neType, data,neId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 普通信息
|
||||
switch (requestId) {
|
||||
// AMF_UE会话事件
|
||||
case 'amf_1010_001':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('amf_ue', data);
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
break;
|
||||
// MME_UE会话事件
|
||||
case 'mme_1011_001':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('mme_ue', data);
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
break;
|
||||
// IMS_CDR会话事件
|
||||
case 'ims_1005_001':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('ims_cdr', data);
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
break;
|
||||
//UPF-总流量数
|
||||
case 'upf_001_0':
|
||||
upfTFParse('0', data);
|
||||
break;
|
||||
case 'upf_001_7':
|
||||
upfTFParse('7', data);
|
||||
break;
|
||||
case 'upf_001_30':
|
||||
upfTFParse('30', data);
|
||||
break;
|
||||
}
|
||||
// 订阅组信息
|
||||
if (!data?.groupId) {
|
||||
return;
|
||||
}
|
||||
switch (data.groupId) {
|
||||
// kpiEvent 指标UPF
|
||||
case '10_UPF_' + upfWhoId.value:
|
||||
if (data.data) {
|
||||
upfFlowParse(data.data);
|
||||
}
|
||||
break;
|
||||
// AMF_UE会话事件
|
||||
case '1010_001':
|
||||
if (data.data) {
|
||||
queue.add(() => eventItemParseAndPush('amf_ue', data.data));
|
||||
}
|
||||
break;
|
||||
// MME_UE会话事件
|
||||
case '1011_001':
|
||||
if (data.data) {
|
||||
queue.add(() => eventItemParseAndPush('mme_ue', data.data));
|
||||
}
|
||||
break;
|
||||
// IMS_CDR会话事件
|
||||
case '1005_001':
|
||||
if (data.data) {
|
||||
queue.add(() => eventItemParseAndPush('ims_cdr', data.data));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**UPF-总流量数 发消息*/
|
||||
function upfTFSend(day: '0' | '7' | '30') {
|
||||
// 请求标记检查避免重复发送
|
||||
if (upfTotalFlow.value[day].requestFlag) {
|
||||
return;
|
||||
}
|
||||
upfTotalFlow.value[day].requestFlag = true;
|
||||
|
||||
ws.send({
|
||||
requestId: `upf_001_${day}`,
|
||||
type: 'upf_tf',
|
||||
data: {
|
||||
neType: 'UPF',
|
||||
neId: '001',
|
||||
day: Number(day),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**userActivitySend 用户行为事件基础列表数据 发消息*/
|
||||
function userActivitySend() {
|
||||
// AMF_UE会话事件
|
||||
ws.send({
|
||||
requestId: 'amf_1010_001',
|
||||
type: 'amf_ue',
|
||||
data: {
|
||||
neType: 'AMF',
|
||||
neId: '001',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
},
|
||||
});
|
||||
// MME_UE会话事件
|
||||
ws.send({
|
||||
requestId: 'mme_1011_001',
|
||||
type: 'mme_ue',
|
||||
data: {
|
||||
neType: 'MME',
|
||||
neId: '001',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
},
|
||||
});
|
||||
// IMS_CDR会话事件
|
||||
ws.send({
|
||||
requestId: 'ims_1005_001',
|
||||
type: 'ims_cdr',
|
||||
data: {
|
||||
neType: 'IMS',
|
||||
neId: '001',
|
||||
recordType: 'MOC',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**重新发送至UPF 10_UPF_neId */
|
||||
function reSendUPF(neId: string) {
|
||||
upfWhoId.value = neId;
|
||||
//初始时时无需还原全部属性以及关闭
|
||||
if (ws.state() === WebSocket.OPEN) {
|
||||
ws.close();
|
||||
// userActivityReset();
|
||||
upfTotalFlowReset();
|
||||
neStateRequestMap.value = new Map();
|
||||
//topologyReset();
|
||||
}
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
/**订阅通道组
|
||||
*
|
||||
* 指标UPF (GroupID:10_neType_neId)
|
||||
* AMF_UE会话事件(GroupID:1010_neId)
|
||||
* MME_UE会话事件(GroupID:1011_neId)
|
||||
* IMS_CDR会话事件(GroupID:1005_neId)
|
||||
*/
|
||||
subGroupID: '10_UPF_' + neId + ',1010_001,1011_001,1005_001',
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.close();
|
||||
userActivityReset();
|
||||
upfTotalFlowReset();
|
||||
topologyReset();
|
||||
upfWhoId.value = '';
|
||||
});
|
||||
|
||||
return {
|
||||
wsSend,
|
||||
userActivitySend,
|
||||
upfTFSend,
|
||||
reSendUPF,
|
||||
};
|
||||
}
|
||||
BIN
src/views/dashboard/overview2/images/bj.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
src/views/dashboard/overview2/images/border.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/views/dashboard/overview2/images/brand.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/views/dashboard/overview2/images/line.png
Normal file
|
After Width: | Height: | Size: 237 B |
BIN
src/views/dashboard/overview2/images/newBrand.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/views/dashboard/overview2/images/rect.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/views/dashboard/overview2/images/title.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
850
src/views/dashboard/overview2/index.vue
Normal file
@@ -0,0 +1,850 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
import svgBase from '@/assets/svg/base.svg';
|
||||
import svgUserIMS from '@/assets/svg/userIMS.svg';
|
||||
import svgUserSMF from '@/assets/svg/userSMF.svg';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import Topology from './components/Topology/index.vue';
|
||||
import NeResources from './components/NeResources/index.vue';
|
||||
import UserActivity from './components/UserActivity/index.vue';
|
||||
import IMSActivity from './components/IMSActivity/index.vue';
|
||||
import AlarnTypeBar from './components/AlarnTypeBar/index.vue';
|
||||
import UPFFlow from './components/UPFFlow/index.vue';
|
||||
import { listUDMSub } from '@/api/neData/udm_sub';
|
||||
import { listUENumBySMF } from '@/api/neUser/smf';
|
||||
import { listUENumByIMS } from '@/api/neUser/ims';
|
||||
import { listBase5G } from '@/api/neUser/base5G';
|
||||
import { graphNodeClickID, graphState, notNeNodes } from './hooks/useTopology';
|
||||
import { upfTotalFlow, upfTFActive } from './hooks/useUPFTotalFlow';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import useWS from './hooks/useWS';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { useRouter } from 'vue-router';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { upfWhoId } from './hooks/useWS';
|
||||
import { listAMFNbStatelist } from '@/api/neData/amf';
|
||||
import { listMMENbStatelist } from '@/api/neData/mme';
|
||||
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { wsSend, userActivitySend, upfTFSend, reSendUPF } = useWS();
|
||||
|
||||
/**概览状态类型 */
|
||||
type SkimStateType = {
|
||||
/**UDM签约用户数量 */
|
||||
udmSubNum: number;
|
||||
/**SMF在线用户数 */
|
||||
smfUeNum: number;
|
||||
/**IMS在线用户数 */
|
||||
imsUeNum: number;
|
||||
/**5G基站数量 */
|
||||
gnbNum: number;
|
||||
/**5G在线用户数量 */
|
||||
gnbUeNum: number;
|
||||
/**4G基站数量 */
|
||||
enbNum: number;
|
||||
/**4G在线用户数量 */
|
||||
enbUeNum: number;
|
||||
/**5G用户总数量 */
|
||||
gNbSumNum: number;
|
||||
/**4G用户总数量 */
|
||||
eNbSumNum: number;
|
||||
};
|
||||
/**概览状态信息 */
|
||||
let skimState: SkimStateType = reactive({
|
||||
udmSubNum: 0,
|
||||
smfUeNum: 0,
|
||||
imsUeNum: 0,
|
||||
gnbNum: 0,
|
||||
gnbUeNum: 0,
|
||||
enbNum: 0,
|
||||
enbUeNum: 0,
|
||||
gNbSumNum: 0,
|
||||
eNbSumNum: 0,
|
||||
});
|
||||
|
||||
/**网元参数 */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**总览节点 */
|
||||
const viewportDom = ref<HTMLElement | null>(null);
|
||||
const { isFullscreen, toggle } = useFullscreen(viewportDom);
|
||||
|
||||
let initFlag = false;
|
||||
/**10s调度器 */
|
||||
const interval10s = ref<any>(null);
|
||||
|
||||
/**5s调度器 */
|
||||
const interval5s = ref<any>(null);
|
||||
|
||||
/**查询网元状态 */
|
||||
function fnGetNeState() {
|
||||
// 获取节点状态
|
||||
for (const node of graphState.data.nodes) {
|
||||
if (notNeNodes.includes(node.id)) continue;
|
||||
|
||||
const neInfoList = node.neInfoList || [];
|
||||
if (neInfoList.length === 0) continue;
|
||||
|
||||
for (const neInfo of neInfoList) {
|
||||
if (!neInfo.neType || !neInfo.neId) continue;
|
||||
|
||||
wsSend({
|
||||
requestId: `neState_${neInfo.neType}_${neInfo.neId}`,
|
||||
type: 'ne_state',
|
||||
data: {
|
||||
neType: neInfo.neType,
|
||||
neId: neInfo.neId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**获取概览信息 */
|
||||
async function fnGetSkim() {
|
||||
let tempGnbSumNum = 0;
|
||||
let tempEnbSumNum = 0;
|
||||
|
||||
const neHandlers = new Map([
|
||||
// [
|
||||
// 'UDM',
|
||||
// {
|
||||
// request: (neId: string) =>
|
||||
// listUDMSub({ neId: neId, pageNum: 1, pageSize: 1 }),
|
||||
// process: (res: any) =>
|
||||
// res.code === RESULT_CODE_SUCCESS &&
|
||||
// (skimState.udmSubNum += res.total),
|
||||
// },
|
||||
// ],
|
||||
[
|
||||
'UDM',
|
||||
{
|
||||
request: (neId: string) =>
|
||||
listUDMSub({ neId: neId, pageNum: 1, pageSize: 1 }),
|
||||
process: (res: any) =>
|
||||
res.code === RESULT_CODE_SUCCESS && (skimState.udmSubNum = res.total),
|
||||
},
|
||||
],
|
||||
[
|
||||
'SMF',
|
||||
{
|
||||
request: (neId: string) => listUENumBySMF(neId),
|
||||
process: (res: any) =>
|
||||
res.code === RESULT_CODE_SUCCESS && (skimState.smfUeNum += res.data),
|
||||
},
|
||||
],
|
||||
[
|
||||
'IMS',
|
||||
{
|
||||
request: (neId: string) => listUENumByIMS(neId),
|
||||
process: (res: any) =>
|
||||
res.code === RESULT_CODE_SUCCESS && (skimState.imsUeNum += res.data),
|
||||
},
|
||||
],
|
||||
[
|
||||
'AMF',
|
||||
{
|
||||
request: (neId: string) => listBase5G({ neType: 'AMF', neId }),
|
||||
process: async (res: any, neId: any) => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.gnbNum += res.total;
|
||||
skimState.gnbUeNum += res.rows.reduce(
|
||||
(sum: number, item: any) => sum + item.ueNum,
|
||||
0
|
||||
);
|
||||
const amfNbRes = await listAMFNbStatelist({ neId });
|
||||
if (
|
||||
amfNbRes.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(amfNbRes.data)
|
||||
) {
|
||||
// skimState.gNbSumNum += amfNbRes.data.length;
|
||||
tempGnbSumNum += amfNbRes.data.length;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'MME',
|
||||
{
|
||||
request: (neId: string) => listBase5G({ neType: 'MME', neId }),
|
||||
process: async (res: any, neId: any) => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.enbNum += res.total;
|
||||
skimState.enbUeNum += res.rows.reduce(
|
||||
(sum: number, item: any) => sum + item.ueNum,
|
||||
0
|
||||
);
|
||||
|
||||
const mmeNbRes = await listMMENbStatelist({ neId });
|
||||
if (
|
||||
mmeNbRes.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(mmeNbRes.data)
|
||||
) {
|
||||
// skimState.eNbSumNum += mmeNbRes.data.length;
|
||||
tempEnbSumNum += mmeNbRes.data.length;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
const requests = neCascaderOptions.value.flatMap(
|
||||
(ne: any) =>
|
||||
ne.children
|
||||
?.map((child: any) => {
|
||||
const handler = neHandlers.get(child.neType);
|
||||
return handler
|
||||
? {
|
||||
promise: handler.request(child.neId),
|
||||
process: handler.process,
|
||||
neId: child.neId, // 这里加上neId
|
||||
}
|
||||
: null;
|
||||
})
|
||||
.filter(Boolean) || []
|
||||
);
|
||||
|
||||
const results = await Promise.allSettled(requests.map((r: any) => r.promise));
|
||||
|
||||
// 重置
|
||||
Object.assign(skimState, {
|
||||
udmSubNum: 0,
|
||||
smfUeNum: 0,
|
||||
imsUeNum: 0,
|
||||
gnbNum: 0,
|
||||
gnbUeNum: 0,
|
||||
enbNum: 0,
|
||||
enbUeNum: 0,
|
||||
});
|
||||
const processPromises = results.map((result: any, index: any) => {
|
||||
const req = requests[index];
|
||||
if (result.status === 'fulfilled') {
|
||||
return req.process(result.value, req.neId);
|
||||
} else {
|
||||
return req.process(0, req.neId);
|
||||
}
|
||||
});
|
||||
|
||||
// 等待所有 process 执行完再赋值gNbSumNum等
|
||||
await Promise.all(processPromises);
|
||||
|
||||
skimState.gNbSumNum = tempGnbSumNum;
|
||||
skimState.eNbSumNum = tempEnbSumNum;
|
||||
|
||||
// UDM
|
||||
// UDM - 使用await确保同步处理
|
||||
// try {
|
||||
// const udmRes = await listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 });
|
||||
// if (udmRes.code === RESULT_CODE_SUCCESS) {
|
||||
// skimState.udmSubNum = udmRes.total;
|
||||
// } else {
|
||||
// skimState.udmSubNum = 0;
|
||||
// }
|
||||
// } catch (error) {
|
||||
// skimState.udmSubNum = 0;
|
||||
// }
|
||||
// UDM
|
||||
// listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
// if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// skimState.udmSubNum = res.total;
|
||||
// } else {
|
||||
// skimState.udmSubNum = 0;
|
||||
// }
|
||||
// }).catch(() => {
|
||||
// skimState.udmSubNum = 0;
|
||||
// });
|
||||
}
|
||||
|
||||
/**初始数据函数 */
|
||||
function loadData() {
|
||||
fnGetNeState(); // 获取网元状态
|
||||
userActivitySend();
|
||||
upfTFSend('0');
|
||||
upfTFSend('7');
|
||||
upfTFSend('30');
|
||||
|
||||
clearInterval(interval10s.value);
|
||||
interval10s.value = setInterval(() => {
|
||||
if (!interval10s.value) return;
|
||||
if (upfTFActive.value === '0') {
|
||||
upfTFSend('7');
|
||||
upfTFActive.value = '7';
|
||||
} else if (upfTFActive.value === '7') {
|
||||
upfTFSend('30');
|
||||
upfTFActive.value = '30';
|
||||
} else if (upfTFActive.value === '30') {
|
||||
upfTFSend('0');
|
||||
upfTFActive.value = '0';
|
||||
}
|
||||
}, 10_000);
|
||||
|
||||
clearInterval(interval5s.value);
|
||||
interval5s.value = setInterval(() => {
|
||||
if (!interval5s.value || !initFlag) return;
|
||||
fnGetSkim(); // 获取概览信息
|
||||
fnGetNeState(); // 获取网元状态
|
||||
}, 10_000);
|
||||
}
|
||||
|
||||
/**栏目信息跳转 */
|
||||
function fnToRouter(name: string, query?: any) {
|
||||
router.push({ name, query });
|
||||
}
|
||||
|
||||
/**网元参数 */
|
||||
let neOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
// UPF实时流量下拉框选择
|
||||
function fnSelectNe(value: any, option: any) {
|
||||
upfWhoId.value = value;
|
||||
reSendUPF(value);
|
||||
// upfTotalFlow.value.map((item: any) => {
|
||||
// item.requestFlag = false;
|
||||
// });
|
||||
|
||||
for (var key in upfTotalFlow.value) {
|
||||
upfTotalFlow.value[key].requestFlag = false;
|
||||
}
|
||||
// loadData();
|
||||
}
|
||||
|
||||
// UPF实时流量下拉菜单选择
|
||||
function fnSelectUPF(e: any) {
|
||||
upfWhoId.value = e.key;
|
||||
reSendUPF(e.key);
|
||||
|
||||
for (var key in upfTotalFlow.value) {
|
||||
upfTotalFlow.value[key].requestFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
let udmNeId = ref<string>('001');
|
||||
let udmOtions = ref<Record<string, any>[]>([]);
|
||||
let onlineOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**用户数量-选择UDM */
|
||||
async function fnSelectUDM(e: any) {
|
||||
udmNeId.value = e.key;
|
||||
try {
|
||||
const res = await listUDMSub({
|
||||
neId: udmNeId.value,
|
||||
pageNum: 1,
|
||||
pageSize: 1,
|
||||
});
|
||||
// listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.udmSubNum = res.total;
|
||||
} else {
|
||||
skimState.udmSubNum = 0;
|
||||
}
|
||||
// }).catch(() => {
|
||||
// skimState.udmSubNum = 0;
|
||||
// });
|
||||
} catch (error) {
|
||||
skimState.udmSubNum = 0;
|
||||
}
|
||||
}
|
||||
/**资源控制-选择NE */
|
||||
function fnSelectNeRe(e: any) {
|
||||
graphNodeClickID.value = e.key;
|
||||
}
|
||||
//
|
||||
// 定义一个方法返回 views 容器
|
||||
const getPopupContainer = () => {
|
||||
// 使用 ref 或其他方式来引用你的 views 容器
|
||||
// 如果 views 容器直接在这个组件内部,你可以使用 ref
|
||||
// 但在这个例子中,我们假设它是通过类名来获取的
|
||||
return document.querySelector('.viewport');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
neInfoStore
|
||||
.fnNelist()
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length > 0) {
|
||||
// UPF
|
||||
let arr: Record<string, any>[] = [];
|
||||
res.data.forEach(i => {
|
||||
if (i.neType === 'UPF') {
|
||||
arr.push({ value: i.neId, label: i.neName, rmUid: i.rmUid });
|
||||
}
|
||||
});
|
||||
neOtions.value = arr;
|
||||
if (arr.length > 0) {
|
||||
//queryParams.neRealId = arr[0].value;
|
||||
fnSelectNe(arr[0].value, arr[0]);
|
||||
}
|
||||
//online Ne
|
||||
let onlineArr: Record<string, any>[] = [];
|
||||
|
||||
// UDM
|
||||
let arr1: Record<string, any>[] = [];
|
||||
res.data.forEach((v: any) => {
|
||||
if (
|
||||
v.status &&
|
||||
[
|
||||
'UDM',
|
||||
'UPF',
|
||||
'AUSF',
|
||||
'PCF',
|
||||
'SMF',
|
||||
'AMF',
|
||||
'OMC',
|
||||
'SMSC',
|
||||
'IMS',
|
||||
'MME',
|
||||
].includes(v.neType)
|
||||
) {
|
||||
onlineArr.push({
|
||||
value: v.neType + '_' + v.neId,
|
||||
label: v.neName,
|
||||
rmUid: v.rmUid,
|
||||
});
|
||||
}
|
||||
if (v.neType === 'UDM') {
|
||||
arr1.push({ value: v.neId, label: v.neName, rmUid: v.rmUid });
|
||||
}
|
||||
});
|
||||
udmOtions.value = arr1;
|
||||
onlineOtions.value = onlineArr;
|
||||
// if (arr1.length > 0) {
|
||||
// fnSelectUDM({ key: arr1[0].value });
|
||||
// }
|
||||
// 确保设置正确的udmNeId
|
||||
if (arr1.length > 0) {
|
||||
udmNeId.value = arr1[0].value;
|
||||
}
|
||||
// 移除单独的fnSelectUDM调用,让fnGetSkim统一处理
|
||||
// if (arr1.length > 0) {
|
||||
// fnSelectUDM({ key: arr1[0].value });
|
||||
// }
|
||||
|
||||
if (onlineArr.length > 0) {
|
||||
fnSelectNeRe({ key: onlineArr[0].value });
|
||||
}
|
||||
|
||||
// 过滤不可用的网元
|
||||
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter(
|
||||
(item: any) => {
|
||||
return ['UDM', 'SMF', 'IMS', 'AMF', 'MME'].includes(item.value);
|
||||
}
|
||||
);
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
initFlag = true;
|
||||
fnGetSkim().then(() => {
|
||||
loadData();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(interval10s.value);
|
||||
interval10s.value = null;
|
||||
clearInterval(interval5s.value);
|
||||
interval5s.value = null;
|
||||
initFlag = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="viewport" ref="viewportDom">
|
||||
<div class="brand">
|
||||
<div
|
||||
class="brand-title"
|
||||
@click="toggle"
|
||||
:title="t('views.dashboard.overview.fullscreen')"
|
||||
>
|
||||
{{ t('views.dashboard.overview.title') }}
|
||||
<FullscreenExitOutlined v-if="isFullscreen" />
|
||||
<FullscreenOutlined v-else />
|
||||
</div>
|
||||
<div class="brand-desc">{{ appStore.appName }}</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="skim panel">
|
||||
<div class="inner">
|
||||
<h3 class="leftright">
|
||||
<span class="title">
|
||||
<IdcardOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.skim.userTitle') }}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="data">
|
||||
<div
|
||||
class="item toRouter"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div @click="fnToRouter('Sub_2010')">
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.udmSubNum }}
|
||||
</div>
|
||||
<span>
|
||||
<a-dropdown
|
||||
:trigger="['click']"
|
||||
:get-Popup-Container="getPopupContainer"
|
||||
>
|
||||
<div class="toDeep-text">
|
||||
{{ t('views.dashboard.overview.skim.users') }}
|
||||
<DownOutlined style="margin-left: 12px; font-size: 12px" />
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnSelectUDM">
|
||||
<a-menu-item
|
||||
v-for="v in udmOtions"
|
||||
:key="v.value"
|
||||
:disabled="udmNeId === v.value"
|
||||
>
|
||||
{{ v.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ims_2080')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
style="margin: 0 12px"
|
||||
v-perms:has="['dashboard:overview:imsUeNum']"
|
||||
>
|
||||
<div>
|
||||
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px" />
|
||||
{{ skimState.imsUeNum }}
|
||||
</div>
|
||||
<span>
|
||||
{{ t('views.dashboard.overview.skim.imsUeNum') }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ue_2081')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
v-perms:has="['dashboard:overview:smfUeNum']"
|
||||
>
|
||||
<div>
|
||||
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px" />
|
||||
{{ skimState.smfUeNum }}
|
||||
</div>
|
||||
<span>
|
||||
{{ t('views.dashboard.overview.skim.smfUeNum') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--告警统计-->
|
||||
<div class="alarmType panel">
|
||||
<div class="inner">
|
||||
<h3
|
||||
class="toRouter leftright"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<span class="title" @click="fnToRouter('HistoryAlarm_2097')">
|
||||
<PieChartOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="chart">
|
||||
<AlarnTypeBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户行为 -->
|
||||
<div class="userActivity panel">
|
||||
<div class="inner">
|
||||
<h3 class="leftright">
|
||||
<span class="title">
|
||||
<WhatsAppOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.userActivity.title') }}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="chart">
|
||||
<UserActivity />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0">
|
||||
<!-- 实时流量 -->
|
||||
<div class="upfFlow panel">
|
||||
<div class="inner">
|
||||
<h3 class="centerStyle">
|
||||
<span class="title">
|
||||
<div
|
||||
class="toRouter"
|
||||
@click="fnToRouter('GoldTarget_2104')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<AreaChartOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.upfFlow.title') }}
|
||||
</div>
|
||||
|
||||
<a-dropdown
|
||||
:trigger="['click']"
|
||||
:get-Popup-Container="getPopupContainer"
|
||||
>
|
||||
<div class="toDeep-text">
|
||||
{{
|
||||
neOtions.find((item: any) => item.value === upfWhoId)
|
||||
?.label || 'Select UPF'
|
||||
}}
|
||||
<DownOutlined style="margin-left: -2px; font-size: 12px" />
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnSelectUPF">
|
||||
<a-menu-item v-for="v in neOtions" :key="v.value">
|
||||
{{ v.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div class="chart">
|
||||
<UPFFlow />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网络拓扑 -->
|
||||
<div class="topology panel">
|
||||
<div class="inner">
|
||||
<h3
|
||||
class="toRouter centerStyle"
|
||||
@click="fnToRouter('TopologyArchitecture_2128')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<span class="title">
|
||||
<ApartmentOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.topology.title') }}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="chart">
|
||||
<Topology />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<!-- 基站信息 -->
|
||||
<div class="skim panel base" v-perms:has="['dashboard:overview:gnbBase']">
|
||||
<div class="inner">
|
||||
<h3 class="leftright">
|
||||
<span class="title">
|
||||
<GlobalOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.skim.nodeBInfo') }}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="data" style="margin-top: 20px">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img
|
||||
:src="svgBase"
|
||||
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||
/>
|
||||
{{ skimState.gNbSumNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.gnbSumBase') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img
|
||||
:src="svgBase"
|
||||
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||
/>
|
||||
{{ skimState.gnbNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.gnbBase') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.gnbUeNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="skim panel base" v-perms:has="['dashboard:overview:enbBase']">
|
||||
<div class="inner">
|
||||
<h3></h3>
|
||||
<div class="data" style="margin-top: 40px">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img
|
||||
:src="svgBase"
|
||||
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||
/>
|
||||
{{ skimState.eNbSumNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.enbSumBase') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img
|
||||
:src="svgBase"
|
||||
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||
/>
|
||||
{{ skimState.enbNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.enbBase') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.enbUeNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.enbUeNum') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 资源情况 -->
|
||||
<div class="resources panel">
|
||||
<div class="inner">
|
||||
<h3 class="resources leftright">
|
||||
<span class="title">
|
||||
<DashboardOutlined
|
||||
style="color: #68d8fe; font-size: 20px"
|
||||
/>
|
||||
<div style="margin-left: -3px">
|
||||
{{ t('views.dashboard.overview.resources.title') }}:
|
||||
</div>
|
||||
<a-dropdown
|
||||
:trigger="['click']"
|
||||
:get-Popup-Container="getPopupContainer"
|
||||
>
|
||||
<div class="toDeep-text">
|
||||
{{ graphNodeClickID }}
|
||||
<DownOutlined style="margin-left: -2px; font-size: 12px" />
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnSelectNeRe">
|
||||
<a-menu-item v-for="v in onlineOtions" :key="v.value">
|
||||
{{ v.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="chart">
|
||||
<NeResources />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IMS用户行为 -->
|
||||
<div class="userActivity panel">
|
||||
<div class="inner">
|
||||
<h3 class="leftright">
|
||||
<span class="title">
|
||||
<WhatsAppOutlined
|
||||
style="color: #68d8fe; font-size: 20px"
|
||||
/>
|
||||
{{ t('views.dashboard.overview.userActivity.imsTitle') }}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="chart">
|
||||
<IMSActivity />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('./css/index.css');
|
||||
|
||||
.toDeep {
|
||||
--editor-background-color: blue;
|
||||
}
|
||||
|
||||
.toDeep :deep(.ant-select-selector) {
|
||||
background-color: #050f23;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.toDeep :deep(.ant-select-arrow) {
|
||||
color: #4c9bfd;
|
||||
}
|
||||
|
||||
.toDeep :deep(.ant-select-selection-item) {
|
||||
color: #4c9bfd;
|
||||
}
|
||||
|
||||
.toDeep-text {
|
||||
color: #4c9bfd !important;
|
||||
font-size: 0.844rem !important;
|
||||
position: relative !important;
|
||||
line-height: 2rem !important;
|
||||
white-space: nowrap !important;
|
||||
text-align: start !important;
|
||||
text-overflow: ellipsis !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
</style>
|
||||
@@ -21,6 +21,9 @@ import PQueue from 'p-queue';
|
||||
import saveAs from 'file-saver';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import { dayjsRanges } from '@/hooks/useRangePicker';
|
||||
import ExportCustomModal from '@/components/ExportCustomModal/index.vue';
|
||||
import * as XLSX from 'xlsx';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
const ws = new WS();
|
||||
@@ -34,21 +37,6 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
dayjs().startOf('hour'),
|
||||
dayjs().endOf('hour'),
|
||||
]);
|
||||
/**时间范围 */
|
||||
let rangePickerPresets = ref([
|
||||
{
|
||||
label: 'Now hour',
|
||||
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
|
||||
},
|
||||
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
|
||||
{
|
||||
label: 'Yesterday',
|
||||
value: [
|
||||
dayjs().subtract(1, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
@@ -110,12 +98,12 @@ let tableState: TabeStateType = reactive({
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
// {
|
||||
// title: t('common.rowId'),
|
||||
// dataIndex: 'id',
|
||||
// align: 'left',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: t('views.dashboard.cdr.chargingID'), // 计费ID
|
||||
dataIndex: 'cdrJSON',
|
||||
@@ -376,14 +364,18 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading) return;
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.cdr.exportTip'),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageSize = 10000;
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
exportSGWCDataCDR(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -407,9 +399,243 @@ function fnExportList() {
|
||||
});
|
||||
}
|
||||
|
||||
/**自定义导出 - 先获取后端数据来确定可用列 */
|
||||
function fnExportCustom() {
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
|
||||
// 先获取后端标准格式的完整数据
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = Math.min(tablePagination.total, 10); // 只获取前10条用于分析列结构
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
|
||||
exportSGWCDataCDR(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 解析后端Excel文件,获取可用的列
|
||||
parseExcelColumns(res.data);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Export error:', error);
|
||||
message.error({
|
||||
content: t('common.operateError'),
|
||||
duration: 3,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**解析Excel获取可用的列信息 */
|
||||
function parseExcelColumns(excelBlob: Blob) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
const data = new Uint8Array(e.target?.result as ArrayBuffer);
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
|
||||
// 获取第一个工作表
|
||||
const firstSheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[firstSheetName];
|
||||
|
||||
// 将工作表转换为JSON格式
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
|
||||
if (jsonData.length === 0) {
|
||||
message.error(t('common.noData'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取表头(第一行)
|
||||
const headers = jsonData[0] as string[];
|
||||
const dataRows = jsonData.slice(1, 4); // 取前3行作为示例数据
|
||||
|
||||
// 构建可用列配置
|
||||
const availableColumns = headers.map((header, index) => ({
|
||||
key: `col_${index}`,
|
||||
title: header,
|
||||
originalTitle: header,
|
||||
dataIndex: `col_${index}`,
|
||||
columnIndex: index,
|
||||
visible: true
|
||||
}));
|
||||
|
||||
exportAvailableColumns.value = availableColumns;
|
||||
|
||||
// 构建示例数据
|
||||
const sampleData = dataRows.map((row: any) => {
|
||||
const obj: any = { id: Math.random() };
|
||||
headers.forEach((header, index) => {
|
||||
obj[`col_${index}`] = row[index] || '';
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
|
||||
exportSampleData.value = sampleData;
|
||||
|
||||
// 打开自定义导出对话框
|
||||
exportCustomVisible.value = true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Parse Excel error:', error);
|
||||
message.error(t('common.operateError'));
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(excelBlob);
|
||||
}
|
||||
|
||||
/**处理自定义导出确认 */
|
||||
function handleExportCustomConfirm(config: any[]) {
|
||||
exportCustomConfig.value = config;
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
|
||||
// 先获取后端标准格式的完整数据
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
|
||||
exportSGWCDataCDR(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 后端返回标准格式数据后,在前端进行自定义处理
|
||||
processCustomExport(res.data, config);
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Export error:', error);
|
||||
message.error({
|
||||
content: t('common.operateError'),
|
||||
duration: 3,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**处理自定义导出 - 基于后端数据在前端自定义处理 */
|
||||
function processCustomExport(excelBlob: Blob, config: any[]) {
|
||||
// 读取后端返回的Excel文件
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
const data = new Uint8Array(e.target?.result as ArrayBuffer);
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
|
||||
// 获取第一个工作表
|
||||
const firstSheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[firstSheetName];
|
||||
|
||||
// 将工作表转换为JSON格式
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
|
||||
if (jsonData.length === 0) {
|
||||
message.error(t('common.noData'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取表头(第一行)
|
||||
const originalHeaders = jsonData[0] as string[];
|
||||
const dataRows = jsonData.slice(1);
|
||||
|
||||
// 根据配置处理数据
|
||||
const processedData = processDataWithConfig(originalHeaders, dataRows, config);
|
||||
|
||||
// 生成新的Excel文件
|
||||
generateCustomExcelFile(processedData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Process custom export error:', error);
|
||||
message.error(t('common.operateError'));
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(excelBlob);
|
||||
}
|
||||
|
||||
/**根据配置处理数据 */
|
||||
function processDataWithConfig(originalHeaders: string[], dataRows: any[], config: any[]) {
|
||||
// 获取可见的列配置
|
||||
const visibleColumns = config.filter(col => col.visible);
|
||||
|
||||
// 处理表头
|
||||
const newHeaders = visibleColumns.map(col => col.title);
|
||||
|
||||
// 处理数据行 - 使用columnIndex直接访问
|
||||
const newDataRows = dataRows.map(row => {
|
||||
return visibleColumns.map(col => {
|
||||
// 使用columnIndex字段直接访问对应列的数据
|
||||
const columnIndex = col.columnIndex;
|
||||
return columnIndex !== undefined ? (row[columnIndex] || '') : '';
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
headers: newHeaders,
|
||||
data: newDataRows
|
||||
};
|
||||
}
|
||||
|
||||
/**生成自定义Excel文件 */
|
||||
function generateCustomExcelFile(processedData: { headers: string[], data: any[] }) {
|
||||
// 创建工作簿
|
||||
const wb = XLSX.utils.book_new();
|
||||
|
||||
// 准备Excel数据
|
||||
const excelData = [processedData.headers, ...processedData.data];
|
||||
|
||||
// 创建工作表
|
||||
const ws = XLSX.utils.aoa_to_sheet(excelData);
|
||||
|
||||
// 设置列宽
|
||||
const colWidths = processedData.headers.map(() => ({ wch: 20 }));
|
||||
ws['!cols'] = colWidths;
|
||||
|
||||
// 添加工作表到工作簿
|
||||
XLSX.utils.book_append_sheet(wb, ws, 'SGWC CDR');
|
||||
|
||||
// 生成Excel文件并下载
|
||||
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
|
||||
const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
|
||||
saveAs(blob, `sgwc_cdr_custom_export_${Date.now()}.xlsx`);
|
||||
}
|
||||
|
||||
/**实时数据开关 */
|
||||
const realTimeData = ref<boolean>(false);
|
||||
|
||||
/**自定义导出配置 */
|
||||
const exportCustomVisible = ref<boolean>(false);
|
||||
const exportCustomConfig = ref<any[]>([]);
|
||||
const exportAvailableColumns = ref<any[]>([]);
|
||||
const exportSampleData = ref<any[]>([]);
|
||||
|
||||
/**
|
||||
* 实时数据
|
||||
*/
|
||||
@@ -579,7 +805,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:presets="rangePickerPresets"
|
||||
:presets="dayjsRanges()"
|
||||
:bordered="true"
|
||||
:allow-clear="false"
|
||||
style="width: 100%"
|
||||
@@ -629,10 +855,25 @@ onBeforeUnmount(() => {
|
||||
{{ t('common.deleteText') }}
|
||||
</a-button>
|
||||
|
||||
<a-button type="dashed" @click.prevent="fnExportList()">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
{{ t('common.export') }}
|
||||
</a-button>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="dashed">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
{{ t('common.export') }}
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="export-default" @click="fnExportList()">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
{{ t('common.exportDefault') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="export-custom" @click="fnExportCustom()">
|
||||
<template #icon><SettingOutlined /></template>
|
||||
{{ t('common.exportCustom') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
@@ -840,6 +1081,14 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 自定义导出配置模态框 -->
|
||||
<ExportCustomModal
|
||||
v-model:open="exportCustomVisible"
|
||||
:original-columns="exportAvailableColumns"
|
||||
:sample-data="exportSampleData"
|
||||
@confirm="handleExportCustomConfirm"
|
||||
/>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -19,9 +19,12 @@ import {
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import PQueue from 'p-queue';
|
||||
import saveAs from 'file-saver';
|
||||
import { listTenant } from '@/api/system/tenant';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { parseSizeFromByte } from '@/utils/parse-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { dayjsRanges } from '@/hooks/useRangePicker';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
const ws = new WS();
|
||||
@@ -35,28 +38,16 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
dayjs().startOf('hour'),
|
||||
dayjs().endOf('hour'),
|
||||
]);
|
||||
/**时间范围 */
|
||||
let rangePickerPresets = ref([
|
||||
{
|
||||
label: 'Now hour',
|
||||
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
|
||||
},
|
||||
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
|
||||
{
|
||||
label: 'Yesterday',
|
||||
value: [
|
||||
dayjs().subtract(1, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
tenantNameArr: <Record<string, any>[]>[],
|
||||
/**网元类型 */
|
||||
neType: 'SMF',
|
||||
neId: '001',
|
||||
subscriberID: '',
|
||||
/** 租户名称*/
|
||||
tenantName: '',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**开始时间 */
|
||||
@@ -73,6 +64,10 @@ let queryParams = reactive({
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
subscriberID: '',
|
||||
/** 租户名称*/
|
||||
tenantName: '',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
@@ -107,12 +102,12 @@ let tableState: TabeStateType = reactive({
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns = ref<ColumnsType>([
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
// {
|
||||
// title: t('common.rowId'),
|
||||
// dataIndex: 'id',
|
||||
// align: 'center',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: t('views.dashboard.cdr.chargingID'), // 计费ID
|
||||
dataIndex: 'cdrJSON',
|
||||
@@ -232,12 +227,19 @@ let tableColumns = ref<ColumnsType>([
|
||||
title: t('views.dashboard.cdr.invocationTime'), // 调用时间
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
width: 250,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.invocationTimestamp;
|
||||
return parseDateToStr(cdrJSON.invocationTimestamp);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.tenantName'),
|
||||
dataIndex: 'tenantName',
|
||||
align: 'center',
|
||||
key: 'tenantName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -406,14 +408,18 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading) return;
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.cdr.exportTip'),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageSize = 10000;
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
exportSMFDataCDR(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -532,6 +538,20 @@ onMounted(() => {
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
//查询租户
|
||||
listTenant({ parentId: 0 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
|
||||
res.data.forEach((item: any) => {
|
||||
if (item.parentId === '0') {
|
||||
queryParams.tenantNameArr.push({
|
||||
value: item.tenantName,
|
||||
label: item.tenantName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -582,7 +602,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:presets="rangePickerPresets"
|
||||
:presets="dayjsRanges()"
|
||||
:bordered="true"
|
||||
:allow-clear="false"
|
||||
style="width: 100%"
|
||||
@@ -591,6 +611,17 @@ onBeforeUnmount(() => {
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.sub.tenantName')"
|
||||
name="tenantName "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.tenantName"
|
||||
:options="queryParams.tenantNameArr"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
@@ -761,7 +792,9 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||
<span>{{ record.cdrJSON.invocationTimestamp }}</span>
|
||||
<span>{{
|
||||
parseDateToStr(record.cdrJSON.invocationTimestamp)
|
||||
}}</span>
|
||||
</div>
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||
@@ -816,16 +849,22 @@ onBeforeUnmount(() => {
|
||||
</strong> -->
|
||||
<div>
|
||||
<div>
|
||||
<span>Data Total Volume: </span>
|
||||
<span>{{ udata.dataTotalVolume }}</span>
|
||||
<span>Data Volume Uplink: </span>
|
||||
<span>{{
|
||||
parseSizeFromByte(udata.dataVolumeUplink, 'MB')
|
||||
}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Data Volume Downlink: </span>
|
||||
<span>{{ udata.dataVolumeDownlink }}</span>
|
||||
<span>{{
|
||||
parseSizeFromByte(udata.dataVolumeDownlink, 'MB')
|
||||
}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Data Volume Uplink: </span>
|
||||
<span>{{ udata.dataVolumeUplink }}</span>
|
||||
<span>Data Total Volume: </span>
|
||||
<span>{{
|
||||
parseSizeFromByte(udata.dataTotalVolume, 'MB')
|
||||
}}</span>
|
||||
</div>
|
||||
<!-- <div>
|
||||
<span>Time: </span>
|
||||
|
||||
@@ -24,7 +24,14 @@ echarts.use([
|
||||
UniversalTransition,
|
||||
]);
|
||||
|
||||
import { reactive, onMounted, toRaw, onBeforeUnmount, ref } from 'vue';
|
||||
import {
|
||||
reactive,
|
||||
onMounted,
|
||||
toRaw,
|
||||
onBeforeUnmount,
|
||||
ref,
|
||||
nextTick,
|
||||
} from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
@@ -37,6 +44,8 @@ import { parseSizeFromByte } from '@/utils/parse-utils';
|
||||
import { message } from 'ant-design-vue';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { dayjsRanges } from '@/hooks/useRangePicker';
|
||||
const { t, currentLocale } = useI18n();
|
||||
const ws = new WS();
|
||||
|
||||
@@ -80,9 +89,9 @@ const option = {
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataZoom: {
|
||||
yAxisIndex: 'none',
|
||||
},
|
||||
// dataZoom: {
|
||||
// yAxisIndex: 'none',
|
||||
// },
|
||||
saveAsImage: {},
|
||||
},
|
||||
},
|
||||
@@ -216,16 +225,20 @@ const option = {
|
||||
function fnRanderChart() {
|
||||
const container: HTMLElement | undefined = cdrChartDom.value;
|
||||
if (!container) return;
|
||||
container.style.display = 'block';
|
||||
|
||||
// 如果图表已经存在,先销毁
|
||||
if (cdrChart) {
|
||||
cdrChart.dispose();
|
||||
cdrChart = null;
|
||||
}
|
||||
|
||||
const locale = currentLocale.value.split('_')[0];
|
||||
cdrChart = echarts.init(container, 'light', {
|
||||
// https://github.com/apache/echarts/tree/release/src/i18n 取值langEN.ts ==> EN
|
||||
locale: locale.toUpperCase(),
|
||||
});
|
||||
cdrChart.setOption(option);
|
||||
// cdrChart.showLoading('default', {
|
||||
// text: 'Please enter IMSI to query user traffic',
|
||||
// fontSize: 16, // 字体大小
|
||||
// });
|
||||
|
||||
// 创建 ResizeObserver 实例 监听图表容器大小变化,并在变化时调整图表大小
|
||||
var observer = new ResizeObserver(entries => {
|
||||
@@ -245,21 +258,6 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
dayjs().startOf('hour'),
|
||||
dayjs().endOf('hour'),
|
||||
]);
|
||||
/**时间范围 */
|
||||
let rangePickerPresets = ref([
|
||||
{
|
||||
label: 'Now hour',
|
||||
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
|
||||
},
|
||||
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
|
||||
{
|
||||
label: 'Yesterday',
|
||||
value: [
|
||||
dayjs().subtract(1, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
@@ -286,6 +284,15 @@ function fnQueryReset() {
|
||||
queryParams.dnn = '';
|
||||
queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')];
|
||||
fnGetList(1);
|
||||
// 重置关闭图
|
||||
ws.close();
|
||||
if (cdrChart) {
|
||||
cdrChart.clear();
|
||||
cdrChart.dispose();
|
||||
if (cdrChartDom.value) {
|
||||
cdrChartDom.value.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let state = reactive({
|
||||
@@ -303,21 +310,9 @@ let state = reactive({
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (state.loading) return;
|
||||
state.loading = true;
|
||||
if (!queryParams.subscriberID) {
|
||||
message.warning('Please enter IMSI to query user traffic');
|
||||
state.loading = false;
|
||||
return;
|
||||
}
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
if (cdrChart) {
|
||||
cdrChart.showLoading('default', {
|
||||
text: 'Loading...',
|
||||
fontSize: 16, // 字体大小
|
||||
});
|
||||
}
|
||||
|
||||
// 时间范围
|
||||
if (
|
||||
Array.isArray(queryRangePicker.value) &&
|
||||
@@ -357,9 +352,55 @@ function fnGetList(pageNum?: number) {
|
||||
})
|
||||
.finally(() => {
|
||||
state.loading = false;
|
||||
fnRanderChartDataLoad();
|
||||
|
||||
// 根据subscriberID是否为完整的15位决定是否显示图表
|
||||
// 15位subscriberID表示查询单个用户,显示图表
|
||||
// 少于15位表示模糊查询多个用户,隐藏图表
|
||||
if (queryParams.subscriberID && queryParams.subscriberID.length === 15) {
|
||||
// 只有当显示图表时才操作图表
|
||||
if (cdrChart) {
|
||||
cdrChart.showLoading('default', {
|
||||
text: 'Loading...',
|
||||
fontSize: 16, // 字体大小
|
||||
});
|
||||
}
|
||||
fnRanderChartDataLoad();
|
||||
} else {
|
||||
fnSumDataUsage();
|
||||
}
|
||||
});
|
||||
}
|
||||
// 无搜索IMSI, 累加总量
|
||||
function fnSumDataUsage() {
|
||||
// 累加总量
|
||||
let uplinkTotal = 0;
|
||||
let downlinkTotal = 0;
|
||||
for (const item of state.data) {
|
||||
if (!item.cdrJSON.invocationTimestamp) {
|
||||
continue;
|
||||
}
|
||||
const listOfMultipleUnitUsage = item.cdrJSON.listOfMultipleUnitUsage;
|
||||
if (
|
||||
!Array.isArray(listOfMultipleUnitUsage) ||
|
||||
listOfMultipleUnitUsage.length < 1
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// 数据
|
||||
for (const v of listOfMultipleUnitUsage) {
|
||||
if (Array.isArray(v.usedUnitContainer)) {
|
||||
for (const used of v.usedUnitContainer) {
|
||||
uplinkTotal += +used.dataVolumeUplink;
|
||||
downlinkTotal += +used.dataVolumeDownlink;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state.dataUsage = [
|
||||
parseSizeFromByte(uplinkTotal, 'MB'),
|
||||
parseSizeFromByte(downlinkTotal, 'MB'),
|
||||
];
|
||||
}
|
||||
|
||||
/**图表配置数据x轴 */
|
||||
let dataTimeXAxisData: string[] = [];
|
||||
@@ -368,7 +409,15 @@ let dataVolumeUplinkYSeriesData: number[] = [];
|
||||
let dataVolumeDownlinkYSeriesData: number[] = [];
|
||||
/**图表数据渲染 */
|
||||
function fnRanderChartDataLoad() {
|
||||
if (!cdrChart) return;
|
||||
// 如果需要显示图表但图表未初始化,则先初始化图表
|
||||
if (!cdrChart) {
|
||||
// 使用 nextTick 确保DOM已更新
|
||||
nextTick(() => {
|
||||
fnRanderChart();
|
||||
fnRanderChartDataLoad(); // 递归调用以继续数据加载
|
||||
});
|
||||
return;
|
||||
}
|
||||
dataTimeXAxisData = [];
|
||||
dataVolumeUplinkYSeriesData = [];
|
||||
dataVolumeDownlinkYSeriesData = [];
|
||||
@@ -379,7 +428,7 @@ function fnRanderChartDataLoad() {
|
||||
break;
|
||||
}
|
||||
// 时间
|
||||
const dataTime = item.cdrJSON.invocationTimestamp;
|
||||
const dataTime = parseDateToStr(item.cdrJSON.invocationTimestamp);
|
||||
const listOfMultipleUnitUsage = item.cdrJSON.listOfMultipleUnitUsage;
|
||||
if (
|
||||
!Array.isArray(listOfMultipleUnitUsage) ||
|
||||
@@ -405,6 +454,8 @@ function fnRanderChartDataLoad() {
|
||||
}
|
||||
// 绘制图数据
|
||||
fnRanderChartDataUpdate();
|
||||
// 动态ws
|
||||
fnRealTime();
|
||||
} else {
|
||||
message.warning('No Data');
|
||||
cdrChart.hideLoading();
|
||||
@@ -581,8 +632,7 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
fnRanderChart();
|
||||
fnRealTime();
|
||||
fnGetList();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -616,7 +666,7 @@ onBeforeUnmount(() => {
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="IMSI" name="subscriberID" :required="true">
|
||||
<a-form-item label="IMSI (Prefix)" name="subscriberID">
|
||||
<a-input
|
||||
v-model:value="queryParams.subscriberID"
|
||||
allow-clear
|
||||
@@ -666,7 +716,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:presets="rangePickerPresets"
|
||||
:presets="dayjsRanges()"
|
||||
:bordered="true"
|
||||
:allow-clear="false"
|
||||
style="width: 100%"
|
||||
@@ -681,10 +731,15 @@ onBeforeUnmount(() => {
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false">
|
||||
<!-- 图数据 -->
|
||||
<div ref="cdrChartDom" style="height: 600px; width: 100%"></div>
|
||||
|
||||
<a-descriptions title="Data Usage" bordered :column="2">
|
||||
<a-descriptions
|
||||
title="Data Usage"
|
||||
bordered
|
||||
:column="2"
|
||||
:style="{
|
||||
width: '60%',
|
||||
marginBottom: '24px',
|
||||
}"
|
||||
>
|
||||
<a-descriptions-item label="Total Uplink">
|
||||
{{ state.dataUsage[0] }}
|
||||
</a-descriptions-item>
|
||||
@@ -692,6 +747,11 @@ onBeforeUnmount(() => {
|
||||
{{ state.dataUsage[1] }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<!-- 图数据 -->
|
||||
<div
|
||||
ref="cdrChartDom"
|
||||
style="display: none; height: 600px; width: 100%"
|
||||
></div>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
@@ -21,9 +21,11 @@ import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import saveAs from 'file-saver';
|
||||
import PQueue from 'p-queue';
|
||||
import { listTenant } from '@/api/system/tenant';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { hasPermissions } from '@/plugins/auth-user';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import { dayjsRanges } from '@/hooks/useRangePicker';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
@@ -32,10 +34,10 @@ const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**CDR 响应原因代码类别类型 */
|
||||
cdrCauseCode: DictType[];
|
||||
/**CDR SMSC 响应原因代码类别类型 */
|
||||
cdrSMSCCauseCode: DictType[];
|
||||
} = reactive({
|
||||
cdrCauseCode: [],
|
||||
cdrSMSCCauseCode: [],
|
||||
});
|
||||
|
||||
/**网元可选 */
|
||||
@@ -46,30 +48,18 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
dayjs().startOf('hour'),
|
||||
dayjs().endOf('hour'),
|
||||
]);
|
||||
/**时间范围 */
|
||||
let rangePickerPresets = ref([
|
||||
{
|
||||
label: 'Now hour',
|
||||
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
|
||||
},
|
||||
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
|
||||
{
|
||||
label: 'Yesterday',
|
||||
value: [
|
||||
dayjs().subtract(1, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
tenantNameArr: <Record<string, any>[]>[],
|
||||
/**网元类型 */
|
||||
neType: 'SMSC',
|
||||
neId: '001',
|
||||
recordType: '',
|
||||
callerParty: '',
|
||||
calledParty: '',
|
||||
/** 租户名称*/
|
||||
tenantName: '',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**开始时间 */
|
||||
@@ -89,6 +79,8 @@ function fnQueryReset() {
|
||||
recordType: '',
|
||||
callerParty: '',
|
||||
calledParty: '',
|
||||
/** 租户名称*/
|
||||
tenantName: '',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
pageNum: 1,
|
||||
@@ -135,12 +127,12 @@ let tableState: TabeStateType = reactive({
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
// {
|
||||
// title: t('common.rowId'),
|
||||
// dataIndex: 'id',
|
||||
// align: 'left',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: t('views.dashboard.cdr.recordType'),
|
||||
dataIndex: 'cdrJSON',
|
||||
@@ -185,25 +177,39 @@ let tableColumns: ColumnsType = [
|
||||
},
|
||||
|
||||
{
|
||||
title: t('views.dashboard.cdr.result'),
|
||||
title: t('views.dashboard.cdr.resultCode'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'cause',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.resultCause'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'result',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.time'),
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
width: 250,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
if (typeof cdrJSON.updateTime === 'number') {
|
||||
return parseDateToStr(+cdrJSON.updateTime * 1000);
|
||||
}
|
||||
return cdrJSON.updateTime;
|
||||
return parseDateToStr(cdrJSON.updateTime);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.tenantName'),
|
||||
dataIndex: 'tenantName',
|
||||
align: 'center',
|
||||
key: 'tenantName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -372,14 +378,18 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading) return;
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.cdr.exportTip'),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageSize = 10000;
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
exportSMSCDataCDR(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -472,9 +482,9 @@ function wsMessage(res: Record<string, any>) {
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('cdr_cause_code')]).then(resArr => {
|
||||
Promise.allSettled([getDict('cdr_cause_smsc')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrCauseCode = resArr[0].value;
|
||||
dict.cdrSMSCCauseCode = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取网元网元列表
|
||||
@@ -504,6 +514,20 @@ onMounted(() => {
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
//查询租户
|
||||
listTenant({ parentId: 0 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
|
||||
res.data.forEach((item: any) => {
|
||||
if (item.parentId === '0') {
|
||||
queryParams.tenantNameArr.push({
|
||||
value: item.tenantName,
|
||||
label: item.tenantName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -558,21 +582,7 @@ onBeforeUnmount(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.recordType')"
|
||||
name="recordType"
|
||||
@@ -593,7 +603,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:presets="rangePickerPresets"
|
||||
:presets="dayjsRanges()"
|
||||
:bordered="true"
|
||||
:allow-clear="false"
|
||||
style="width: 100%"
|
||||
@@ -602,6 +612,32 @@ onBeforeUnmount(() => {
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.sub.tenantName')"
|
||||
name="tenantName "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.tenantName"
|
||||
:options="queryParams.tenantNameArr"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
@@ -715,17 +751,15 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'cause'">
|
||||
<span v-if="record.cdrJSON.result === 0">
|
||||
{{ t('views.dashboard.cdr.resultFail') }},
|
||||
<DictTag
|
||||
:options="dict.cdrCauseCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
<span v-if="record.cdrJSON.result === 0"> FAILED </span>
|
||||
<span v-else> SUCCESS </span>
|
||||
</template>
|
||||
<template v-if="column.key === 'result'">
|
||||
<DictTag
|
||||
:options="dict.cdrSMSCCauseCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
@@ -757,7 +791,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="8" :md="12" :xs="22" :offset="2">
|
||||
<a-col :lg="8" :md="12" :xs="24" :offset="2">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.cdrInfo') }}
|
||||
</a-divider>
|
||||
@@ -775,7 +809,7 @@ onBeforeUnmount(() => {
|
||||
{{
|
||||
typeof record.cdrJSON.updateTime === 'number'
|
||||
? parseDateToStr(+record.cdrJSON.updateTime * 1000)
|
||||
: record.cdrJSON.updateTime
|
||||
: parseDateToStr(record.cdrJSON.updateTime)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
@@ -797,18 +831,19 @@ onBeforeUnmount(() => {
|
||||
<span>{{ record.cdrJSON.calledParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||
<span v-if="record.cdrJSON.result === 0">
|
||||
{{ t('views.dashboard.cdr.resultFail') }},
|
||||
<span>Result Code: </span>
|
||||
<span v-if="record.cdrJSON.result === 0"> FAILED </span>
|
||||
<span v-else> SUCCESS </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Result Cause: </span>
|
||||
<span>
|
||||
<DictTag
|
||||
:options="dict.cdrCauseCode"
|
||||
:options="dict.cdrSMSCCauseCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { reactive, ref, onMounted, toRaw, onUnmounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
@@ -558,7 +558,8 @@ function fnClear() {
|
||||
content: t('views.faultManage.activeAlarm.delSure'),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
clearAlarm(state.selectedRowKeys).then(res => {
|
||||
const ids = state.selectedRowKeys.map(v => `${v}`);
|
||||
clearAlarm(ids).then(res => {
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
@@ -747,6 +748,22 @@ function fnGetList(pageNum?: number) {
|
||||
});
|
||||
}
|
||||
|
||||
let interval: any = null;
|
||||
/**自动刷新 */
|
||||
function AutoRefresh() {
|
||||
interval = setInterval(() => {
|
||||
listAct(toRaw(queryParams), '').then((res: any) => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
} else {
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
}
|
||||
});
|
||||
}, 5_000);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
@@ -768,9 +785,12 @@ onMounted(() => {
|
||||
dict.activeAlarmSeverity = resArr[3].value;
|
||||
}
|
||||
});
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore().fnNelist();
|
||||
fnGetList();
|
||||
// 自动刷新
|
||||
AutoRefresh();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1200,7 +1220,7 @@ onMounted(() => {
|
||||
>
|
||||
{{ modalState.from.alarmType }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
@@ -1210,7 +1230,7 @@ onMounted(() => {
|
||||
>
|
||||
{{ modalState.from.locationInfo }}
|
||||
</a-form-item>
|
||||
|
||||
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
@@ -1260,7 +1280,10 @@ onMounted(() => {
|
||||
:label="t('views.faultManage.activeAlarm.ackState')"
|
||||
name="ackState"
|
||||
>
|
||||
<DictTag :options="dict.activeAckState" :value="modalState.from.ackState" />
|
||||
<DictTag
|
||||
:options="dict.activeAckState"
|
||||
:value="modalState.from.ackState"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
BIN
src/views/faultManage/alarm-overview/images/bg.png
Normal file
|
After Width: | Height: | Size: 257 KiB |