视频监控模块开发,地图模块管理开发

This commit is contained in:
扈兆增 2026-05-19 18:56:33 +08:00
parent c960b57e0b
commit e7658caa11
31 changed files with 12561 additions and 217 deletions

View File

@ -12,8 +12,8 @@ VITE_APP_BASE_API = '/dev-api'
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
# 汤伟
# VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
VITE_APP_BASE_URL = 'http://192.168.1.162:8093'
VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
# VITE_APP_BASE_URL = 'http://192.168.1.162:8093'
# 测试环境线上
VITE_APP_TEST_ONLINE_URL = 'https://211.99.26.225:12122'

View File

@ -40,6 +40,8 @@
"pinia": "^2.0.12",
"screenfull": "^6.0.0",
"sortablejs": "^1.14.0",
"video.js": "^8.23.7",
"videojs-contrib-hls": "^5.15.0",
"vue": "^3.2.40",
"vue-i18n": "^9.1.9",
"vue-router": "^4.1.6",

View File

@ -98,6 +98,12 @@ importers:
sortablejs:
specifier: ^1.14.0
version: 1.15.7
video.js:
specifier: ^8.23.7
version: 8.23.7
videojs-contrib-hls:
specifier: ^5.15.0
version: 5.15.0
vue:
specifier: ^3.2.40
version: 3.5.33(typescript@6.0.3)
@ -865,6 +871,19 @@ packages:
peerDependencies:
'@uppy/core': ^2.3.3
'@videojs/http-streaming@3.17.4':
resolution: {integrity: sha512-XAvdG2dolBuV2Fx8bu1kjmQ2D4TonGzZH68Pgv/O9xMSFWdZtITSMFismeQLEAtMmGwze8qNJp3RgV+jStrJqg==}
engines: {node: '>=8', npm: '>=5'}
peerDependencies:
video.js: ^8.19.0
'@videojs/vhs-utils@4.1.1':
resolution: {integrity: sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==}
engines: {node: '>=8', npm: '>=5'}
'@videojs/xhr@2.7.0':
resolution: {integrity: sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==}
'@vitejs/plugin-vue@4.6.2':
resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==}
engines: {node: ^14.18.0 || >=16.0.0}
@ -1021,6 +1040,10 @@ packages:
slate: ^0.72.0
snabbdom: ^3.1.0
'@xmldom/xmldom@0.8.13':
resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
engines: {node: '>=10.0.0'}
'@zarrita/storage@0.2.0':
resolution: {integrity: sha512-855ZXqtnds7spnT8vNvD+MXa3QExP1m2GqShe8yt7uZXHnQLgJHgkpVwFjE1B0KDDRO0ki09hmk6OboTaIfPsQ==}
@ -1050,6 +1073,12 @@ packages:
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
engines: {node: '>=0.8'}
aes-decrypter@1.0.3:
resolution: {integrity: sha512-rsx8pfx7wJsn+ziYbpJ8XA5c93hKAtBCrfydxJqJCMT+qfjipd/B5wC2xHtBcoxyvlqJcpeAo3K55t0lXOn9yQ==}
aes-decrypter@4.0.2:
resolution: {integrity: sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==}
ajv@6.14.0:
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
@ -1173,6 +1202,9 @@ packages:
axios@1.15.2:
resolution: {integrity: sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==}
babel-runtime@6.26.0:
resolution: {integrity: sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -1350,6 +1382,10 @@ packages:
resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
engines: {node: '>=0.10.0'}
core-js@2.6.12:
resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==}
deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
core-js@3.49.0:
resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==}
@ -1534,6 +1570,9 @@ packages:
dom-to-image@2.6.0:
resolution: {integrity: sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==}
dom-walk@0.1.2:
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
dom7@3.0.0:
resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==}
@ -1639,6 +1678,10 @@ packages:
resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
engines: {node: '>=0.10'}
es5-shim@4.6.7:
resolution: {integrity: sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==}
engines: {node: '>=0.4.0'}
es6-iterator@2.0.3:
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
@ -1956,6 +1999,12 @@ packages:
resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==}
engines: {node: '>=4'}
global@4.3.2:
resolution: {integrity: sha512-/4AybdwIDU4HkCUbJkZdWpe4P6vuw/CUtu+0I1YlLIPe7OlUO7KNJ+q/rO70CW2/NW6Jc6I62++Hzsf5Alu6rQ==}
global@4.4.0:
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
globals@13.24.0:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
engines: {node: '>=8'}
@ -2099,6 +2148,9 @@ packages:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
individual@2.0.0:
resolution: {integrity: sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==}
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@ -2191,6 +2243,9 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-function@1.0.2:
resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==}
is-generator-function@1.1.2:
resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
engines: {node: '>= 0.4'}
@ -2480,6 +2535,12 @@ packages:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
m3u8-parser@2.1.0:
resolution: {integrity: sha512-WbEpQ2FUaNGbJ0YanSeyj9D9ruu4FUvz+ZvebIzI2bSME+PUwcPXO1kKXZkjcPUAFruDikoOI5fWQNIA6JCCOQ==}
m3u8-parser@7.2.0:
resolution: {integrity: sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==}
magic-string@0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
@ -2564,6 +2625,9 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
min-document@2.19.2:
resolution: {integrity: sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==}
min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
@ -2585,6 +2649,10 @@ packages:
moment@2.30.1:
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
mpd-parser@1.3.1:
resolution: {integrity: sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==}
hasBin: true
ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
@ -2594,6 +2662,14 @@ packages:
muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
mux.js@4.3.2:
resolution: {integrity: sha512-g0q6DPdvb3yYcoK7ElBGobdSSrhY/RjPt19U7uUc733aqvc5bCS/aCvL9z+448y+IoCZnYDwyZfQBBXMSmGOaQ==}
mux.js@7.1.0:
resolution: {integrity: sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==}
engines: {node: '>=8', npm: '>=5'}
hasBin: true
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
@ -2816,6 +2892,15 @@ packages:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
pkcs7@0.2.3:
resolution: {integrity: sha512-kJRwmADEQUg+qJyRgWLtpEL9q9cFjZschejTEK3GRjKvnsU9G5WWoe/wKqRgbBoqWdVSeTUKP6vIA3Y72M3rWA==}
engines: {node: ^0.10, npm: ^1.4.6}
hasBin: true
pkcs7@1.0.4:
resolution: {integrity: sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==}
hasBin: true
posix-character-classes@0.1.1:
resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==}
engines: {node: '>=0.10.0'}
@ -2920,6 +3005,14 @@ packages:
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
process@0.5.2:
resolution: {integrity: sha512-oNpcutj+nYX2FjdEW7PGltWhXulAnFlM0My/k48L90hARCOJtvBbQXc/6itV2jDvU5xAAtonP+r6wmQgCcbAUA==}
engines: {node: '>= 0.6.0'}
protobufjs@8.3.0:
resolution: {integrity: sha512-JpJpFaR7yKNb6WqKvJJ1MLbiuIQWQnbUUb06nDtf2/i8YWYYLEfP6xf9BwSJoJQg1wAy61EQB8dssQg64oX4aA==}
engines: {node: '>=12.0.0'}
@ -3005,6 +3098,9 @@ packages:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
regenerator-runtime@0.11.1:
resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==}
regex-not@1.0.2:
resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}
engines: {node: '>=0.10.0'}
@ -3078,6 +3174,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
rust-result@1.0.0:
resolution: {integrity: sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==}
safe-array-concat@1.1.4:
resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==}
engines: {node: '>=0.4'}
@ -3088,6 +3187,9 @@ packages:
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-json-parse@4.0.0:
resolution: {integrity: sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==}
safe-push-apply@1.0.0:
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
engines: {node: '>= 0.4'}
@ -3464,6 +3566,10 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsml@1.0.1:
resolution: {integrity: sha512-3KmepnH9SUsoOVtg013CRrL7c+AK7ECaquAsJdvu4288EDJuraqBlP4PDXT/rLEJ9YDn4jqLAzRJsnFPx+V6lg==}
deprecated: no longer maintained
tsutils@3.21.0:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
@ -3559,6 +3665,9 @@ packages:
resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==}
deprecated: Please see https://github.com/lydell/urix#deprecated
url-toolkit@2.2.5:
resolution: {integrity: sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==}
use@3.1.1:
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
engines: {node: '>=0.10.0'}
@ -3576,6 +3685,40 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
video.js@6.13.0:
resolution: {integrity: sha512-36/JR/GhPQSZj0o+GNbhcEYv/b0SkV9SQsjlodAnzMQYN0TA7VhmqrKPYMCi1NGRYu7S9W3OaFCFoUxkYfSVlg==}
video.js@8.23.7:
resolution: {integrity: sha512-cG4HOygYt+Z8j6Sf5DuK6OgEOoM+g9oGP6vpqoZRaD13aHE4PMITbyjJUXZcIQbgB0wJEadBRaVm5lJIzo2jAA==}
videojs-contrib-hls@5.15.0:
resolution: {integrity: sha512-18zbMYZ0XRBKTPEayA9bFTWWrqhT9b4G8+zf0czJLD7Epe5PcK1I/3dflTHQeQ5rwlWir+/XnFU3sMg/B2MMcw==}
engines: {node: '>= 0.10.12'}
videojs-contrib-media-sources@4.7.2:
resolution: {integrity: sha512-e6iCHWBFuV05EGo7v+pS9iepObXnJ9joms467gzi8ZjpKVb3ifha9M0Ja24Rd8JfvYpzjltsgDVtGFDvIg4hQQ==}
videojs-contrib-quality-levels@4.1.0:
resolution: {integrity: sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==}
engines: {node: '>=16', npm: '>=8'}
peerDependencies:
video.js: ^8
videojs-font@2.1.0:
resolution: {integrity: sha512-zFqWpLrXf1q8NtYx5qtZhMC6SLUFScDmR6j+UGPogobxR21lvXShhnzcNNMdOxJUuFLiToJ/BPpFUQwX4xhpvA==}
videojs-font@4.2.0:
resolution: {integrity: sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==}
videojs-ie8@1.1.2:
resolution: {integrity: sha512-0Zb2T4MLkpfZbeGMK/Z93b8Lrepr+rLFoHgQV1CoDeFqXvH7b+Vsd/VHoILGxQrgCSHFQ7mAODR6oyMjuiD4/g==}
videojs-vtt.js@0.12.6:
resolution: {integrity: sha512-XFXeGBQiljnElMhwCcZst0RDbZn2n8LU7ZScXryd3a00OaZsHAjdZu/7/RdSr7Z1jHphd45FnOvOQkGK4YrWCQ==}
videojs-vtt.js@0.15.5:
resolution: {integrity: sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==}
vite-plugin-cesium@1.2.22:
resolution: {integrity: sha512-OnS+VKNGck4kUu4/67Fdfhz0/zF9mDVNUp9hUWtX19C38O0mJsJy2MH1ev2QcrVLf6VieJ7vCGxkLchdB1n1HQ==}
peerDependencies:
@ -3679,6 +3822,9 @@ packages:
web-worker@1.5.0:
resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==}
webwackify@0.1.6:
resolution: {integrity: sha512-pGcw1T3HpNnM/UTRQqqRkkkzythSLts05mB+7Gr00B+0VbL0m39dFL5g20rSIEUt9Wrpw+/8k+snxRlUFHhcqA==}
which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'}
@ -3730,6 +3876,9 @@ packages:
wujie@1.0.29:
resolution: {integrity: sha512-u7PmBT4l5ov7ciwmZNG8DLIRKrYrnXDa0LnAFgRQikJ/ZrVZYzzSNz6duzEJoj8/o46dnMy/g9Hh/PBYGbgFUw==}
xhr@2.4.0:
resolution: {integrity: sha512-TUbBsdAuJbX8olk9hsDwGK8P1ri1XlV+PdEWkYw+HQQbpkiBR8PLgD1F3kQDPBs9l4Px34hP9rCYAZOCCAENbw==}
xlsx@0.18.5:
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
engines: {node: '>=0.8'}
@ -3738,6 +3887,10 @@ packages:
xml-utils@1.10.2:
resolution: {integrity: sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==}
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -4435,6 +4588,28 @@ snapshots:
'@uppy/utils': 4.1.3
nanoid: 3.3.11
'@videojs/http-streaming@3.17.4(video.js@8.23.7)':
dependencies:
'@babel/runtime': 7.29.2
'@videojs/vhs-utils': 4.1.1
aes-decrypter: 4.0.2
global: 4.4.0
m3u8-parser: 7.2.0
mpd-parser: 1.3.1
mux.js: 7.1.0
video.js: 8.23.7
'@videojs/vhs-utils@4.1.1':
dependencies:
'@babel/runtime': 7.29.2
global: 4.4.0
'@videojs/xhr@2.7.0':
dependencies:
'@babel/runtime': 7.29.2
global: 4.4.0
is-function: 1.0.2
'@vitejs/plugin-vue@4.6.2(vite@4.5.14(@types/node@16.18.126)(sass@1.99.0))(vue@3.5.33(typescript@6.0.3))':
dependencies:
vite: 4.5.14(@types/node@16.18.126)(sass@1.99.0)
@ -4662,6 +4837,8 @@ snapshots:
slate: 0.72.8
snabbdom: 3.6.3
'@xmldom/xmldom@0.8.13': {}
'@zarrita/storage@0.2.0':
dependencies:
reference-spec-reader: 0.2.0
@ -4686,6 +4863,17 @@ snapshots:
adler-32@1.3.1: {}
aes-decrypter@1.0.3:
dependencies:
pkcs7: 0.2.3
aes-decrypter@4.0.2:
dependencies:
'@babel/runtime': 7.29.2
'@videojs/vhs-utils': 4.1.1
global: 4.4.0
pkcs7: 1.0.4
ajv@6.14.0:
dependencies:
fast-deep-equal: 3.1.3
@ -4812,6 +5000,11 @@ snapshots:
transitivePeerDependencies:
- debug
babel-runtime@6.26.0:
dependencies:
core-js: 2.6.12
regenerator-runtime: 0.11.1
balanced-match@1.0.2: {}
base@0.11.2:
@ -5038,6 +5231,8 @@ snapshots:
copy-descriptor@0.1.1: {}
core-js@2.6.12: {}
core-js@3.49.0: {}
core-util-is@1.0.3: {}
@ -5211,6 +5406,8 @@ snapshots:
dom-to-image@2.6.0: {}
dom-walk@0.1.2: {}
dom7@3.0.0:
dependencies:
ssr-window: 3.0.0
@ -5387,6 +5584,8 @@ snapshots:
esniff: 2.0.1
next-tick: 1.1.0
es5-shim@4.6.7: {}
es6-iterator@2.0.3:
dependencies:
d: 1.0.2
@ -5797,6 +5996,16 @@ snapshots:
dependencies:
ini: 1.3.8
global@4.3.2:
dependencies:
min-document: 2.19.2
process: 0.5.2
global@4.4.0:
dependencies:
min-document: 2.19.2
process: 0.11.10
globals@13.24.0:
dependencies:
type-fest: 0.20.2
@ -5926,6 +6135,8 @@ snapshots:
indent-string@4.0.0: {}
individual@2.0.0: {}
inflight@1.0.6:
dependencies:
once: 1.4.0
@ -6021,6 +6232,8 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
is-function@1.0.2: {}
is-generator-function@1.1.2:
dependencies:
call-bound: 1.0.4
@ -6263,6 +6476,14 @@ snapshots:
dependencies:
yallist: 4.0.0
m3u8-parser@2.1.0: {}
m3u8-parser@7.2.0:
dependencies:
'@babel/runtime': 7.29.2
'@videojs/vhs-utils': 4.1.1
global: 4.4.0
magic-string@0.25.9:
dependencies:
sourcemap-codec: 1.4.8
@ -6352,6 +6573,10 @@ snapshots:
mimic-fn@2.1.0: {}
min-document@2.19.2:
dependencies:
dom-walk: 0.1.2
min-indent@1.0.1: {}
minimatch@3.1.5:
@ -6373,12 +6598,26 @@ snapshots:
moment@2.30.1: {}
mpd-parser@1.3.1:
dependencies:
'@babel/runtime': 7.29.2
'@videojs/vhs-utils': 4.1.1
'@xmldom/xmldom': 0.8.13
global: 4.4.0
ms@2.0.0: {}
ms@2.1.3: {}
muggle-string@0.4.1: {}
mux.js@4.3.2: {}
mux.js@7.1.0:
dependencies:
'@babel/runtime': 7.29.2
global: 4.4.0
mz@2.7.0:
dependencies:
any-promise: 1.3.0
@ -6598,6 +6837,12 @@ snapshots:
pirates@4.0.7: {}
pkcs7@0.2.3: {}
pkcs7@1.0.4:
dependencies:
'@babel/runtime': 7.29.2
posix-character-classes@0.1.1: {}
possible-typed-array-names@1.1.0: {}
@ -6687,6 +6932,10 @@ snapshots:
process-nextick-args@2.0.1: {}
process@0.11.10: {}
process@0.5.2: {}
protobufjs@8.3.0:
dependencies:
long: 5.3.2
@ -6775,6 +7024,8 @@ snapshots:
get-proto: 1.0.1
which-builtin-type: 1.2.1
regenerator-runtime@0.11.1: {}
regex-not@1.0.2:
dependencies:
extend-shallow: 3.0.2
@ -6842,6 +7093,10 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
rust-result@1.0.0:
dependencies:
individual: 2.0.0
safe-array-concat@1.1.4:
dependencies:
call-bind: 1.0.9
@ -6854,6 +7109,10 @@ snapshots:
safe-buffer@5.2.1: {}
safe-json-parse@4.0.0:
dependencies:
rust-result: 1.0.0
safe-push-apply@1.0.0:
dependencies:
es-errors: 1.3.0
@ -7317,6 +7576,8 @@ snapshots:
tslib@2.8.1: {}
tsml@1.0.1: {}
tsutils@3.21.0(typescript@6.0.3):
dependencies:
tslib: 1.14.1
@ -7421,6 +7682,8 @@ snapshots:
urix@0.1.0: {}
url-toolkit@2.2.5: {}
use@3.1.1: {}
util-deprecate@1.0.2: {}
@ -7434,6 +7697,71 @@ snapshots:
vary@1.1.2: {}
video.js@6.13.0:
dependencies:
babel-runtime: 6.26.0
global: 4.3.2
safe-json-parse: 4.0.0
tsml: 1.0.1
videojs-font: 2.1.0
videojs-ie8: 1.1.2
videojs-vtt.js: 0.12.6
xhr: 2.4.0
video.js@8.23.7:
dependencies:
'@babel/runtime': 7.29.2
'@videojs/http-streaming': 3.17.4(video.js@8.23.7)
'@videojs/vhs-utils': 4.1.1
'@videojs/xhr': 2.7.0
aes-decrypter: 4.0.2
global: 4.4.0
m3u8-parser: 7.2.0
mpd-parser: 1.3.1
mux.js: 7.1.0
videojs-contrib-quality-levels: 4.1.0(video.js@8.23.7)
videojs-font: 4.2.0
videojs-vtt.js: 0.15.5
videojs-contrib-hls@5.15.0:
dependencies:
aes-decrypter: 1.0.3
global: 4.4.0
m3u8-parser: 2.1.0
mux.js: 4.3.2
url-toolkit: 2.2.5
video.js: 6.13.0
videojs-contrib-media-sources: 4.7.2
webwackify: 0.1.6
videojs-contrib-media-sources@4.7.2:
dependencies:
global: 4.4.0
mux.js: 4.3.2
video.js: 6.13.0
webwackify: 0.1.6
videojs-contrib-quality-levels@4.1.0(video.js@8.23.7):
dependencies:
global: 4.4.0
video.js: 8.23.7
videojs-font@2.1.0: {}
videojs-font@4.2.0: {}
videojs-ie8@1.1.2:
dependencies:
es5-shim: 4.6.7
videojs-vtt.js@0.12.6:
dependencies:
global: 4.4.0
videojs-vtt.js@0.15.5:
dependencies:
global: 4.4.0
vite-plugin-cesium@1.2.22(cesium@1.141.0)(rollup@3.30.0)(vite@4.5.14(@types/node@16.18.126)(sass@1.99.0)):
dependencies:
cesium: 1.141.0
@ -7533,6 +7861,8 @@ snapshots:
web-worker@1.5.0: {}
webwackify@0.1.6: {}
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0
@ -7603,6 +7933,13 @@ snapshots:
dependencies:
'@babel/runtime': 7.29.2
xhr@2.4.0:
dependencies:
global: 4.3.2
is-function: 1.0.2
parse-headers: 2.0.6
xtend: 4.0.2
xlsx@0.18.5:
dependencies:
adler-32: 1.3.1
@ -7615,6 +7952,8 @@ snapshots:
xml-utils@1.10.2: {}
xtend@4.0.2: {}
y18n@5.0.8: {}
yallist@4.0.0: {}

