Compare commits
11 Commits
7eb62ca37f
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| f2b7af5824 | |||
| 0f89e142e9 | |||
| 9723495f12 | |||
| 4aaf584451 | |||
| 330a9a48fd | |||
| e233911d5e | |||
| 821b895d04 | |||
| 6a60b4daf4 | |||
| e491dd78bf | |||
| 7ec0ad36ba | |||
| 0828edf6b1 |
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "frost-mqtt-client",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "一个使用 AI 开发的多功能 MQTT 桌面客户端,基于 Electron + Vue 3 + Naive UI 构建。",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Frost-ZX",
|
||||
@@ -28,6 +28,7 @@
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-vite": "^5.0.0",
|
||||
"eslint": "^9.39.1",
|
||||
"less": "^4.7.0",
|
||||
"eslint-plugin-vue": "^10.6.2",
|
||||
"naive-ui": "^2.44.1",
|
||||
"vite": "^7.2.6",
|
||||
|
||||
Generated
+185
-8
@@ -23,7 +23,7 @@ importers:
|
||||
version: 2.1.0(eslint@9.39.5(jiti@2.7.0))
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.8(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0))(vue@3.5.39)
|
||||
version: 6.0.8(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0)(less@4.7.0))(vue@3.5.39)
|
||||
electron:
|
||||
specifier: ^39.2.6
|
||||
version: 39.8.10
|
||||
@@ -32,19 +32,22 @@ importers:
|
||||
version: 26.15.3(electron-builder-squirrel-windows@26.15.3)
|
||||
electron-vite:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0))
|
||||
version: 5.0.0(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0)(less@4.7.0))
|
||||
eslint:
|
||||
specifier: ^9.39.1
|
||||
version: 9.39.5(jiti@2.7.0)
|
||||
eslint-plugin-vue:
|
||||
specifier: ^10.6.2
|
||||
version: 10.9.2(eslint@9.39.5(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@9.39.5(jiti@2.7.0)))
|
||||
less:
|
||||
specifier: ^4.7.0
|
||||
version: 4.7.0
|
||||
naive-ui:
|
||||
specifier: ^2.44.1
|
||||
version: 2.44.1(vue@3.5.39)
|
||||
vite:
|
||||
specifier: ^7.2.6
|
||||
version: 7.3.6(@types/node@26.1.1)(jiti@2.7.0)
|
||||
version: 7.3.6(@types/node@26.1.1)(jiti@2.7.0)(less@4.7.0)
|
||||
vue:
|
||||
specifier: ^3.5.25
|
||||
version: 3.5.39
|
||||
@@ -1088,6 +1091,10 @@ packages:
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
copy-anything@3.0.5:
|
||||
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
||||
engines: {node: '>=12.13'}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
@@ -1120,6 +1127,22 @@ packages:
|
||||
date-fns@4.4.0:
|
||||
resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==}
|
||||
|
||||
debug@2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -1232,6 +1255,10 @@ packages:
|
||||
err-code@2.0.3:
|
||||
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
|
||||
|
||||
errno@0.1.8:
|
||||
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
|
||||
hasBin: true
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1544,6 +1571,14 @@ packages:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
@@ -1582,6 +1617,10 @@ packages:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-what@4.1.16:
|
||||
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
||||
engines: {node: '>=12.13'}
|
||||
|
||||
isarray@1.0.0:
|
||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||
|
||||
@@ -1660,6 +1699,11 @@ packages:
|
||||
lazy-val@1.0.5:
|
||||
resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
|
||||
|
||||
less@4.7.0:
|
||||
resolution: {integrity: sha512-i7dAlT3+boO0mMh1G4cex0vz1lLAScmBbikm1VEDNv+cy0ore1CUo2UtX8m3N9QLE5WYDr4ISbiCRzHNGyFkrA==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -1694,6 +1738,10 @@ packages:
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
make-dir@5.1.0:
|
||||
resolution: {integrity: sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
matcher@3.0.0:
|
||||
resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1710,6 +1758,11 @@ packages:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime@1.6.0:
|
||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
mime@2.6.0:
|
||||
resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
@@ -1761,6 +1814,9 @@ packages:
|
||||
engines: {node: '>=16.0.0'}
|
||||
hasBin: true
|
||||
|
||||
ms@2.0.0:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -1778,6 +1834,16 @@ packages:
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
needle@2.9.1:
|
||||
resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==}
|
||||
engines: {node: '>= 4.4.x'}
|
||||
hasBin: true
|
||||
|
||||
needle@3.5.0:
|
||||
resolution: {integrity: sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==}
|
||||
engines: {node: '>= 4.4.x'}
|
||||
hasBin: true
|
||||
|
||||
node-abi@4.33.0:
|
||||
resolution: {integrity: sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
@@ -1839,6 +1905,10 @@ packages:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
parse-node-version@1.0.1:
|
||||
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1890,6 +1960,9 @@ packages:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
probe-image-size@7.3.0:
|
||||
resolution: {integrity: sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==}
|
||||
|
||||
proc-log@6.1.0:
|
||||
resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==}
|
||||
engines: {node: ^20.17.0 || >=22.9.0}
|
||||
@@ -1912,6 +1985,9 @@ packages:
|
||||
proper-lockfile@4.1.2:
|
||||
resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
|
||||
|
||||
prr@1.0.1:
|
||||
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
||||
|
||||
pump@3.0.4:
|
||||
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||
|
||||
@@ -1994,6 +2070,9 @@ packages:
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
sanitize-filename@1.6.4:
|
||||
resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==}
|
||||
|
||||
@@ -2074,6 +2153,9 @@ packages:
|
||||
resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
stream-parser@0.3.1:
|
||||
resolution: {integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2993,10 +3075,10 @@ snapshots:
|
||||
'@types/node': 22.20.1
|
||||
optional: true
|
||||
|
||||
'@vitejs/plugin-vue@6.0.8(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0))(vue@3.5.39)':
|
||||
'@vitejs/plugin-vue@6.0.8(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0)(less@4.7.0))(vue@3.5.39)':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.1
|
||||
vite: 7.3.6(@types/node@26.1.1)(jiti@2.7.0)
|
||||
vite: 7.3.6(@types/node@26.1.1)(jiti@2.7.0)(less@4.7.0)
|
||||
vue: 3.5.39
|
||||
|
||||
'@vue/compiler-core@3.5.39':
|
||||
@@ -3320,6 +3402,10 @@ snapshots:
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
copy-anything@3.0.5:
|
||||
dependencies:
|
||||
is-what: 4.1.16
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
cross-dirname@0.1.0:
|
||||
@@ -3348,6 +3434,16 @@ snapshots:
|
||||
|
||||
date-fns@4.4.0: {}
|
||||
|
||||
debug@2.6.9:
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
optional: true
|
||||
|
||||
debug@3.2.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
optional: true
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -3455,7 +3551,7 @@ snapshots:
|
||||
|
||||
electron-to-chromium@1.5.392: {}
|
||||
|
||||
electron-vite@5.0.0(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0)):
|
||||
electron-vite@5.0.0(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0)(less@4.7.0)):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7)
|
||||
@@ -3463,7 +3559,7 @@ snapshots:
|
||||
esbuild: 0.25.12
|
||||
magic-string: 0.30.21
|
||||
picocolors: 1.1.1
|
||||
vite: 7.3.6(@types/node@26.1.1)(jiti@2.7.0)
|
||||
vite: 7.3.6(@types/node@26.1.1)(jiti@2.7.0)(less@4.7.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3499,6 +3595,11 @@ snapshots:
|
||||
|
||||
err-code@2.0.3: {}
|
||||
|
||||
errno@0.1.8:
|
||||
dependencies:
|
||||
prr: 1.0.1
|
||||
optional: true
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
@@ -3909,6 +4010,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
optional: true
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
optional: true
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
@@ -3937,6 +4048,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-what@4.1.16: {}
|
||||
|
||||
isarray@1.0.0: {}
|
||||
|
||||
isbinaryfile@4.0.10: {}
|
||||
@@ -3996,6 +4109,21 @@ snapshots:
|
||||
|
||||
lazy-val@1.0.5: {}
|
||||
|
||||
less@4.7.0:
|
||||
dependencies:
|
||||
copy-anything: 3.0.5
|
||||
parse-node-version: 1.0.1
|
||||
optionalDependencies:
|
||||
errno: 0.1.8
|
||||
graceful-fs: 4.2.11
|
||||
make-dir: 5.1.0
|
||||
mime: 1.6.0
|
||||
needle: 3.5.0
|
||||
probe-image-size: 7.3.0
|
||||
source-map: 0.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
@@ -4027,6 +4155,9 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
make-dir@5.1.0:
|
||||
optional: true
|
||||
|
||||
matcher@3.0.0:
|
||||
dependencies:
|
||||
escape-string-regexp: 4.0.0
|
||||
@@ -4040,6 +4171,9 @@ snapshots:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
mime@1.6.0:
|
||||
optional: true
|
||||
|
||||
mime@2.6.0: {}
|
||||
|
||||
mimic-response@1.0.1: {}
|
||||
@@ -4105,6 +4239,9 @@ snapshots:
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
ms@2.0.0:
|
||||
optional: true
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
naive-ui@2.44.1(vue@3.5.39):
|
||||
@@ -4133,6 +4270,21 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
needle@2.9.1:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
iconv-lite: 0.4.24
|
||||
sax: 1.6.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
needle@3.5.0:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
sax: 1.6.0
|
||||
optional: true
|
||||
|
||||
node-abi@4.33.0:
|
||||
dependencies:
|
||||
semver: 7.7.4
|
||||
@@ -4205,6 +4357,8 @@ snapshots:
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
|
||||
parse-node-version@1.0.1: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
path-is-absolute@1.0.1: {}
|
||||
@@ -4252,6 +4406,15 @@ snapshots:
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
probe-image-size@7.3.0:
|
||||
dependencies:
|
||||
lodash.merge: 4.6.2
|
||||
needle: 2.9.1
|
||||
stream-parser: 0.3.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
proc-log@6.1.0: {}
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
@@ -4271,6 +4434,9 @@ snapshots:
|
||||
retry: 0.12.0
|
||||
signal-exit: 3.0.7
|
||||
|
||||
prr@1.0.1:
|
||||
optional: true
|
||||
|
||||
pump@3.0.4:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.5
|
||||
@@ -4385,6 +4551,9 @@ snapshots:
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
optional: true
|
||||
|
||||
sanitize-filename@1.6.4:
|
||||
dependencies:
|
||||
truncate-utf8-bytes: 1.0.2
|
||||
@@ -4444,6 +4613,13 @@ snapshots:
|
||||
|
||||
stat-mode@1.0.0: {}
|
||||
|
||||
stream-parser@0.3.1:
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
@@ -4561,7 +4737,7 @@ snapshots:
|
||||
evtd: 0.2.4
|
||||
vue: 3.5.39
|
||||
|
||||
vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0):
|
||||
vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0)(less@4.7.0):
|
||||
dependencies:
|
||||
esbuild: 0.28.1
|
||||
fdir: 6.5.0(picomatch@4.0.5)
|
||||
@@ -4573,6 +4749,7 @@ snapshots:
|
||||
'@types/node': 26.1.1
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.7.0
|
||||
less: 4.7.0
|
||||
|
||||
vooks@0.2.12(vue@3.5.39):
|
||||
dependencies:
|
||||
|
||||
@@ -77,6 +77,17 @@ export function registerIpcHandlers() {
|
||||
return mqttManager.importServers(serverList);
|
||||
});
|
||||
|
||||
// ==================== 快速发送 ====================
|
||||
|
||||
ipcMain.handle('mqtt:get-quick-send-list', () => {
|
||||
return mqttManager.getQuickSendList();
|
||||
});
|
||||
|
||||
ipcMain.handle('mqtt:save-quick-send-list', (_, list) => {
|
||||
mqttManager.saveQuickSendList(list);
|
||||
return true;
|
||||
});
|
||||
|
||||
// ==================== 连接管理 ====================
|
||||
|
||||
ipcMain.handle('mqtt:connect', (_, id) => {
|
||||
|
||||
@@ -35,6 +35,7 @@ class MqttManager {
|
||||
this.listeners = new Set(); // IPC callback functions
|
||||
this.msgIdCounter = 0; // 用于生成唯一消息 id
|
||||
this.dataPath = join(app.getPath('userData'), 'frost-mqtt-data.json');
|
||||
this.quickSendList = [];
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
@@ -101,6 +102,12 @@ class MqttManager {
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(data.quickSendList)) {
|
||||
this.quickSendList = data.quickSendList.filter((item) => {
|
||||
return item && typeof item.id === 'string' && typeof item.topic === 'string';
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
this.initDefaultServers();
|
||||
}
|
||||
@@ -124,6 +131,7 @@ class MqttManager {
|
||||
})),
|
||||
messages: Object.fromEntries(this.messages),
|
||||
publishHistory: Object.fromEntries(this.publishHistory),
|
||||
quickSendList: this.quickSendList,
|
||||
};
|
||||
|
||||
writeFileSync(this.dataPath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
@@ -409,6 +417,30 @@ class MqttManager {
|
||||
return this.getServers();
|
||||
}
|
||||
|
||||
// ==================== 快速发送 ====================
|
||||
|
||||
/**
|
||||
* 获取快速发送列表
|
||||
* @returns {QuickSendItem[]} 快速发送项数组
|
||||
*/
|
||||
getQuickSendList() {
|
||||
return this.quickSendList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存快速发送列表
|
||||
* @param {QuickSendItem[]} list - 快速发送项数组
|
||||
*/
|
||||
saveQuickSendList(list) {
|
||||
if (!Array.isArray(list)) {
|
||||
return;
|
||||
}
|
||||
this.quickSendList = list.filter((item) => {
|
||||
return item && typeof item.id === 'string' && typeof item.topic === 'string';
|
||||
});
|
||||
this.saveData();
|
||||
}
|
||||
|
||||
// ==================== 连接管理 ====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,10 @@ const api = {
|
||||
exportServers: () => ipcRenderer.invoke('mqtt:export-servers'),
|
||||
importServers: (serverList) => ipcRenderer.invoke('mqtt:import-servers', serverList),
|
||||
|
||||
// 快速发送
|
||||
getQuickSendList: () => ipcRenderer.invoke('mqtt:get-quick-send-list'),
|
||||
saveQuickSendList: (list) => ipcRenderer.invoke('mqtt:save-quick-send-list', list),
|
||||
|
||||
// 连接管理
|
||||
connect: (id) => ipcRenderer.invoke('mqtt:connect', id),
|
||||
disconnect: (id) => ipcRenderer.invoke('mqtt:disconnect', id),
|
||||
|
||||
@@ -172,7 +172,38 @@ function handleSwitchTab(tab) {
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
<style lang="less">
|
||||
:root {
|
||||
--frost-primary-color: #0ea5a0;
|
||||
--frost-primary-color-hover: #0b8c87;
|
||||
--frost-primary-color-pressed: #0b8c87;
|
||||
--frost-primary-color-suppl: #0ea5a0;
|
||||
--frost-success-color: #22c55e;
|
||||
--frost-success-color-hover: #16a34a;
|
||||
--frost-success-color-pressed: #15803d;
|
||||
--frost-success-color-suppl: #22c55e;
|
||||
--frost-warning-color: #f59e0b;
|
||||
--frost-warning-color-hover: #d97706;
|
||||
--frost-warning-color-pressed: #b45309;
|
||||
--frost-warning-color-suppl: #f59e0b;
|
||||
--frost-error-color: #ef4444;
|
||||
--frost-error-color-hover: #dc2626;
|
||||
--frost-error-color-pressed: #b91c1c;
|
||||
--frost-error-color-suppl: #ef4444;
|
||||
--frost-info-color: #3b82f6;
|
||||
--frost-info-color-hover: #2563eb;
|
||||
--frost-info-color-pressed: #1d4ed8;
|
||||
--frost-info-color-suppl: #3b82f6;
|
||||
--frost-color-text-tertiary: #999;
|
||||
--frost-color-topic: #d25a00;
|
||||
--frost-color-bg-light: #f5f7fa;
|
||||
--frost-color-bg-item: #fafafa;
|
||||
--frost-shadow-black-4: rgba(0, 0, 0, 0.04);
|
||||
--frost-shadow-black-6: rgba(0, 0, 0, 0.06);
|
||||
--frost-shadow-black-8: rgba(0, 0, 0, 0.08);
|
||||
--frost-shadow-black-12: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
html, body, #app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -189,12 +220,12 @@ html, body, #app {
|
||||
padding: 0 0 16px 0;
|
||||
height: calc(100vh - 50px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dashboard-content {
|
||||
&.dashboard-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-scroll-wrapper {
|
||||
@@ -210,29 +241,27 @@ html, body, #app {
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.publish-panel,
|
||||
.messages-panel {
|
||||
.publish-panel,
|
||||
.messages-panel {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-panel {
|
||||
.publish-panel {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.messages-panel {
|
||||
.messages-panel {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.message-center-layout {
|
||||
@media (max-width: 900px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -242,22 +271,27 @@ html, body, #app {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(144, 147, 153, 0.5) transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
|
||||
&-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
}
|
||||
|
||||
&-thumb {
|
||||
background-color: rgba(144, 147, 153, 0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(144, 147, 153, 0.8);
|
||||
}
|
||||
::-webkit-scrollbar-corner {
|
||||
}
|
||||
}
|
||||
|
||||
&-corner {
|
||||
display: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -216,7 +216,7 @@ async function handleImportFile(event) {
|
||||
</n-layout-sider>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
:deep(.sidebar-scroll-container) {
|
||||
height: 100%;
|
||||
overflow: hidden !important;
|
||||
@@ -242,7 +242,7 @@ async function handleImportFile(event) {
|
||||
.sidebar-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #0ea5a0;
|
||||
background: var(--frost-primary-color);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -278,15 +278,19 @@ async function handleImportFile(event) {
|
||||
margin-bottom: 4px;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.server-item:hover {
|
||||
&:hover {
|
||||
background: var(--n-color-hover);
|
||||
}
|
||||
|
||||
.server-item.active {
|
||||
.server-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--n-color-pressed);
|
||||
border-color: #0ea5a0;
|
||||
border-color: var(--frost-primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.server-item-top {
|
||||
@@ -310,23 +314,19 @@ async function handleImportFile(event) {
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.server-item:hover .server-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.server-url {
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.server-url :deep(.n-tag) {
|
||||
:deep(.n-tag) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.server-url :deep(.n-tag__content) {
|
||||
:deep(.n-tag__content) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.server-bottom {
|
||||
@@ -347,23 +347,23 @@ async function handleImportFile(event) {
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.status-dot.connected {
|
||||
background: #22c55e;
|
||||
}
|
||||
&.connected {
|
||||
background: var(--frost-success-color);
|
||||
}
|
||||
|
||||
.status-dot.connecting {
|
||||
background: #f59e0b;
|
||||
&.connecting {
|
||||
background: var(--frost-warning-color);
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.status-dot.disconnected {
|
||||
background: #999;
|
||||
}
|
||||
&.disconnected {
|
||||
background: var(--frost-color-text-tertiary);
|
||||
}
|
||||
|
||||
.status-dot.error {
|
||||
background: #ef4444;
|
||||
&.error {
|
||||
background: var(--frost-error-color);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
|
||||
@@ -189,25 +189,27 @@ async function handleToggleConnection(srv) {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
.dashboard {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.server-descriptions :deep(.n-descriptions-table) {
|
||||
.server-descriptions {
|
||||
:deep(.n-descriptions-table) {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.server-descriptions :deep(.n-descriptions-table-header),
|
||||
.server-descriptions :deep(.n-descriptions-table-content) {
|
||||
:deep(.n-descriptions-table-header),
|
||||
:deep(.n-descriptions-table-content) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.server-descriptions :deep(.n-descriptions-table-content) {
|
||||
:deep(.n-descriptions-table-content) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
@@ -215,23 +217,23 @@ async function handleToggleConnection(srv) {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.status-dot.connected {
|
||||
background: #22c55e;
|
||||
}
|
||||
&.connected {
|
||||
background: var(--frost-success-color);
|
||||
}
|
||||
|
||||
.status-dot.connecting {
|
||||
background: #f59e0b;
|
||||
&.connecting {
|
||||
background: var(--frost-warning-color);
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.status-dot.disconnected {
|
||||
background: #999;
|
||||
}
|
||||
&.disconnected {
|
||||
background: var(--frost-color-text-tertiary);
|
||||
}
|
||||
|
||||
.status-dot.error {
|
||||
background: #ef4444;
|
||||
&.error {
|
||||
background: var(--frost-error-color);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useMqttStore } from '../stores/mqtt.js';
|
||||
import { NCard, NSpace, NInput, NButton, NEmpty, useMessage } from 'naive-ui';
|
||||
import { NCard, NSelect, NButton, NEmpty, useMessage } from 'naive-ui';
|
||||
|
||||
// 全局状态与消息 API
|
||||
const store = useMqttStore();
|
||||
const message = useMessage();
|
||||
|
||||
// 主题过滤关键字
|
||||
const filter = ref('');
|
||||
// 已选中的过滤主题列表
|
||||
const filter = ref([]);
|
||||
|
||||
/**
|
||||
* 复制主题到剪贴板
|
||||
@@ -24,22 +24,65 @@ async function copyTopic(topic) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据过滤关键字筛选后的消息列表
|
||||
* 可过滤的主题选项(来自已配置主题和已接收消息)
|
||||
* @type {import('vue').ComputedRef<Array<{label: string, value: string}>>}
|
||||
*/
|
||||
const topicOptions = computed(() => {
|
||||
|
||||
let server = store.activeServer.value;
|
||||
let serverTopics = server?.topics?.map((t) => t.topic) || [];
|
||||
let messageTopics = store.messages.value.map((m) => m.topic);
|
||||
let all = [...new Set([...serverTopics, ...messageTopics])];
|
||||
|
||||
all.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
return all.map((topic) => ({ label: topic, value: topic }));
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* 判断消息主题是否匹配指定的过滤主题(支持 + 单层通配符与 # 多层通配符)
|
||||
* @param {string} filterTopic - 过滤主题,可包含 + 或 #
|
||||
* @param {string} messageTopic - 消息主题
|
||||
* @returns {boolean} 是否匹配
|
||||
*/
|
||||
function topicMatchesFilter(filterTopic, messageTopic) {
|
||||
|
||||
if (filterTopic === '#') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filterTopic.endsWith('/#')) {
|
||||
let prefix = filterTopic.slice(0, -2);
|
||||
return messageTopic === prefix || messageTopic.startsWith(prefix + '/');
|
||||
}
|
||||
|
||||
let filterParts = filterTopic.split('/');
|
||||
let messageParts = messageTopic.split('/');
|
||||
|
||||
if (filterParts.length !== messageParts.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filterParts.every((part, index) => part === '+' || part === messageParts[index]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据选中主题筛选后的消息列表(支持 + / # 通配符)
|
||||
* @type {import('vue').ComputedRef<Array<Object>>}
|
||||
*/
|
||||
const filteredMessages = computed(() => {
|
||||
|
||||
if (!filter.value.trim()) {
|
||||
if (!filter.value || filter.value.length === 0) {
|
||||
return store.messages.value;
|
||||
}
|
||||
|
||||
let f = filter.value.trim().toLowerCase();
|
||||
return store.messages.value.filter((m) =>
|
||||
filter.value.some((f) => topicMatchesFilter(f, m.topic))
|
||||
);
|
||||
|
||||
return store.messages.value.filter((m) => {
|
||||
return m.topic.toLowerCase().includes(f);
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* 清空当前活动服务器的消息列表
|
||||
@@ -79,10 +122,8 @@ function formatPayload(payload) {
|
||||
}"
|
||||
>
|
||||
<template #header>
|
||||
<n-space
|
||||
align="center"
|
||||
justify="space-between"
|
||||
style="width: 100%"
|
||||
<div
|
||||
style="display: flex; flex-direction: column; gap: 8px; width: 100%"
|
||||
>
|
||||
<div>
|
||||
<span>消息监控</span>
|
||||
@@ -90,20 +131,26 @@ function formatPayload(payload) {
|
||||
style="font-size: 12px; opacity: 0.55; margin-left: 8px"
|
||||
>实时查看接收和发送的 MQTT 消息</span>
|
||||
</div>
|
||||
<n-space>
|
||||
<n-input
|
||||
<div
|
||||
style="display: flex; align-items: center; gap: 8px; width: 100%"
|
||||
>
|
||||
<n-select
|
||||
v-model:value="filter"
|
||||
size="small"
|
||||
placeholder="过滤主题..."
|
||||
style="width: 180px"
|
||||
style="flex: 1 1 auto; max-width: 480px; min-width: 0"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
max-tag-count="responsive"
|
||||
:options="topicOptions"
|
||||
/>
|
||||
<n-button
|
||||
size="small"
|
||||
@click="handleClear"
|
||||
>清空消息</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<n-empty
|
||||
v-if="filteredMessages.length === 0"
|
||||
@@ -154,29 +201,29 @@ function formatPayload(payload) {
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
.messages-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.messages-view :deep(.n-card) {
|
||||
:deep(.n-card) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.messages-view :deep(.n-card__content) {
|
||||
:deep(.n-card__content) {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-log {
|
||||
background: #f5f7fa;
|
||||
background: var(--frost-color-bg-light);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
height: 100%;
|
||||
@@ -192,7 +239,7 @@ function formatPayload(payload) {
|
||||
|
||||
.msg-entry {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-bottom: 1px solid var(--frost-shadow-black-6);
|
||||
}
|
||||
|
||||
.msg-header {
|
||||
@@ -203,45 +250,51 @@ function formatPayload(payload) {
|
||||
}
|
||||
|
||||
.msg-time {
|
||||
color: #999;
|
||||
color: var(--frost-color-text-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.msg-dir {
|
||||
flex-shrink: 0;
|
||||
font-weight: 700;
|
||||
width: 28px;
|
||||
|
||||
&.pub {
|
||||
color: var(--frost-info-color);
|
||||
}
|
||||
|
||||
&.sub {
|
||||
color: var(--frost-success-color);
|
||||
}
|
||||
}
|
||||
.msg-dir.pub {
|
||||
color: #3b82f6;
|
||||
}
|
||||
.msg-dir.sub {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.msg-topic {
|
||||
color: #d25a00;
|
||||
color: var(--frost-color-topic);
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-topic:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.msg-payload {
|
||||
margin: 6px 0 0;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
background: var(--frost-shadow-black-4);
|
||||
color: #333;
|
||||
font: inherit;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.msg-meta {
|
||||
color: #999;
|
||||
color: var(--frost-color-text-tertiary);
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ async function copyTopic(topic) {
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
.publish-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -307,61 +307,61 @@ async function copyTopic(topic) {
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.publish-view :deep(.n-tabs) {
|
||||
:deep(.n-tabs) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-view :deep(.n-tabs-nav) {
|
||||
:deep(.n-tabs-nav) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-view :deep(.n-tabs-pane-wrapper) {
|
||||
:deep(.n-tabs-pane-wrapper) {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-view :deep(.n-tabs-tab-panels),
|
||||
.publish-view :deep(.n-tabs-pane-wrapper) {
|
||||
:deep(.n-tabs-tab-panels),
|
||||
:deep(.n-tabs-pane-wrapper) {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-view :deep(.n-tab-pane) {
|
||||
:deep(.n-tab-pane) {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-view :deep(.n-tab-pane > .n-card) {
|
||||
:deep(.n-tab-pane > .n-card) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-view :deep(.n-card__content) {
|
||||
:deep(.n-card__content) {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.publish-view :deep(.n-card__content > div) {
|
||||
> div {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.topic-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.topic-field :deep(.n-form-item-blank) {
|
||||
:deep(.n-form-item-blank) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-form {
|
||||
@@ -369,34 +369,34 @@ async function copyTopic(topic) {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.publish-form .payload-field {
|
||||
.payload-field {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.publish-form .payload-field :deep(.n-form-item-blank) {
|
||||
:deep(.n-form-item-blank) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-form .payload-field :deep(.n-input) {
|
||||
:deep(.n-input) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-form .payload-field :deep(.n-input__textarea-el),
|
||||
.publish-form .payload-field :deep(textarea) {
|
||||
:deep(.n-input__textarea-el),
|
||||
:deep(textarea) {
|
||||
resize: none !important;
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msg-log {
|
||||
background: #f5f7fa;
|
||||
background: var(--frost-color-bg-light);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
height: 100%;
|
||||
@@ -411,7 +411,7 @@ async function copyTopic(topic) {
|
||||
|
||||
.msg-entry {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-bottom: 1px solid var(--frost-shadow-black-6);
|
||||
}
|
||||
|
||||
.msg-header {
|
||||
@@ -422,7 +422,7 @@ async function copyTopic(topic) {
|
||||
}
|
||||
|
||||
.msg-time {
|
||||
color: #999;
|
||||
color: var(--frost-color-text-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -430,31 +430,31 @@ async function copyTopic(topic) {
|
||||
flex-shrink: 0;
|
||||
font-weight: 700;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.msg-dir.pub {
|
||||
color: #3b82f6;
|
||||
&.pub {
|
||||
color: var(--frost-info-color);
|
||||
}
|
||||
}
|
||||
|
||||
.msg-topic {
|
||||
color: #d25a00;
|
||||
color: var(--frost-color-topic);
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.msg-topic:hover {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-payload {
|
||||
margin: 6px 0 0;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
background: var(--frost-shadow-black-4);
|
||||
color: #333;
|
||||
font: inherit;
|
||||
white-space: pre-wrap;
|
||||
@@ -462,7 +462,7 @@ async function copyTopic(topic) {
|
||||
}
|
||||
|
||||
.msg-meta {
|
||||
color: #999;
|
||||
color: var(--frost-color-text-tertiary);
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@ const store = useMqttStore();
|
||||
const msg = useMessage();
|
||||
const dialog = useDialog();
|
||||
|
||||
// localStorage 持久化 key
|
||||
const storageKey = 'mqtt-client.quick-send-list';
|
||||
// 旧版 localStorage key,用于数据迁移
|
||||
const oldStorageKey = 'mqtt-client.quick-send-list';
|
||||
|
||||
/**
|
||||
* @typedef {Object} QuickSendItem
|
||||
* @property {string} id
|
||||
* @property {string} topic
|
||||
* @property {string} comment
|
||||
* @property {string} payload
|
||||
* @property {0|1|2} qos
|
||||
* @property {boolean} retain
|
||||
@@ -24,6 +25,9 @@ const storageKey = 'mqtt-client.quick-send-list';
|
||||
/** @type {import('vue').Ref<QuickSendItem[]>} */
|
||||
const quickList = ref([]);
|
||||
|
||||
// 列表是否已从本地文件加载完成
|
||||
const quickListLoaded = ref(false);
|
||||
|
||||
// 表单弹窗显隐
|
||||
const showModal = ref(false);
|
||||
const isEdit = ref(false);
|
||||
@@ -31,30 +35,38 @@ const isEdit = ref(false);
|
||||
// 表单数据
|
||||
const formId = ref('');
|
||||
const formTopic = ref('');
|
||||
const formComment = ref('');
|
||||
const formPayload = ref('');
|
||||
const formQos = ref(1);
|
||||
const formRetain = ref(false);
|
||||
|
||||
// 从 localStorage 恢复列表
|
||||
try {
|
||||
|
||||
let saved = JSON.parse(localStorage.getItem(storageKey) || '[]');
|
||||
|
||||
if (Array.isArray(saved)) {
|
||||
// 从旧版 localStorage 迁移数据
|
||||
function migrateFromLocalStorage() {
|
||||
try {
|
||||
let saved = JSON.parse(localStorage.getItem(oldStorageKey) || '[]');
|
||||
if (Array.isArray(saved) && saved.length > 0) {
|
||||
quickList.value = saved.filter((item) => {
|
||||
return item && typeof item.id === 'string' && typeof item.topic === 'string';
|
||||
});
|
||||
window.api.saveQuickSendList(JSON.parse(JSON.stringify(quickList.value)));
|
||||
}
|
||||
} catch {
|
||||
// 忽略旧数据解析错误
|
||||
} finally {
|
||||
localStorage.removeItem(oldStorageKey);
|
||||
}
|
||||
|
||||
} catch {
|
||||
localStorage.removeItem(storageKey);
|
||||
}
|
||||
|
||||
// 列表变化时持久化
|
||||
// 列表变化时持久化到本地文件
|
||||
watch(
|
||||
quickList,
|
||||
() => {
|
||||
localStorage.setItem(storageKey, JSON.stringify(quickList.value));
|
||||
if (!quickListLoaded.value) {
|
||||
return;
|
||||
}
|
||||
window.api.saveQuickSendList(JSON.parse(JSON.stringify(quickList.value))).catch((e) => {
|
||||
console.error('保存快速发送列表失败:', e);
|
||||
});
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
@@ -111,6 +123,7 @@ function handleAdd() {
|
||||
isEdit.value = false;
|
||||
formId.value = '';
|
||||
formTopic.value = '';
|
||||
formComment.value = '';
|
||||
formPayload.value = '';
|
||||
formQos.value = 1;
|
||||
formRetain.value = false;
|
||||
@@ -125,6 +138,7 @@ function handleEdit(item) {
|
||||
isEdit.value = true;
|
||||
formId.value = item.id;
|
||||
formTopic.value = item.topic;
|
||||
formComment.value = item.comment || '';
|
||||
formPayload.value = item.payload;
|
||||
formQos.value = item.qos;
|
||||
formRetain.value = item.retain;
|
||||
@@ -143,6 +157,7 @@ function handleSave() {
|
||||
return;
|
||||
}
|
||||
|
||||
let comment = formComment.value.trim();
|
||||
let payload = formPayload.value;
|
||||
let qos = formQos.value;
|
||||
let retain = formRetain.value;
|
||||
@@ -155,6 +170,7 @@ function handleSave() {
|
||||
quickList.value[idx] = {
|
||||
...quickList.value[idx],
|
||||
topic,
|
||||
comment,
|
||||
payload,
|
||||
qos,
|
||||
retain,
|
||||
@@ -166,6 +182,7 @@ function handleSave() {
|
||||
quickList.value.push({
|
||||
id: generateId(),
|
||||
topic,
|
||||
comment,
|
||||
payload,
|
||||
qos,
|
||||
retain,
|
||||
@@ -461,7 +478,23 @@ function updateColumnCount(width) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
try {
|
||||
let list = await window.api.getQuickSendList();
|
||||
if (Array.isArray(list)) {
|
||||
quickList.value = list.filter((item) => {
|
||||
return item && typeof item.id === 'string' && typeof item.topic === 'string';
|
||||
});
|
||||
}
|
||||
if (quickList.value.length === 0) {
|
||||
migrateFromLocalStorage();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载快速发送列表失败:', e);
|
||||
} finally {
|
||||
quickListLoaded.value = true;
|
||||
}
|
||||
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
updateColumnCount(entry.contentRect.width);
|
||||
@@ -582,6 +615,11 @@ onUnmounted(() => {
|
||||
>删除</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.comment"
|
||||
class="quick-comment"
|
||||
:title="item.comment"
|
||||
>{{ item.comment }}</div>
|
||||
<pre
|
||||
v-if="item.payload"
|
||||
class="quick-payload"
|
||||
@@ -616,6 +654,12 @@ onUnmounted(() => {
|
||||
placeholder="例如: sensor/device01/temperature"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="备注">
|
||||
<n-input
|
||||
v-model:value="formComment"
|
||||
placeholder="可选,用于标识该快速消息的用途"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-grid
|
||||
cols="2"
|
||||
x-gap="12"
|
||||
@@ -664,7 +708,7 @@ onUnmounted(() => {
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
.quick-send-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -672,19 +716,19 @@ onUnmounted(() => {
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.quick-send-view :deep(.n-card) {
|
||||
:deep(.n-card) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-send-view :deep(.n-card__content) {
|
||||
:deep(.n-card__content) {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-list {
|
||||
@@ -692,22 +736,22 @@ onUnmounted(() => {
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
padding-right: 4px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.quick-list.columns-1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
&.columns-1 {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.quick-list.columns-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
&.columns-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.quick-list.columns-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
&.columns-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.payload-field {
|
||||
@@ -715,29 +759,29 @@ onUnmounted(() => {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.payload-field :deep(.n-input) {
|
||||
:deep(.n-input) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.payload-field .n-button {
|
||||
.n-button {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-item {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--frost-shadow-black-8);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
background: var(--frost-color-bg-item);
|
||||
cursor: grab;
|
||||
transition: box-shadow 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.quick-item.dragging {
|
||||
&.dragging {
|
||||
opacity: 0.6;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 4px 12px var(--frost-shadow-black-12);
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-header {
|
||||
@@ -751,11 +795,11 @@ onUnmounted(() => {
|
||||
.quick-checkbox {
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.quick-checkbox :deep(.n-checkbox-box) {
|
||||
:deep(.n-checkbox-box) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-topic-wrap {
|
||||
@@ -772,14 +816,14 @@ onUnmounted(() => {
|
||||
font-weight: 700;
|
||||
width: 28px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.quick-dir.pub {
|
||||
color: #3b82f6;
|
||||
&.pub {
|
||||
color: var(--frost-info-color);
|
||||
}
|
||||
}
|
||||
|
||||
.quick-topic {
|
||||
color: #d25a00;
|
||||
color: var(--frost-color-topic);
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
@@ -788,24 +832,33 @@ onUnmounted(() => {
|
||||
cursor: pointer;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.quick-topic:hover {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-meta {
|
||||
color: #999;
|
||||
color: var(--frost-color-text-tertiary);
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.quick-comment {
|
||||
margin-top: 8px;
|
||||
color: var(--frost-color-text-secondary);
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.quick-payload {
|
||||
margin: 10px 0 0;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
background: var(--frost-shadow-black-4);
|
||||
color: #333;
|
||||
font: inherit;
|
||||
white-space: pre-wrap;
|
||||
@@ -817,23 +870,27 @@ onUnmounted(() => {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.quick-send-modal .n-form-item {
|
||||
.quick-send-modal {
|
||||
.n-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.quick-send-modal .n-form-item__label {
|
||||
&__label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-send-modal .n-form-item:last-child {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quick-send-modal .n-grid {
|
||||
.n-grid {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-send-dialog .n-dialog__icon {
|
||||
color: #0ea5a0;
|
||||
.quick-send-dialog {
|
||||
:deep(.n-dialog__icon) {
|
||||
color: var(--frost-primary-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -260,15 +260,6 @@ async function handleEditSave(t) {
|
||||
@keydown.enter="handleAddTopic"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item
|
||||
class="qos-field"
|
||||
label="QoS"
|
||||
>
|
||||
<n-select
|
||||
v-model:value="qos"
|
||||
:options="qosOptions"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item
|
||||
class="comment-field"
|
||||
label="备注"
|
||||
@@ -279,13 +270,20 @@ async function handleEditSave(t) {
|
||||
@keydown.enter="handleAddTopic"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item
|
||||
class="qos-field"
|
||||
label="QoS"
|
||||
>
|
||||
<n-select
|
||||
v-model:value="qos"
|
||||
:options="qosOptions"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-button
|
||||
class="topic-submit"
|
||||
type="primary"
|
||||
@click="handleAddTopic"
|
||||
>
|
||||
+ 添加订阅
|
||||
</n-button>
|
||||
>+ 添加订阅</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
@@ -305,9 +303,7 @@ async function handleEditSave(t) {
|
||||
<n-button
|
||||
size="small"
|
||||
@click="handleSubscribeAll"
|
||||
>
|
||||
全部订阅
|
||||
</n-button>
|
||||
>全部订阅</n-button>
|
||||
</template>
|
||||
<n-empty
|
||||
v-if="!store.activeServer.value.topics || store.activeServer.value.topics.length === 0"
|
||||
@@ -323,69 +319,23 @@ async function handleEditSave(t) {
|
||||
:key="t.id || t.topic"
|
||||
class="topic-item"
|
||||
>
|
||||
<template
|
||||
v-if="editingTopicId !== (t.id || t.topic)"
|
||||
#prefix
|
||||
>
|
||||
<template #prefix>
|
||||
<n-tag
|
||||
class="topic-qos-tag"
|
||||
:type="t.subscribed ? 'success' : 'default'"
|
||||
size="small"
|
||||
>
|
||||
QoS {{ t.qos }}
|
||||
</n-tag>
|
||||
>QoS {{ t.qos }}</n-tag>
|
||||
</template>
|
||||
<div class="topic-content">
|
||||
<div
|
||||
v-if="editingTopicId === (t.id || t.topic)"
|
||||
class="topic-edit-row"
|
||||
>
|
||||
<n-input
|
||||
v-model:value="editingTopic"
|
||||
size="small"
|
||||
placeholder="主题名称"
|
||||
@keydown.enter="handleEditSave(t)"
|
||||
/>
|
||||
<n-select
|
||||
v-model:value="editingQos"
|
||||
size="small"
|
||||
:options="qosOptions"
|
||||
style="width: 160px; flex-shrink: 0"
|
||||
/>
|
||||
<n-input
|
||||
v-model:value="editingComment"
|
||||
size="small"
|
||||
placeholder="备注"
|
||||
/>
|
||||
<n-space
|
||||
:size="4"
|
||||
:wrap="false"
|
||||
>
|
||||
<n-button
|
||||
size="tiny"
|
||||
type="primary"
|
||||
@click="handleEditSave(t)"
|
||||
>
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
@click="cancelEdit"
|
||||
>
|
||||
取消
|
||||
</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="topic-name"
|
||||
:class="{ 'is-hidden': editingTopicId === (t.id || t.topic) }"
|
||||
>
|
||||
<span
|
||||
class="topic-label"
|
||||
:title="t.topic"
|
||||
>{{ t.topic }}</span>
|
||||
<div
|
||||
v-if="t.comment"
|
||||
class="topic-comment"
|
||||
>
|
||||
<div class="topic-comment">
|
||||
<span class="topic-comment-prefix">备注</span>
|
||||
<span
|
||||
class="topic-comment-text"
|
||||
@@ -393,34 +343,72 @@ async function handleEditSave(t) {
|
||||
>{{ t.comment }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="topic-edit-row"
|
||||
:class="{ 'is-visible': editingTopicId === (t.id || t.topic) }"
|
||||
>
|
||||
<n-input
|
||||
v-model:value="editingTopic"
|
||||
class="topic-edit-topic"
|
||||
size="small"
|
||||
placeholder="主题名称"
|
||||
@keydown.enter="handleEditSave(t)"
|
||||
/>
|
||||
<n-input
|
||||
v-model:value="editingComment"
|
||||
class="topic-edit-comment"
|
||||
size="small"
|
||||
placeholder="备注"
|
||||
/>
|
||||
<n-select
|
||||
v-model:value="editingQos"
|
||||
class="topic-edit-qos"
|
||||
size="small"
|
||||
:options="qosOptions"
|
||||
/>
|
||||
<n-space
|
||||
class="topic-edit-actions"
|
||||
:size="4"
|
||||
:wrap="false"
|
||||
>
|
||||
<n-button
|
||||
size="tiny"
|
||||
type="primary"
|
||||
@click="handleEditSave(t)"
|
||||
>保存</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
@click="cancelEdit"
|
||||
>取消</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</div>
|
||||
<template #suffix>
|
||||
<n-space
|
||||
v-if="editingTopicId !== (t.id || t.topic)"
|
||||
class="topic-actions"
|
||||
:class="{ 'is-hidden': editingTopicId === (t.id || t.topic) }"
|
||||
:size="4"
|
||||
:wrap="false"
|
||||
>
|
||||
<n-button
|
||||
size="tiny"
|
||||
@click="startEdit(t)"
|
||||
>
|
||||
编辑
|
||||
</n-button>
|
||||
>编辑</n-button>
|
||||
<n-button
|
||||
class="topic-action-sub"
|
||||
size="tiny"
|
||||
:type="t.subscribed ? 'error' : 'success'"
|
||||
:type="t.subscribed ? 'success' : 'primary'"
|
||||
@click="handleToggleSub(t.topic)"
|
||||
>
|
||||
{{ t.subscribed ? '取消订阅' : '订阅' }}
|
||||
{{ t.subscribed ? '已订阅' : '未订阅' }}
|
||||
</n-button>
|
||||
<n-button
|
||||
class="topic-action-remove"
|
||||
size="tiny"
|
||||
type="error"
|
||||
text
|
||||
@click.stop="handleRemoveTopic(t.topic)"
|
||||
>
|
||||
✕
|
||||
</n-button>
|
||||
>✕</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-list-item>
|
||||
@@ -438,7 +426,7 @@ async function handleEditSave(t) {
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
.subscribe-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -491,33 +479,66 @@ async function handleEditSave(t) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.topic-field :deep(.n-form-item-blank),
|
||||
.qos-field :deep(.n-form-item-blank),
|
||||
.comment-field :deep(.n-form-item-blank) {
|
||||
.topic-field,
|
||||
.qos-field,
|
||||
.comment-field {
|
||||
:deep(.n-form-item-blank) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-item :deep(.n-list-item__main) {
|
||||
.topic-item {
|
||||
box-sizing: border-box;
|
||||
height: 56px;
|
||||
min-height: 56px;
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
|
||||
:deep(.n-list-item__main) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.topic-name {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
min-width: 0;
|
||||
gap: 5px;
|
||||
min-height: 0;
|
||||
gap: 2px;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
visibility: visible;
|
||||
|
||||
&.is-hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
font-family: monospace;
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -527,45 +548,90 @@ async function handleEditSave(t) {
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding: 2px 6px;
|
||||
gap: 5px;
|
||||
padding: 1px 4px;
|
||||
gap: 4px;
|
||||
border: 1px solid rgba(14, 165, 160, 0.16);
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 184, 166, 0.06);
|
||||
color: var(--n-text-color-3);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
|
||||
.topic-comment-prefix {
|
||||
&-prefix {
|
||||
flex-shrink: 0;
|
||||
color: #0f766e;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-comment-text {
|
||||
&-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-qos-tag {
|
||||
min-width: 56px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.topic-actions {
|
||||
flex-shrink: 0;
|
||||
visibility: visible;
|
||||
|
||||
&.is-hidden {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.n-button {
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.topic-action-sub {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.topic-action-remove {
|
||||
min-width: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-edit-row {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
&.is-visible {
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-edit-topic {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.topic-edit-row .n-input {
|
||||
flex: 1;
|
||||
min-width: 180px;
|
||||
.topic-edit-comment {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.topic-edit-qos {
|
||||
flex: 0 0 auto;
|
||||
width: 160px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# CHANGELOG
|
||||
|
||||
## [1.0.1] - 2026-07-21
|
||||
|
||||
### Added
|
||||
|
||||
- 快速发送列表持久化到本地文件 `frost-mqtt-data.json`。
|
||||
- 快速消息支持备注。
|
||||
|
||||
### Fixed
|
||||
|
||||
- 解决“快速发送”模块中,主题名称较长时,外层卡片缩小到一定程度后不能再随外层容器缩小而缩小的问题。
|
||||
|
||||
## [1.0.0] - 2026-07-21
|
||||
|
||||
- 初始版本发布。
|
||||
@@ -0,0 +1,4 @@
|
||||
# TODO
|
||||
|
||||
- [X] 过滤主题功能支持下拉
|
||||
- [X] 订阅主题备注
|
||||
+5
-2
@@ -34,7 +34,11 @@
|
||||
- 事件处理函数以 `handle` 开头,例如 `handleSave`;模板事件名采用 kebab-case,例如 `@edit-server`。
|
||||
- Naive UI 组件使用 `NButton` 等 PascalCase 导入,在模板中写为 `n-button`。
|
||||
- 模板属性一行较长时换行,每个属性单独一行,并保持 2 空格缩进。
|
||||
- 样式采用普通 CSS;类名使用 kebab-case;布局和组件私有样式就近放在对应组件中。
|
||||
- 元素标签内文本简短时,将文本与结束标签放在同一行;较长表达式或动态内容可单独成行。
|
||||
- 样式使用 Less;Vue SFC 中通过 `<style lang="less">` / `<style lang="less" scoped>` 声明。
|
||||
- 类名使用 kebab-case;布局和组件私有样式就近放在对应组件中。
|
||||
- 善用嵌套规则、`&` 父选择器减少重复;全局主题色等公共变量在 App.vue 中通过 CSS 自定义属性定义,命名与 `themeOverrides` 对应(例如 `--frost-primary-color`)。
|
||||
- 组件内直接使用 `var(--xxx)` 引用全局变量,不再单独定义 Less 变量。
|
||||
|
||||
## Electron 分层
|
||||
|
||||
@@ -45,5 +49,4 @@
|
||||
## ESLint
|
||||
|
||||
- 使用 `@electron-toolkit/eslint-config` 与 Vue 推荐规则作为基础。
|
||||
- Vue 组件允许单词名称;未强制默认 Prop;模板单行或多行内容换行规则已关闭。
|
||||
- 提交前执行 `pnpm lint`,并修复实际报告的问题。
|
||||
|
||||
Reference in New Issue
Block a user