View File

@ -1,9 +1,8 @@
import request from '@/utils/request';
import { SERVICE_URLS } from '../config'; // 引入配置
// 基本情况介绍
export function getBaseWbsb(data: any) {
return request({
url: SERVICE_URLS.lygk + '/base/wbsb/GetKendoList',
url: '/dec-lygk-base-server/base/wbsb/GetKendoList',
method: 'post',
data: data
});

View File

@ -0,0 +1,25 @@
import request from '@/utils/request';
// 顶部tabs
export function baseMsstbprpt(data: any) {
return request({
url: '/api/dec-lygk-base-server/base/msstbprpt/GetKendoList',
method: 'post',
data
});
}
// 实时视频
export function envVdTreeGetKendoListCust(data: any) {
return request({
url: '/api/wmp-env-server/env/vd/tree/GetKendoListCust',
method: 'post',
data
});
}
// 录像
export function envVdRunDataGetKendoList(data: any) {
return request({
url: '/api/wmp-env-server/env/vd/runData/GetKendoListCust',
method: 'post',
data
});
}

View File

@ -0,0 +1 @@
<svg width="14" height="12" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M14 0v10.998C14 11.55 13.55 12 12.993 12H0L14 0Z" fill="#2F6B98"/><path d="M7 5h6v6H7z"/><path d="M12.878 7.095a.335.335 0 0 1-.001.5l-3.414 3.027a.42.42 0 0 1-.368.096.38.38 0 0 1-.232-.123L7.096 8.633a.375.375 0 0 1 .558-.501l1.551 1.722 3.114-2.761a.42.42 0 0 1 .56.002Z" fill="#FFF"/></g></svg>

After

Width:  |  Height:  |  Size: 405 B

View File

@ -20,8 +20,8 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick } from "vue";
import { calcTableScrollY } from "@/utils/index";
import { ref, computed, onMounted, watch, nextTick } from 'vue';
import { calcTableScrollY } from '@/utils/index';
// --- Types ---
interface Props {
@ -39,18 +39,18 @@ interface Props {
const props = withDefaults(defineProps<Props>(), {
enableRowSelection: false,
rowKey: "id",
rowKey: 'id',
data: () => [],
searchParams: () => ({}),
defaultPageSize: 20,
getCheckboxProps: undefined,
transformData: undefined,
scrollY: undefined,
scrollY: undefined
});
const emit = defineEmits<{
(e: "data-loaded", params: any, data: any): void;
(e: "selection-change", selectedRowKeys: string[], selectedRows: any[]): void;
(e: 'data-loaded', params: any, data: any): void;
(e: 'selection-change', selectedRowKeys: string[], selectedRows: any[]): void;
}>();
// --- State ---
@ -68,10 +68,11 @@ const tableScrollY = ref<number>(0); // ✅ 新增:存储容器高度
// --- Computed Scroll Config ---
const scrollConfig = computed(() => {
const config: any = {
x: "100%",
x: '100%',
y: 0
};
// 1. scrollY使
// // 1. scrollY使
if (props.scrollY !== undefined && props.scrollY !== null) {
config.y = props.scrollY;
}
@ -79,6 +80,7 @@ const scrollConfig = computed(() => {
else if (tableScrollY.value > 0) {
config.y = tableScrollY.value;
}
console.log(tableScrollY.value);
return config;
});
@ -89,11 +91,11 @@ const rowSelection = computed(() => ({
onChange: (keys: string[], rows: any[]) => {
selectedRowKeys.value = keys;
selectedRows.value = rows;
emit("selection-change", keys, rows);
emit('selection-change', keys, rows);
},
getCheckboxProps: props.getCheckboxProps
? props.getCheckboxProps
: (record: any) => ({}),
: (record: any) => ({})
}));
// --- Pagination Config ---
@ -104,7 +106,7 @@ const paginationConfig = computed(() => ({
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `${total}`,
pageSizeOptions: ["20", "50", "100"],
pageSizeOptions: ['20', '50', '100']
}));
// --- Methods ---
@ -121,7 +123,7 @@ const getList = async (filter?: Record<string, any>) => {
...props.searchParams,
skip: page.value,
take: size.value,
filter: lastFilter.value,
filter: lastFilter.value
};
const res = await props.listUrl(params);
@ -140,9 +142,9 @@ const getList = async (filter?: Record<string, any>) => {
tableData.value = finalRecords;
total.value = finalTotal;
emit("data-loaded", params, { records: finalRecords, total: finalTotal });
emit('data-loaded', params, { records: finalRecords, total: finalTotal });
} catch (error) {
console.error("Fetch table data error:", error);
console.error('Fetch table data error:', error);
tableData.value = [];
total.value = 0;
} finally {
@ -181,8 +183,8 @@ defineExpose({
clearSelection,
getSelected: () => ({
keys: selectedRowKeys.value,
rows: selectedRows.value,
}),
rows: selectedRows.value
})
});
watch(

View File

@ -108,3 +108,8 @@ svg {
max-height: 300px;
overflow: auto;
}
// 暂无数据文字颜色
.ant-empty-description {
color: rgba(0, 0, 0, 0.85);
}

View File

@ -19,14 +19,17 @@ service.interceptors.request.use(
`Expected 'config' and 'config.headers' not to be undefined`
);
}
if (config.url.includes('/dec-lygk-base-server')) {
if (
config.url.includes('/dec-lygk-base-server') ||
config.url.includes('/wmp-env-server')
) {
config.headers._appid = '974975A6-47FD-4C04-9ACD-68938D2992BD';
config.headers._isolateid = '5b34aecb-adfb-4dfc-ad95-21505a9eb388';
config.headers._platformid = '31e6d13f-c359-4a19-8e67-b6f868f2401b';
config.headers._sysid = '10EC2E0B-AEA9-4757-83A2-201BA1BC54E9';
config.headers.authorization =
'bearer f96d0008-f77f-497a-b05d-82cded9ccd89';
'bearer ff5f2ec1-27ba-4ff4-8987-606cbfac6f2f';
config.baseURL = '/';
} else {
const user = useUserStoreHook();

View File

@ -0,0 +1,348 @@
<template>
<div class="live-video-box" :style="{ height }">
<!-- 有视频时显示播放器 -->
<div v-if="videoUrl" class="video-container">
<!-- 视频标题和关闭按钮 -->
<div class="live-video-title">
{{ videoData?.flnm }}
<div class="title-actions">
<CloseOutlined @click="handleClose" />
</div>
</div>
<div :id="playerId" class="video-player"></div>
</div>
<!-- 无视频时显示占位 -->
<div v-else class="video-empty">
<i class="iconfont icon-jiankong"></i>
<div>请选择视频</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, computed } from "vue";
import { CloseOutlined } from "@ant-design/icons-vue";
import videojs from "video.js";
import "video.js/dist/video-js.css";
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
// HLS video.js 8.x HLS
interface VideoData {
flnm?: string;
stnm?: string;
url?: string;
urls?: string[];
[key: string]: any;
}
const props = defineProps<{
videoData?: VideoData | null;
height?: string;
monitorType?: "live" | "record"; // live-, record-
}>();
const emit = defineEmits<{
close: [nodeKey?: string];
}>();
// ID
const playerId = `video-player-${Math.random().toString(36).substr(2, 9)}`;
let player: any = null;
// URL
const videoUrl = computed(() => {
if (!props.videoData) return "";
// 使url
if (props.videoData.url) return props.videoData.url;
// 使urls
if (
props.videoData.urls &&
Array.isArray(props.videoData.urls) &&
props.videoData.urls.length > 0
) {
return props.videoData.urls[0];
}
return "";
});
// video.js
const initPlayer = () => {
if (!videoUrl.value) return;
//
if (player) {
player.dispose();
player = null;
}
try {
// video
const videoElement = document.createElement("video");
videoElement.className = "video-js vjs-default-skin vjs-big-play-centered";
videoElement.setAttribute("id", playerId);
videoElement.setAttribute("playsinline", "playsinline");
videoElement.setAttribute("webkit-playsinline", "webkit-playsinline");
// video
const container = document.getElementById(playerId);
if (container) {
container.innerHTML = "";
container.appendChild(videoElement);
}
// monitorType
// live: m3u8UI
// record:
const isLiveStream = props.monitorType === "live";
// URL MIME Type
const getVideoType = (url: string): string => {
const lowerUrl = url.toLowerCase();
// URL
if (lowerUrl.includes(".m3u8")) {
return "application/x-mpegURL"; // HLS
} else if (lowerUrl.includes(".mp4")) {
return "video/mp4";
} else if (lowerUrl.includes(".webm")) {
return "video/webm";
} else if (lowerUrl.includes(".ogg") || lowerUrl.includes(".ogv")) {
return "video/ogg";
} else if (lowerUrl.includes(".avi")) {
return "video/x-msvideo";
} else if (lowerUrl.includes(".mov")) {
return "video/quicktime";
} else if (lowerUrl.includes(".wmv")) {
return "video/x-ms-wmv";
} else if (lowerUrl.includes(".flv")) {
return "video/x-flv";
}
// URL
return isLiveStream ? "application/x-mpegURL" : "video/mp4";
};
const videoType = getVideoType(videoUrl.value);
// video.js
player = videojs(videoElement, {
autoplay: true,
muted: true,
controls: true,
fluid: false,
width: "100%",
height: "100%",
preload: "auto",
playbackRates: [0.5, 1, 1.5, 2],
liveui: isLiveStream, // UI
//
inactivityTimeout: 0, //
controlBar: {
children: [
"playToggle",
"volumePanel",
"currentTimeDisplay",
"timeDivider",
"durationDisplay",
"progressControl",
"liveDisplay",
"remainingTimeDisplay",
"customControlSpacer",
"playbackRateMenuButton",
"fullscreenToggle",
],
},
html5: {
hls: {
enableLowInitialPlaylist: true,
smoothQualityChange: true,
},
},
sources: [
{
src: videoUrl.value,
type: videoType, //
},
],
});
//
if (!isLiveStream) {
player.ready(() => {
const liveDisplay = player.controlBar.getChild("liveDisplay");
if (liveDisplay) {
liveDisplay.el().style.display = "none";
}
});
}
//
player.on("error", () => {
const error = player.error();
console.error("播放器错误:", error);
//
//
let retryCount = 0;
const maxRetries = 10;
const attemptReload = () => {
if (retryCount < maxRetries && player && videoUrl.value) {
retryCount++;
setTimeout(() => {
try {
player.src({
src: videoUrl.value,
type: videoType, //
});
player.load();
player.play();
} catch (e) {
if (retryCount < maxRetries) {
attemptReload();
}
}
}, 3000);
}
};
attemptReload();
});
//
player.on("loadeddata", () => {});
//
player.on("play", () => {});
//
player.on("pause", () => {});
//
player.on("waiting", () => {});
} catch (error) {
//
}
};
//
const handleClose = () => {
// key
emit("close", props.videoData?.key);
};
// URL
watch(
videoUrl,
(newUrl, oldUrl) => {
if (newUrl && newUrl !== oldUrl) {
// URL DOM
setTimeout(() => initPlayer(), 200);
} else if (newUrl && !oldUrl) {
//
setTimeout(() => initPlayer(), 100);
}
},
{ immediate: true }
);
onMounted(() => {
if (videoUrl.value) {
initPlayer();
}
});
onUnmounted(() => {
//
if (player) {
player.dispose();
player = null;
}
});
</script>
<style scoped lang="scss">
.live-video-box {
width: 100%;
height: 100%;
position: relative;
background-color: #000;
border-radius: 4px;
overflow: hidden;
.live-video-title {
width: 100%;
height: 40px;
background-color: #2f6b98;
display: flex;
padding: 8px 16px;
color: #fff;
font-size: 13px;
justify-content: space-between;
align-items: center;
.title-actions {
display: flex;
gap: 8px;
align-items: center;
span {
font-size: 16px;
cursor: pointer;
transition: opacity 0.2s;
&:hover {
opacity: 0.8;
}
}
}
}
.video-container {
width: 100%;
height: calc(100% - 40px);
position: relative;
.video-player {
width: 100%;
height: 100%;
// video.js
:deep(.video-js) {
width: 100%;
height: 100%;
background-color: #000;
video {
object-fit: contain;
}
}
}
}
.video-empty {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #666;
background-color: #1a1a1a;
i {
font-size: 48px;
margin-bottom: 12px;
opacity: 0.5;
}
div {
font-size: 13px;
color: #999;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
<template>
<a-modal
:title="isEdit ? '编辑配置' : '新增配置'"
v-model:open="modalVisible"
:confirm-loading="localLoading"
width="600px"
:destroy-on-close="true"
@cancel="handleCancel"
@ok="handleOk"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
name="edit_form"
:labelCol="{ span: 5 }"
>
<a-form-item label="配置项名称" name="configName">
<a-input
v-model:value="formData.configName"
style="width: 100%"
placeholder="请输入配置项名称"
/>
</a-form-item>
<a-form-item label="配置类型" name="configType">
<a-input
v-model:value="formData.configType"
style="width: 100%"
placeholder="请输入配置类型"
/>
</a-form-item>
<a-form-item label="配置值" name="configValue">
<a-input
v-model:value="formData.configValue"
style="width: 100%"
placeholder="请输入配置值"
/>
</a-form-item>
<a-form-item label="描述" name="description">
<a-textarea
v-model:value="formData.description"
style="width: 100%"
placeholder="请输入描述"
:rows="3"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue';
import { message } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form';
interface Props {
visible: boolean;
initialValues?: any | null;
loading?: boolean;
}
const localLoading = ref(false);
const props = withDefaults(defineProps<Props>(), {
visible: false,
initialValues: null,
loading: false
});
const modalVisible = computed({
get: () => props.visible,
set: val => emit('update:visible', val)
});
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'cancel'): void;
(e: 'ok', values: any): void;
}>();
const formRef = ref();
const defaultFormData = reactive({
id: undefined,
configName: undefined,
configType: undefined,
configValue: undefined,
description: undefined
});
const formData: any = reactive({ ...defaultFormData });
const rules: Record<string, Rule[]> = {
configName: [
{ required: true, message: '请输入配置项名称', trigger: 'blur' }
],
configType: [{ required: true, message: '请输入配置类型', trigger: 'blur' }]
};
const isEdit = computed(() => !!props.initialValues);
const handleOk = async () => {
try {
await formRef.value.validate();
const submitValues = {
...formData
};
emit('ok', submitValues);
} catch (error) {
console.error('Validate Failed:', error);
message.error('请检查表单填写是否正确');
}
};
const initForm = () => {
if (props.initialValues) {
const values = props.initialValues;
Object.keys(formData).forEach(key => {
if (values.hasOwnProperty(key)) {
formData[key] = values[key];
}
});
} else {
resetForm();
}
};
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
}
Object.assign(formData, defaultFormData);
};
const handleCancel = () => {
emit('update:visible', false);
emit('cancel');
resetForm();
};
watch(
() => props.visible,
newVisible => {
if (newVisible) {
initForm();
}
},
{ immediate: false }
);
defineExpose({
localLoading
});
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,69 @@
<template>
<div class="config-management-search">
<BasicSearch
ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
@reset="handleReset"
@finish="onSearchFinish"
@values-change="onValuesChange"
>
<template #actions>
<a-tooltip title="新增配置">
<a-button @click="props.handleAdd" type="primary">
新增配置
</a-button>
</a-tooltip>
</template>
</BasicSearch>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import BasicSearch from '@/components/BasicSearch/index.vue';
interface Props {
handleAdd: () => void;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'reset', values: any): void;
(e: 'searchFinish', values: any): void;
}>();
const initSearchData = {
configName: ''
};
const searchData = ref<any>({ ...initSearchData });
const searchList: any = computed(() => [
{
type: 'Input',
name: 'configName',
label: '配置项名称',
fieldProps: {
allowClear: true
}
}
]);
const onSearchFinish = (values: any) => {
emit('searchFinish', values);
};
const onValuesChange = (changedValues: any, allValues: any) => {
searchData.value = { ...searchData.value, ...allValues };
};
const handleReset = () => {
emit('reset', initSearchData);
};
onMounted(() => {
emit('searchFinish', initSearchData);
});
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,200 @@
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\components\ConfigManagement\index.vue -->
<template>
<div class="config-management">
<ConfigManagementSearch
ref="configManagementSearch"
:handle-add="handleAdd"
@reset="handleReset"
@search-finish="onSearchFinish"
/>
<BasicTable
:columns="columns"
:data="dataSource"
:list-url="fetchConfigData"
:search-params="searchParams"
:enable-row-selection="true"
@selection-change="handleSelectionChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handleEdit(record)"
>编辑</a-button
>
<a-button
type="link"
size="small"
danger
@click="handleDelete(record)"
>删除</a-button
>
</a-space>
</template>
<template v-if="column.key === 'configType'">
<a-tag :color="getConfigTypeColor(record.configType)">
{{ record.configType }}
</a-tag>
</template>
</template>
</BasicTable>
<ConfigManagementForm
ref="configManagementForm"
v-model:visible="editModalVisible"
:initial-values="currentRecord"
@cancel="editModalCancel"
@ok="handleEditSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import BasicTable from '@/components/BasicTable/index.vue';
import ConfigManagementSearch from './ConfigManagementSearch.vue';
import ConfigManagementForm from './ConfigManagementForm.vue';
import { message } from 'ant-design-vue';
//
const columns = [
{
title: '配置项名称',
dataIndex: 'configName',
key: 'configName',
width: 150
},
{
title: '配置类型',
key: 'configType',
dataIndex: 'configType',
width: 120
},
{
title: '配置值',
dataIndex: 'configValue',
key: 'configValue',
ellipsis: true
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
ellipsis: true
},
{
title: '更新时间',
dataIndex: 'updateTime',
key: 'updateTime',
width: 180
},
{
title: '操作',
key: 'action',
width: 150,
fixed: 'right'
}
];
//
const searchParams = ref({});
//
const currentRecord = ref<any | null>(null);
const editModalVisible = ref(false);
//
const dataSource = ref([]);
//
const getConfigTypeColor = (type: string) => {
const colorMap: Record<string, string> = {
系统配置: 'blue',
显示配置: 'green',
业务配置: 'orange'
};
return colorMap[type] || 'default';
};
//
const fetchConfigData = (params: any) => {
return new Promise(resolve => {
setTimeout(() => {
resolve({
data: {
records: [
{
id: 1,
configName: '默认缩放级别',
configType: '显示配置',
configValue: '10',
description: '地图默认显示的缩放级别',
updateTime: '2023-05-15 10:30:00'
},
{
id: 2,
configName: '数据刷新间隔',
configType: '系统配置',
configValue: '300',
description: '数据自动刷新的时间间隔(秒)',
updateTime: '2023-06-20 14:45:00'
},
{
id: 3,
configName: '预警阈值',
configType: '业务配置',
configValue: '85%',
description: '触发预警的阈值百分比',
updateTime: '2023-07-10 09:15:00'
}
],
total: 3
}
});
}, 500);
});
};
const handleAdd = () => {
currentRecord.value = null;
editModalVisible.value = true;
};
//
const handleEdit = (record: any) => {
currentRecord.value = { ...record };
editModalVisible.value = true;
};
//
const handleDelete = (record: any) => {
message.warning(`删除配置: ${record.configName}`);
};
//
const onSearchFinish = (values: any) => {
console.log(values);
};
//
const handleReset = (params: any) => {};
//
const handleSelectionChange = (selectedRows: any[]) => {
console.log('选中的行:', selectedRows);
};
//
const editModalCancel = () => {
editModalVisible.value = false;
};
//
const handleEditSubmit = (values: any) => {
console.log(values);
};
</script>
<style scoped lang="scss">
.config-management {
height: 100%;
padding: 16px;
}
</style>

View File

@ -0,0 +1,178 @@
<template>
<a-modal
:title="isEdit ? '编辑图层' : '新增图层'"
v-model:open="modalVisible"
:confirm-loading="localLoading"
width="600px"
:destroy-on-close="true"
@cancel="handleCancel"
@ok="handleOk"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
name="edit_form"
:labelCol="{ span: 5 }"
>
<a-form-item label="图层名称" name="title">
<a-input
v-model:value="formData.title"
style="width: 100%"
placeholder="请输入图层名称"
/>
</a-form-item>
<a-form-item label="图层编码" name="code">
<a-input
v-model:value="formData.code"
style="width: 100%"
placeholder="请输入图层编码"
/>
</a-form-item>
<a-form-item label="标签显示级别" name="treeLevel">
<a-input-number
v-model:value="formData.treeLevel"
style="width: 100%"
placeholder="请输入标签显示级别"
:min="0"
:step="1"
/>
</a-form-item>
<a-form-item label="备注" name="description">
<a-textarea
rows="4"
v-model:value="formData.description"
style="width: 100%"
placeholder="请输入备注"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue';
import { message } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form';
// Props
interface Props {
visible: boolean;
initialValues?: any | null;
loading?: boolean;
}
//
const localLoading = ref(false);
const props = withDefaults(defineProps<Props>(), {
visible: false,
initialValues: null,
loading: false
});
const modalVisible = computed({
get: () => props.visible,
set: val => emit('update:visible', val)
});
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'cancel'): void;
(e: 'ok', values: any): void;
(
e: 'preview-click',
record: any,
type: string,
index: number,
action: string
): void;
}>();
//
const formRef = ref();
// 1.
const bodyLengthError = ref<string>('');
const weightError = ref<string>('');
//
const defaultFormData = reactive({
id: undefined,
title: undefined,
code: undefined,
treeLevel: undefined,
description: undefined
});
const formData: any = reactive({ ...defaultFormData });
//
const rules: Record<string, Rule[]> = {
title: [{ required: true, message: '请输入图层名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入图层编码', trigger: 'blur' }],
treeLevel: [
{ required: true, message: '请输入标签显示级别', trigger: 'blur' }
]
};
//
const isEdit = computed(() => !!props.initialValues);
//
const handleOk = async () => {
try {
//
await formRef.value.validate();
//
const submitValues = {
...formData
};
emit('ok', submitValues);
} catch (error) {
console.error('Validate Failed:', error);
message.error('请检查表单填写是否正确');
}
};
const initForm = () => {
if (props.initialValues) {
// --- ---
const values = props.initialValues;
//
Object.keys(formData).forEach(key => {
// initialValues
if (values.hasOwnProperty(key)) {
formData[key] = values[key];
}
});
} else {
resetForm();
}
};
//
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
}
Object.assign(formData, defaultFormData);
//
bodyLengthError.value = '';
weightError.value = '';
};
//
const handleCancel = () => {
emit('update:visible', false);
emit('cancel');
resetForm();
};
watch(
() => props.visible,
newVisible => {
if (newVisible) {
initForm();
}
},
{ immediate: false } // immediate false
);
//
defineExpose({
localLoading
});
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,71 @@
<template>
<div class="guoYuSheShiShuJuTianBao-search">
<BasicSearch
ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
@reset="handleReset"
@finish="onSearchFinish"
@values-change="onValuesChange"
>
<template #actions>
<a-tooltip title="新增图层">
<a-button @click="props.handleAdd" type="primary">
新增图层
</a-button>
</a-tooltip>
</template>
</BasicSearch>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, watch } from 'vue';
import BasicSearch from '@/components/BasicSearch/index.vue'; //
interface Props {
handleAdd: () => void;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'reset', values: any): void;
(e: 'searchFinish', values: any): void;
}>();
const initSearchData = {
title: ''
};
const searchData = ref<any>({ ...initSearchData });
const searchList: any = computed(() => [
{
type: 'Input',
name: 'title',
label: '图层名称',
fieldProps: {
allowClear: true
}
}
]);
// --- Methods ---
// 2.
const onSearchFinish = (values: any) => {
emit('searchFinish', values);
};
//
const onValuesChange = (changedValues: any, allValues: any) => {
searchData.value = { ...searchData.value, ...allValues };
};
//
const handleReset = () => {
emit('reset', initSearchData);
};
onMounted(() => {
emit('searchFinish', initSearchData);
});
</script>
<style lang="scss"></style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,219 @@
<template>
<a-modal
:title="isEdit ? '编辑图例数据' : '新增图例数据'"
v-model:open="modalVisible"
:confirm-loading="localLoading"
width="600px"
:destroy-on-close="true"
@cancel="handleCancel"
@ok="handleOk"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
name="edit_form"
:labelCol="{ span: 5 }"
>
<a-form-item label="图例名称" name="name">
<a-input
v-model:value="formData.name"
style="width: 100%"
placeholder="请输入图例名称"
/>
</a-form-item>
<a-form-item label="图例类型" name="code">
<a-select
v-model:value="formData.code"
style="width: 100%"
placeholder="请选择图例类型"
:options="options"
>
</a-select>
</a-form-item>
<a-form-item label="图例编码" name="nameEn">
<a-input
v-model:value="formData.nameEn"
style="width: 100%"
placeholder="请选择图例类型"
:options="options"
/>
</a-form-item>
<a-form-item label="icon编码" name="icon">
<a-input
v-model:value="formData.icon"
style="width: 100%"
placeholder="请输入icon编码"
/>
</a-form-item>
<a-form-item label="父级图例名称" name="parentName">
<a-input
v-model:value="formData.parentName"
style="width: 100%"
placeholder="请输入父级图例名称"
/>
</a-form-item>
<a-form-item label="图层名称" name="layerCodeName">
<a-input
v-model:value="formData.layerCodeName"
style="width: 100%"
placeholder="请输入图层名称"
/>
</a-form-item>
<a-form-item label="二维最小缩放" name="minZoom">
<a-input
v-model:value="formData.minZoom"
style="width: 100%"
placeholder="请输入二维最小缩放"
/>
</a-form-item>
<a-form-item label="二维最大缩放" name="maxZoom">
<a-input
v-model:value="formData.maxZoom"
style="width: 100%"
placeholder="请输入二维最大缩放"
/>
</a-form-item>
<a-form-item label="三维最小高程" name="minElevation">
<a-input
v-model:value="formData.minElevation"
style="width: 100%"
placeholder="请输入三维最小高程"
/>
</a-form-item>
<a-form-item label="三维最大高程" name="maxElevation">
<a-input
v-model:value="formData.maxElevation"
style="width: 100%"
placeholder="请输入三维最大高程"
/>
</a-form-item>
<a-form-item label="区间表达式" name="expression">
<a-input
v-model:value="formData.expression"
style="width: 100%"
placeholder="请输入区间表达式"
/>
</a-form-item>
<a-form-item label="备注" name="description">
<a-input
v-model:value="formData.description"
style="width: 100%"
placeholder="请输入备注"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from "vue";
import { message } from "ant-design-vue";
import type { Rule } from "ant-design-vue/es/form";
interface Props {
visible: boolean;
initialValues?: any | null;
loading?: boolean;
}
const localLoading = ref(false);
const props = withDefaults(defineProps<Props>(), {
visible: false,
initialValues: null,
loading: false,
});
const options = ref([
{ label: "色块图例", value: "colorLayer" },
{ label: "地图GIS", value: "baseLayer" },
{ label: "锚点", value: "pointLayer" },
]);
const modalVisible = computed({
get: () => props.visible,
set: (val) => emit("update:visible", val),
});
const emit = defineEmits<{
(e: "update:visible", value: boolean): void;
(e: "cancel"): void;
(e: "ok", values: any): void;
}>();
const formRef = ref();
const defaultFormData = reactive({
id: undefined,
dataName: undefined,
dataType: undefined,
relatedLegend: undefined,
dataSource: undefined,
});
const formData: any = reactive({ ...defaultFormData });
const rules: Record<string, Rule[]> = {
name: [{ required: true, message: "请输入图例名称", trigger: "blur" }],
nameEn: [{ required: true, message: "请输入图例编码", trigger: "blur" }],
code: [{ required: true, message: "请选择图例类型", trigger: "blur" }],
icon: [{ required: true, message: "请输入icon编码", trigger: "blur" }],
parentName: [{ required: true, message: "请输入父级图例名称", trigger: "blur" }],
layerCodeName: [{ required: true, message: "请输入图层名称", trigger: "blur" }],
};
const isEdit = computed(() => !!props.initialValues);
const handleOk = async () => {
try {
await formRef.value.validate();
const submitValues = {
...formData,
};
emit("ok", submitValues);
} catch (error) {
console.error("Validate Failed:", error);
message.error("请检查表单填写是否正确");
}
};
const initForm = () => {
if (props.initialValues) {
const values = props.initialValues;
Object.keys(formData).forEach((key) => {
if (values.hasOwnProperty(key)) {
formData[key] = values[key];
}
});
} else {
resetForm();
}
};
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
}
Object.assign(formData, defaultFormData);
};
const handleCancel = () => {
emit("update:visible", false);
emit("cancel");
resetForm();
};
watch(
() => props.visible,
(newVisible) => {
if (newVisible) {
initForm();
}
},
{ immediate: false }
);
defineExpose({
localLoading,
});
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,76 @@
<template>
<div class="legend-data-search">
<BasicSearch
ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
@reset="handleReset"
@finish="onSearchFinish"
@values-change="onValuesChange"
>
<template #actions>
<a-tooltip title="新增图例数据">
<a-button @click="props.handleAdd" type="primary"> 新增图例数据 </a-button>
</a-tooltip>
</template>
</BasicSearch>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import BasicSearch from "@/components/BasicSearch/index.vue";
interface Props {
handleAdd: () => void;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: "reset", values: any): void;
(e: "searchFinish", values: any): void;
}>();
const initSearchData = {
name: "",
parentName: "",
};
const searchData = ref<any>({ ...initSearchData });
const searchList: any = computed(() => [
{
type: "Input",
name: "name",
label: "图例名称",
fieldProps: {
allowClear: true,
},
},
{
type: "Select",
name: "code",
label: "父级图例",
fieldProps: {
allowClear: true,
},
},
]);
const onSearchFinish = (values: any) => {
emit("searchFinish", values);
};
const onValuesChange = (changedValues: any, allValues: any) => {
searchData.value = { ...searchData.value, ...allValues };
};
const handleReset = () => {
emit("reset", initSearchData);
};
onMounted(() => {
emit("searchFinish", initSearchData);
});
</script>
<style lang="scss"></style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,160 @@
<template>
<a-modal
:title="isEdit ? '编辑图例' : '新增图例'"
v-model:open="modalVisible"
:confirm-loading="localLoading"
width="600px"
:destroy-on-close="true"
@cancel="handleCancel"
@ok="handleOk"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
name="edit_form"
:labelCol="{ span: 5 }"
>
<a-form-item label="图例名称" name="name">
<a-input
v-model:value="formData.name"
style="width: 100%"
placeholder="请输入图例名称"
/>
</a-form-item>
<a-form-item label="图例编码" name="nameEn">
<a-input
v-model:value="formData.nameEn"
style="width: 100%"
placeholder="请输入图例编码"
/>
</a-form-item>
<a-form-item label="图例类型" name="code">
<a-select
v-model:value="formData.code"
style="width: 100%"
placeholder="请选择图例类型"
:options="options"
>
</a-select>
</a-form-item>
<a-form-item label="备注" name="description">
<a-textarea
rows="4"
v-model:value="formData.description"
style="width: 100%"
placeholder="请输入备注"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from "vue";
import { message } from "ant-design-vue";
import type { Rule } from "ant-design-vue/es/form";
interface Props {
visible: boolean;
initialValues?: any | null;
loading?: boolean;
}
const localLoading = ref(false);
const props = withDefaults(defineProps<Props>(), {
visible: false,
initialValues: null,
loading: false,
});
const options = ref([
{ label: "色块图例", value: "colorLayer" },
{ label: "地图GIS", value: "baseLayer" },
{ label: "锚点", value: "pointLayer" },
]);
const modalVisible = computed({
get: () => props.visible,
set: (val) => emit("update:visible", val),
});
const emit = defineEmits<{
(e: "update:visible", value: boolean): void;
(e: "cancel"): void;
(e: "ok", values: any): void;
}>();
const formRef = ref();
const defaultFormData = reactive({
id: undefined,
name: undefined,
nameEn: undefined,
code: undefined,
description: undefined,
});
const formData: any = reactive({ ...defaultFormData });
const rules: Record<string, Rule[]> = {
name: [{ required: true, message: "请输入图例名称", trigger: "blur" }],
nameEn: [{ required: true, message: "请输入图例编码", trigger: "blur" }],
code: [{ required: true, message: "请选择图例类型", trigger: "change" }],
};
const isEdit = computed(() => !!props.initialValues);
const handleOk = async () => {
try {
await formRef.value.validate();
const submitValues = {
...formData,
};
emit("ok", submitValues);
} catch (error) {
console.error("Validate Failed:", error);
message.error("请检查表单填写是否正确");
}
};
const initForm = () => {
if (props.initialValues) {
const values = props.initialValues;
Object.keys(formData).forEach((key) => {
if (values.hasOwnProperty(key)) {
formData[key] = values[key];
}
});
} else {
resetForm();
}
};
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
}
Object.assign(formData, defaultFormData);
};
const handleCancel = () => {
emit("update:visible", false);
emit("cancel");
resetForm();
};
watch(
() => props.visible,
(newVisible) => {
if (newVisible) {
initForm();
}
},
{ immediate: false }
);
defineExpose({
localLoading,
});
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,67 @@
<template>
<div class="legend-structure-search">
<BasicSearch
ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
@reset="handleReset"
@finish="onSearchFinish"
@values-change="onValuesChange"
>
<template #actions>
<a-tooltip title="新增图例">
<a-button @click="props.handleAdd" type="primary"> 新增图例 </a-button>
</a-tooltip>
</template>
</BasicSearch>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import BasicSearch from "@/components/BasicSearch/index.vue";
interface Props {
handleAdd: () => void;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: "reset", values: any): void;
(e: "searchFinish", values: any): void;
}>();
const initSearchData = {
name: "",
};
const searchData = ref<any>({ ...initSearchData });
const searchList: any = computed(() => [
{
type: "Input",
name: "name",
label: "图例名称",
fieldProps: {
allowClear: true,
},
},
]);
const onSearchFinish = (values: any) => {
emit("searchFinish", values);
};
const onValuesChange = (changedValues: any, allValues: any) => {
searchData.value = { ...searchData.value, ...allValues };
};
const handleReset = () => {
emit("reset", initSearchData);
};
onMounted(() => {
emit("searchFinish", initSearchData);
});
</script>
<style lang="scss"></style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,160 @@
<template>
<a-modal
:title="isEdit ? '编辑电站' : '新增电站'"
v-model:open="modalVisible"
:confirm-loading="localLoading"
width="600px"
:destroy-on-close="true"
@cancel="handleCancel"
@ok="handleOk"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
name="edit_form"
:labelCol="{ span: 5 }"
>
<a-form-item label="电站名称" name="stationName">
<a-input
v-model:value="formData.stationName"
style="width: 100%"
placeholder="请输入电站名称"
/>
</a-form-item>
<a-form-item label="所属流域" name="basin">
<a-input
v-model:value="formData.basin"
style="width: 100%"
placeholder="请输入所属流域"
/>
</a-form-item>
<a-form-item label="装机容量(MW)" name="capacity">
<a-input-number
v-model:value="formData.capacity"
style="width: 100%"
placeholder="请输入装机容量"
:min="0"
/>
</a-form-item>
<a-form-item label="状态" name="status">
<a-select v-model:value="formData.status" style="width: 100%">
<a-select-option value="running">运行中</a-select-option>
<a-select-option value="stopped">停运</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="位置坐标" name="coordinates">
<a-input
v-model:value="formData.coordinates"
style="width: 100%"
placeholder="请输入位置坐标"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue';
import { message } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form';
interface Props {
visible: boolean;
initialValues?: any | null;
loading?: boolean;
}
const localLoading = ref(false);
const props = withDefaults(defineProps<Props>(), {
visible: false,
initialValues: null,
loading: false
});
const modalVisible = computed({
get: () => props.visible,
set: val => emit('update:visible', val)
});
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'cancel'): void;
(e: 'ok', values: any): void;
}>();
const formRef = ref();
const defaultFormData = reactive({
id: undefined,
stationName: undefined,
basin: undefined,
capacity: undefined,
status: undefined,
coordinates: undefined
});
const formData: any = reactive({ ...defaultFormData });
const rules: Record<string, Rule[]> = {
stationName: [{ required: true, message: '请输入电站名称', trigger: 'blur' }],
basin: [{ required: true, message: '请输入所属流域', trigger: 'blur' }],
capacity: [{ required: true, message: '请输入装机容量', trigger: 'blur' }]
};
const isEdit = computed(() => !!props.initialValues);
const handleOk = async () => {
try {
await formRef.value.validate();
const submitValues = {
...formData
};
emit('ok', submitValues);
} catch (error) {
console.error('Validate Failed:', error);
message.error('请检查表单填写是否正确');
}
};
const initForm = () => {
if (props.initialValues) {
const values = props.initialValues;
Object.keys(formData).forEach(key => {
if (values.hasOwnProperty(key)) {
formData[key] = values[key];
}
});
} else {
resetForm();
}
};
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
}
Object.assign(formData, defaultFormData);
};
const handleCancel = () => {
emit('update:visible', false);
emit('cancel');
resetForm();
};
watch(
() => props.visible,
newVisible => {
if (newVisible) {
initForm();
}
},
{ immediate: false }
);
defineExpose({
localLoading
});
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,69 @@
<template>
<div class="station-management-search">
<BasicSearch
ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
@reset="handleReset"
@finish="onSearchFinish"
@values-change="onValuesChange"
>
<template #actions>
<a-tooltip title="新增电站">
<a-button @click="props.handleAdd" type="primary">
新增电站
</a-button>
</a-tooltip>
</template>
</BasicSearch>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import BasicSearch from '@/components/BasicSearch/index.vue';
interface Props {
handleAdd: () => void;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'reset', values: any): void;
(e: 'searchFinish', values: any): void;
}>();
const initSearchData = {
stationName: ''
};
const searchData = ref<any>({ ...initSearchData });
const searchList: any = computed(() => [
{
type: 'Input',
name: 'stationName',
label: '电站名称',
fieldProps: {
allowClear: true
}
}
]);
const onSearchFinish = (values: any) => {
emit('searchFinish', values);
};
const onValuesChange = (changedValues: any, allValues: any) => {
searchData.value = { ...searchData.value, ...allValues };
};
const handleReset = () => {
emit('reset', initSearchData);
};
onMounted(() => {
emit('searchFinish', initSearchData);
});
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,190 @@
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\components\StationManagement\index.vue -->
<template>
<div class="station-management">
<StationManagementSearch
ref="stationManagementSearch"
:handle-add="handleAdd"
@reset="handleReset"
@search-finish="onSearchFinish"
/>
<BasicTable
:columns="columns"
:data="dataSource"
:list-url="fetchStationData"
:search-params="searchParams"
:enable-row-selection="true"
@selection-change="handleSelectionChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handleEdit(record)"
>编辑</a-button
>
<a-button
type="link"
size="small"
danger
@click="handleDelete(record)"
>删除</a-button
>
</a-space>
</template>
<template v-if="column.key === 'status'">
<a-tag :color="record.status === 'running' ? 'green' : 'red'">
{{ record.status === 'running' ? '运行中' : '停运' }}
</a-tag>
</template>
</template>
</BasicTable>
<StationManagementForm
ref="stationManagementForm"
v-model:visible="editModalVisible"
:initial-values="currentRecord"
@cancel="editModalCancel"
@ok="handleEditSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import BasicTable from '@/components/BasicTable/index.vue';
import StationManagementSearch from './StationManagementSearch.vue';
import StationManagementForm from './StationManagementForm.vue';
import { message } from 'ant-design-vue';
//
const columns = [
{
title: '电站名称',
dataIndex: 'stationName',
key: 'stationName',
width: 150
},
{
title: '所属流域',
dataIndex: 'basin',
key: 'basin',
width: 120
},
{
title: '装机容量(MW)',
dataIndex: 'capacity',
key: 'capacity',
width: 120
},
{
title: '状态',
key: 'status',
dataIndex: 'status',
width: 100
},
{
title: '位置坐标',
dataIndex: 'coordinates',
key: 'coordinates',
ellipsis: true
},
{
title: '操作',
key: 'action',
width: 150,
fixed: 'right'
}
];
//
const searchParams = ref({});
//
const currentRecord = ref<any | null>(null);
const editModalVisible = ref(false);
//
const dataSource = ref([]);
//
const fetchStationData = (params: any) => {
return new Promise(resolve => {
setTimeout(() => {
resolve({
data: {
records: [
{
id: 1,
stationName: '三峡电站',
basin: '长江流域',
capacity: 22500,
status: 'running',
coordinates: '111.29,30.83'
},
{
id: 2,
stationName: '葛洲坝电站',
basin: '长江流域',
capacity: 2715,
status: 'running',
coordinates: '111.28,30.75'
},
{
id: 3,
stationName: '溪洛渡电站',
basin: '金沙江流域',
capacity: 13860,
status: 'stopped',
coordinates: '103.65,28.25'
}
],
total: 3
}
});
}, 500);
});
};
const handleAdd = () => {
currentRecord.value = null;
editModalVisible.value = true;
};
//
const handleEdit = (record: any) => {
currentRecord.value = { ...record };
editModalVisible.value = true;
};
//
const handleDelete = (record: any) => {
message.warning(`删除电站: ${record.stationName}`);
};
//
const onSearchFinish = (values: any) => {
console.log(values);
};
//
const handleReset = (params: any) => {};
//
const handleSelectionChange = (selectedRows: any[]) => {
console.log('选中的行:', selectedRows);
};
//
const editModalCancel = () => {
editModalVisible.value = false;
};
//
const handleEditSubmit = (values: any) => {
console.log(values);
};
</script>
<style scoped lang="scss">
.station-management {
height: 100%;
padding: 16px;
}
</style>

View File

@ -0,0 +1,169 @@
<template>
<a-modal
:title="isEdit ? '编辑倾斜摄影' : '新增倾斜摄影'"
v-model:open="modalVisible"
:confirm-loading="localLoading"
width="600px"
:destroy-on-close="true"
@cancel="handleCancel"
@ok="handleOk"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
name="edit_form"
:labelCol="{ span: 5 }"
>
<a-form-item label="项目名称" name="projectName">
<a-input
v-model:value="formData.projectName"
style="width: 100%"
placeholder="请输入项目名称"
/>
</a-form-item>
<a-form-item label="拍摄日期" name="captureDate">
<a-date-picker
v-model:value="formData.captureDate"
style="width: 100%"
placeholder="请选择拍摄日期"
/>
</a-form-item>
<a-form-item label="文件大小" name="fileSize">
<a-input
v-model:value="formData.fileSize"
style="width: 100%"
placeholder="请输入文件大小"
/>
</a-form-item>
<a-form-item label="状态" name="status">
<a-select v-model:value="formData.status" style="width: 100%">
<a-select-option value="processed">已处理</a-select-option>
<a-select-option value="pending">待处理</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="覆盖区域" name="coverageArea">
<a-textarea
v-model:value="formData.coverageArea"
style="width: 100%"
placeholder="请输入覆盖区域"
:rows="3"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue';
import { message } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form';
import dayjs from 'dayjs';
interface Props {
visible: boolean;
initialValues?: any | null;
loading?: boolean;
}
const localLoading = ref(false);
const props = withDefaults(defineProps<Props>(), {
visible: false,
initialValues: null,
loading: false
});
const modalVisible = computed({
get: () => props.visible,
set: val => emit('update:visible', val)
});
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
(e: 'cancel'): void;
(e: 'ok', values: any): void;
}>();
const formRef = ref();
const defaultFormData = reactive({
id: undefined,
projectName: undefined,
captureDate: undefined,
fileSize: undefined,
status: undefined,
coverageArea: undefined
});
const formData: any = reactive({ ...defaultFormData });
const rules: Record<string, Rule[]> = {
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
captureDate: [
{ required: true, message: '请选择拍摄日期', trigger: 'change' }
]
};
const isEdit = computed(() => !!props.initialValues);
const handleOk = async () => {
try {
await formRef.value.validate();
const submitValues = {
...formData,
captureDate: formData.captureDate
? dayjs(formData.captureDate).format('YYYY-MM-DD')
: undefined
};
emit('ok', submitValues);
} catch (error) {
console.error('Validate Failed:', error);
message.error('请检查表单填写是否正确');
}
};
const initForm = () => {
if (props.initialValues) {
const values = props.initialValues;
Object.keys(formData).forEach(key => {
if (values.hasOwnProperty(key)) {
if (key === 'captureDate' && values[key]) {
formData[key] = dayjs(values[key]);
} else {
formData[key] = values[key];
}
}
});
} else {
resetForm();
}
};
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
}
Object.assign(formData, defaultFormData);
};
const handleCancel = () => {
emit('update:visible', false);
emit('cancel');
resetForm();
};
watch(
() => props.visible,
newVisible => {
if (newVisible) {
initForm();
}
},
{ immediate: false }
);
defineExpose({
localLoading
});
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,69 @@
<template>
<div class="tilt-photo-management-search">
<BasicSearch
ref="basicSearchRef"
:searchList="searchList"
:initial-values="initSearchData"
@reset="handleReset"
@finish="onSearchFinish"
@values-change="onValuesChange"
>
<template #actions>
<a-tooltip title="新增倾斜摄影">
<a-button @click="props.handleAdd" type="primary">
新增倾斜摄影
</a-button>
</a-tooltip>
</template>
</BasicSearch>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import BasicSearch from '@/components/BasicSearch/index.vue';
interface Props {
handleAdd: () => void;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'reset', values: any): void;
(e: 'searchFinish', values: any): void;
}>();
const initSearchData = {
projectName: ''
};
const searchData = ref<any>({ ...initSearchData });
const searchList: any = computed(() => [
{
type: 'Input',
name: 'projectName',
label: '项目名称',
fieldProps: {
allowClear: true
}
}
]);
const onSearchFinish = (values: any) => {
emit('searchFinish', values);
};
const onValuesChange = (changedValues: any, allValues: any) => {
searchData.value = { ...searchData.value, ...allValues };
};
const handleReset = () => {
emit('reset', initSearchData);
};
onMounted(() => {
emit('searchFinish', initSearchData);
});
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,198 @@
<!-- d:\wordpack\WholeProcessPlatform\frontend\src\views\system\map\components\TiltPhotoManagement\index.vue -->
<template>
<div class="tilt-photo-management">
<TiltPhotoManagementSearch
ref="tiltPhotoManagementSearch"
:handle-add="handleAdd"
@reset="handleReset"
@search-finish="onSearchFinish"
/>
<BasicTable
:columns="columns"
:data="dataSource"
:list-url="fetchTiltPhotoData"
:search-params="searchParams"
:enable-row-selection="true"
@selection-change="handleSelectionChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handlePreview(record)"
>预览</a-button
>
<a-button type="link" size="small" @click="handleEdit(record)"
>编辑</a-button
>
<a-button
type="link"
size="small"
danger
@click="handleDelete(record)"
>删除</a-button
>
</a-space>
</template>
<template v-if="column.key === 'status'">
<a-tag :color="record.status === 'processed' ? 'green' : 'orange'">
{{ record.status === 'processed' ? '已处理' : '待处理' }}
</a-tag>
</template>
</template>
</BasicTable>
<TiltPhotoManagementForm
ref="tiltPhotoManagementForm"
v-model:visible="editModalVisible"
:initial-values="currentRecord"
@cancel="editModalCancel"
@ok="handleEditSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import BasicTable from '@/components/BasicTable/index.vue';
import TiltPhotoManagementSearch from './TiltPhotoManagementSearch.vue';
import TiltPhotoManagementForm from './TiltPhotoManagementForm.vue';
import { message } from 'ant-design-vue';
//
const columns = [
{
title: '项目名称',
dataIndex: 'projectName',
key: 'projectName',
width: 150
},
{
title: '拍摄日期',
dataIndex: 'captureDate',
key: 'captureDate',
width: 120
},
{
title: '文件大小',
dataIndex: 'fileSize',
key: 'fileSize',
width: 100
},
{
title: '状态',
key: 'status',
dataIndex: 'status',
width: 100
},
{
title: '覆盖区域',
dataIndex: 'coverageArea',
key: 'coverageArea',
ellipsis: true
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right'
}
];
//
const searchParams = ref({});
//
const currentRecord = ref<any | null>(null);
const editModalVisible = ref(false);
//
const dataSource = ref([]);
//
const fetchTiltPhotoData = (params: any) => {
return new Promise(resolve => {
setTimeout(() => {
resolve({
data: {
records: [
{
id: 1,
projectName: '三峡库区倾斜摄影',
captureDate: '2023-03-15',
fileSize: '2.5GB',
status: 'processed',
coverageArea: '三峡大坝周边5公里范围'
},
{
id: 2,
projectName: '葛洲坝区域航拍',
captureDate: '2023-04-20',
fileSize: '1.8GB',
status: 'processed',
coverageArea: '葛洲坝枢纽工程区域'
},
{
id: 3,
projectName: '金沙江下游航测',
captureDate: '2023-05-10',
fileSize: '3.2GB',
status: 'pending',
coverageArea: '金沙江下游河段'
}
],
total: 3
}
});
}, 500);
});
};
const handleAdd = () => {
currentRecord.value = null;
editModalVisible.value = true;
};
//
const handlePreview = (record: any) => {
message.info(`预览倾斜摄影: ${record.projectName}`);
};
//
const handleEdit = (record: any) => {
currentRecord.value = { ...record };
editModalVisible.value = true;
};
//
const handleDelete = (record: any) => {
message.warning(`删除倾斜摄影: ${record.projectName}`);
};
//
const onSearchFinish = (values: any) => {
console.log(values);
};
//
const handleReset = (params: any) => {};
//
const handleSelectionChange = (selectedRows: any[]) => {
console.log('选中的行:', selectedRows);
};
//
const editModalCancel = () => {
editModalVisible.value = false;
};
//
const handleEditSubmit = (values: any) => {
console.log(values);
};
</script>
<style scoped lang="scss">
.tilt-photo-management {
height: 100%;
padding: 16px;
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<div class="map-page">
<!-- Ant Design Tabs -->
<a-tabs v-model:activeKey="activeTab" class="map-tabs">
<a-tab-pane v-for="tab in tabs" :key="tab.value" :tab="tab.name">
<!-- 动态加载对应的组件 -->
<component :is="getComponent(tab.value)" />
</a-tab-pane>
</a-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, defineAsyncComponent } from 'vue';
// Tab
const tabs = [
{
name: '图层管理',
value: 'layer'
},
{
name: '图例结构管理',
value: 'legend'
},
{
name: '图例数据管理',
value: 'legendData'
},
{
name: '电站专题管理',
value: 'station'
},
{
name: '沿程配置管理',
value: 'config'
},
{
name: '倾斜摄影管理',
value: 'tilt'
}
];
// tab
const activeTab = ref('layer');
//
const getComponent = (value: string) => {
const componentMap: Record<string, any> = {
layer: defineAsyncComponent(
() => import('./components/LayerManagement/index.vue')
),
legend: defineAsyncComponent(
() => import('./components/LegendStructure/index.vue')
),
legendData: defineAsyncComponent(
() => import('./components/LegendData/index.vue')
),
station: defineAsyncComponent(
() => import('./components/StationManagement/index.vue')
),
config: defineAsyncComponent(
() => import('./components/ConfigManagement/index.vue')
),
tilt: defineAsyncComponent(
() => import('./components/TiltPhotoManagement/index.vue')
)
};
return componentMap[value] || null;
};
</script>
<style scoped lang="scss">
.map-page {
position: relative;
z-index: 900;
pointer-events: all;
width: 100%;
height: 100%;
padding-top: 2px;
background-color: #ffffff;
box-sizing: border-box;
overflow: hidden;
.content {
position: relative;
z-index: 900;
pointer-events: all;
width: 100%;
height: 100%;
background-color: #ffffff;
display: flex;
flex-direction: column;
gap: 10px;
box-sizing: border-box;
overflow: hidden;
}
}
:deep(.ant-tabs) {
height: 100%; // tabs header
padding: 0 13px;
}
.map-tabs {
:deep(.ant-tabs-content) {
height: 100%; // tabs header
overflow: auto;
}
:deep(.ant-tabs-tabpane) {
height: 100%;
}
}
</style>

View File

@ -54,6 +54,16 @@ export default ({ mode }: ConfigEnv): UserConfig => {
new RegExp('^/api/dec-lygk-base-server'),
'/api/dec-lygk-base-server'
)
},
'/api/wmp-env-server': {
target: 'https://211.99.26.225:12122',
changeOrigin: true,
secure: false,
rewrite: path =>
path.replace(
new RegExp('^/api/wmp-env-server'),
'/api/wmp-env-server'
)
}
}
},