260128
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
5
README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Vue 3 + TypeScript + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||||
388
bun.lock
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "motor-ui",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/qrcode": "^1.5.6",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
|
"lucide-vue-next": "^0.562.0",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"vue": "^3.5.24",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"@vue/tsconfig": "^0.8.1",
|
||||||
|
"sass-embedded": "^1.97.2",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"vite": "^7.2.4",
|
||||||
|
"vue-tsc": "^3.1.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||||
|
|
||||||
|
"@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
|
||||||
|
|
||||||
|
"@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
|
||||||
|
|
||||||
|
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.10.2", "", {}, "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A=="],
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="],
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||||
|
|
||||||
|
"@parcel/watcher": ["@parcel/watcher@2.5.4", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.4", "@parcel/watcher-darwin-arm64": "2.5.4", "@parcel/watcher-darwin-x64": "2.5.4", "@parcel/watcher-freebsd-x64": "2.5.4", "@parcel/watcher-linux-arm-glibc": "2.5.4", "@parcel/watcher-linux-arm-musl": "2.5.4", "@parcel/watcher-linux-arm64-glibc": "2.5.4", "@parcel/watcher-linux-arm64-musl": "2.5.4", "@parcel/watcher-linux-x64-glibc": "2.5.4", "@parcel/watcher-linux-x64-musl": "2.5.4", "@parcel/watcher-win32-arm64": "2.5.4", "@parcel/watcher-win32-ia32": "2.5.4", "@parcel/watcher-win32-x64": "2.5.4" } }, "sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.4", "", { "os": "android", "cpu": "arm64" }, "sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.4", "", { "os": "linux", "cpu": "arm" }, "sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg=="],
|
||||||
|
|
||||||
|
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw=="],
|
||||||
|
|
||||||
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.2", "", { "os": "android", "cpu": "arm" }, "sha512-21J6xzayjy3O6NdnlO6aXi/urvSRjm6nCI6+nF6ra2YofKruGixN9kfT+dt55HVNwfDmpDHJcaS3JuP/boNnlA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.2", "", { "os": "android", "cpu": "arm64" }, "sha512-eXBg7ibkNUZ+sTwbFiDKou0BAckeV6kIigK7y5Ko4mB/5A1KLhuzEKovsmfvsL8mQorkoincMFGnQuIT92SKqA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UCbaTklREjrc5U47ypLulAgg4njaqfOVLU18VrCrI+6E5MQjuG0lSWaqLlAJwsD7NpFV249XgB0Bi37Zh5Sz4g=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-dP67MA0cCMHFT2g5XyjtpVOtp7y4UyUxN3dhLdt11at5cPKnSm4lY+EhwNvDXIMzAMIo2KU+mc9wxaAQJTn7sQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-WDUPLUwfYV9G1yxNRJdXcvISW15mpvod1Wv3ok+Ws93w1HjIVmCIFxsG2DquO+3usMNCpJQ0wqO+3GhFdl6Fow=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Ng95wtHVEulRwn7R0tMrlUuiLVL/HXA8Lt/MYVpy88+s5ikpntzZba1qEulTuPnPIZuOPcW9wNEiqvZxZmgmqQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.2", "", { "os": "linux", "cpu": "arm" }, "sha512-AEXMESUDWWGqD6LwO/HkqCZgUE1VCJ1OhbvYGsfqX2Y6w5quSXuyoy/Fg3nRqiwro+cJYFxiw5v4kB2ZDLhxrw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.2", "", { "os": "linux", "cpu": "arm" }, "sha512-ZV7EljjBDwBBBSv570VWj0hiNTdHt9uGznDtznBB4Caj3ch5rgD4I2K1GQrtbvJ/QiB+663lLgOdcADMNVC29Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-uvjwc8NtQVPAJtq4Tt7Q49FOodjfbf6NpqXyW/rjXoV+iZ3EJAHLNAnKT5UJBc6ffQVgmXTUL2ifYiLABlGFqA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-s3KoWVNnye9mm/2WpOZ3JeUiediUVw6AvY/H7jNA6qgKA2V2aM25lMkVarTDfiicn/DLq3O0a81jncXszoyCFA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-gi21faacK+J8aVSyAUptML9VQN26JRxe484IbF+h3hpG+sNVoMXPduhREz2CcYr5my0NE3MjVvQ5bMKX71pfVA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-qSlWiXnVaS/ceqXNfnoFZh4IiCA0EwvCivivTGbEu1qv2o+WTHpn1zNmCTAoOG5QaVr2/yhCoLScQtc/7RxshA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-rPyuLFNoF1B0+wolH277E780NUKf+KoEDb3OyoLbAO18BbeKi++YN6gC/zuJoPPDlQRL3fIxHxCxVEWiem2yXw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-g+0ZLMook31iWV4PvqKU0i9E78gaZgYpSrYPed/4Bu+nGTgfOPtfs1h11tSSRPXSjC5EzLTjV/1A7L2Vr8pJoQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-i+sGeRGsjKZcQRh3BRfpLsM3LX3bi4AoEVqmGDyc50L6KfYsN45wVCSz70iQMwPWr3E5opSiLOwsC9WB4/1pqg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-C1vLcKc4MfFV6I0aWsC7B2Y9QcsiEcvKkfxprwkPfLaN8hQf0/fKHwSF2lcYzA9g4imqnhic729VB9Fo70HO3Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-68gHUK/howpQjh7g7hlD9DvTTt4sNLp1Bb+Yzw2Ki0xvscm2cOdCLZNJNhd2jW8lsTPrHAHuF751BygifW4bkQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.2", "", { "os": "linux", "cpu": "x64" }, "sha512-1e30XAuaBP1MAizaOBApsgeGZge2/Byd6wV4a8oa6jPdHELbRHBiw7wvo4dp7Ie2PE8TZT4pj9RLGZv9N4qwlw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4BJucJBGbuGnH6q7kpPqGJGzZnYrpAzRd60HQSt3OpX/6/YVgSsJnNzR8Ot74io50SeVT4CtCWe/RYIAymFPwA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cT2MmXySMo58ENv8p6/O6wI/h/gLnD3D6JoajwXFZH6X9jz4hARqUhWpGuQhOgLNXscfZYRQMJvZDtWNzMAIDw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sZnyUgGkuzIXaK3jNMPmUIyJrxu/PjmATQrocpGA1WbCPX8H5tfGgRSuYtqBYAvLuIGp8SPRb1O4d1Fkb5fXaQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-sDpFbenhmWjNcEbBcoTV0PWvW5rPJFvu+P7XoTY0YLGRupgLbFY0XPfwIbJOObzO7QgkRDANh65RjhPmgSaAjQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-GvJ03TqqaweWCigtKQVBErw2bEhu1tyfNQbarwr94wCGnczA9HF8wqEe3U/Lfu6EdeNP0p6R+APeHVwEqVxpUQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.2", "", { "os": "win32", "cpu": "x64" }, "sha512-KvXsBvp13oZz9JGe5NYS7FNizLe99Ny+W8ETsuCyjXiKdiGrcz2/J/N8qxZ/RSwivqjQguug07NLHqrIHrqfYw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.2", "", { "os": "win32", "cpu": "x64" }, "sha512-xNO+fksQhsAckRtDSPWaMeT1uIM+JrDRXlerpnWNXhn1TdB3YZ6uKBMBTKP0eX9XtYEP978hHk1f8332i2AW8Q=="],
|
||||||
|
|
||||||
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@24.10.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw=="],
|
||||||
|
|
||||||
|
"@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="],
|
||||||
|
|
||||||
|
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.53" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w=="],
|
||||||
|
|
||||||
|
"@volar/language-core": ["@volar/language-core@2.4.27", "", { "dependencies": { "@volar/source-map": "2.4.27" } }, "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ=="],
|
||||||
|
|
||||||
|
"@volar/source-map": ["@volar/source-map@2.4.27", "", {}, "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg=="],
|
||||||
|
|
||||||
|
"@volar/typescript": ["@volar/typescript@2.4.27", "", { "dependencies": { "@volar/language-core": "2.4.27", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg=="],
|
||||||
|
|
||||||
|
"@vue/compiler-core": ["@vue/compiler-core@3.5.27", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.27", "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ=="],
|
||||||
|
|
||||||
|
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.27", "", { "dependencies": { "@vue/compiler-core": "3.5.27", "@vue/shared": "3.5.27" } }, "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w=="],
|
||||||
|
|
||||||
|
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.27", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.27", "@vue/compiler-dom": "3.5.27", "@vue/compiler-ssr": "3.5.27", "@vue/shared": "3.5.27", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ=="],
|
||||||
|
|
||||||
|
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.27", "", { "dependencies": { "@vue/compiler-dom": "3.5.27", "@vue/shared": "3.5.27" } }, "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw=="],
|
||||||
|
|
||||||
|
"@vue/language-core": ["@vue/language-core@3.2.2", "", { "dependencies": { "@volar/language-core": "2.4.27", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" } }, "sha512-5DAuhxsxBN9kbriklh3Q5AMaJhyOCNiQJvCskN9/30XOpdLiqZU9Q+WvjArP17ubdGEyZtBzlIeG5nIjEbNOrQ=="],
|
||||||
|
|
||||||
|
"@vue/reactivity": ["@vue/reactivity@3.5.27", "", { "dependencies": { "@vue/shared": "3.5.27" } }, "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ=="],
|
||||||
|
|
||||||
|
"@vue/runtime-core": ["@vue/runtime-core@3.5.27", "", { "dependencies": { "@vue/reactivity": "3.5.27", "@vue/shared": "3.5.27" } }, "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A=="],
|
||||||
|
|
||||||
|
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.27", "", { "dependencies": { "@vue/reactivity": "3.5.27", "@vue/runtime-core": "3.5.27", "@vue/shared": "3.5.27", "csstype": "^3.2.3" } }, "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg=="],
|
||||||
|
|
||||||
|
"@vue/server-renderer": ["@vue/server-renderer@3.5.27", "", { "dependencies": { "@vue/compiler-ssr": "3.5.27", "@vue/shared": "3.5.27" }, "peerDependencies": { "vue": "3.5.27" } }, "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA=="],
|
||||||
|
|
||||||
|
"@vue/shared": ["@vue/shared@3.5.27", "", {}, "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ=="],
|
||||||
|
|
||||||
|
"@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="],
|
||||||
|
|
||||||
|
"alien-signals": ["alien-signals@3.1.2", "", {}, "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
|
"buffer-builder": ["buffer-builder@0.2.0", "", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="],
|
||||||
|
|
||||||
|
"camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||||
|
|
||||||
|
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||||
|
|
||||||
|
"cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
|
||||||
|
|
||||||
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
|
|
||||||
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
|
"colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="],
|
||||||
|
|
||||||
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
|
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
||||||
|
|
||||||
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
|
"dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="],
|
||||||
|
|
||||||
|
"echarts": ["echarts@6.0.0", "", { "dependencies": { "tslib": "2.3.0", "zrender": "6.0.0" } }, "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ=="],
|
||||||
|
|
||||||
|
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
|
"entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="],
|
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
|
||||||
|
|
||||||
|
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
|
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
|
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
|
|
||||||
|
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||||
|
|
||||||
|
"immutable": ["immutable@5.1.4", "", {}, "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="],
|
||||||
|
|
||||||
|
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||||
|
|
||||||
|
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
|
|
||||||
|
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||||
|
|
||||||
|
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||||
|
|
||||||
|
"lucide-vue-next": ["lucide-vue-next@0.562.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-LN0BLGKMFulv0lnfK29r14DcngRUhIqdcaL0zXTt2o0oS9odlrjCGaU3/X9hIihOjjN8l8e+Y9G/famcNYaI7Q=="],
|
||||||
|
|
||||||
|
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
|
||||||
|
|
||||||
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
|
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||||
|
|
||||||
|
"p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||||
|
|
||||||
|
"p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||||
|
|
||||||
|
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
|
||||||
|
|
||||||
|
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||||
|
|
||||||
|
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||||
|
|
||||||
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
|
|
||||||
|
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
||||||
|
|
||||||
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
|
"qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
|
||||||
|
|
||||||
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
|
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||||
|
|
||||||
|
"require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
|
||||||
|
|
||||||
|
"rollup": ["rollup@4.55.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.2", "@rollup/rollup-android-arm64": "4.55.2", "@rollup/rollup-darwin-arm64": "4.55.2", "@rollup/rollup-darwin-x64": "4.55.2", "@rollup/rollup-freebsd-arm64": "4.55.2", "@rollup/rollup-freebsd-x64": "4.55.2", "@rollup/rollup-linux-arm-gnueabihf": "4.55.2", "@rollup/rollup-linux-arm-musleabihf": "4.55.2", "@rollup/rollup-linux-arm64-gnu": "4.55.2", "@rollup/rollup-linux-arm64-musl": "4.55.2", "@rollup/rollup-linux-loong64-gnu": "4.55.2", "@rollup/rollup-linux-loong64-musl": "4.55.2", "@rollup/rollup-linux-ppc64-gnu": "4.55.2", "@rollup/rollup-linux-ppc64-musl": "4.55.2", "@rollup/rollup-linux-riscv64-gnu": "4.55.2", "@rollup/rollup-linux-riscv64-musl": "4.55.2", "@rollup/rollup-linux-s390x-gnu": "4.55.2", "@rollup/rollup-linux-x64-gnu": "4.55.2", "@rollup/rollup-linux-x64-musl": "4.55.2", "@rollup/rollup-openbsd-x64": "4.55.2", "@rollup/rollup-openharmony-arm64": "4.55.2", "@rollup/rollup-win32-arm64-msvc": "4.55.2", "@rollup/rollup-win32-ia32-msvc": "4.55.2", "@rollup/rollup-win32-x64-gnu": "4.55.2", "@rollup/rollup-win32-x64-msvc": "4.55.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-PggGy4dhwx5qaW+CKBilA/98Ql9keyfnb7lh4SR6shQ91QQQi1ORJ1v4UinkdP2i87OBs9AQFooQylcrrRfIcg=="],
|
||||||
|
|
||||||
|
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
|
||||||
|
|
||||||
|
"sass": ["sass@1.97.2", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw=="],
|
||||||
|
|
||||||
|
"sass-embedded": ["sass-embedded@1.97.2", "", { "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-all-unknown": "1.97.2", "sass-embedded-android-arm": "1.97.2", "sass-embedded-android-arm64": "1.97.2", "sass-embedded-android-riscv64": "1.97.2", "sass-embedded-android-x64": "1.97.2", "sass-embedded-darwin-arm64": "1.97.2", "sass-embedded-darwin-x64": "1.97.2", "sass-embedded-linux-arm": "1.97.2", "sass-embedded-linux-arm64": "1.97.2", "sass-embedded-linux-musl-arm": "1.97.2", "sass-embedded-linux-musl-arm64": "1.97.2", "sass-embedded-linux-musl-riscv64": "1.97.2", "sass-embedded-linux-musl-x64": "1.97.2", "sass-embedded-linux-riscv64": "1.97.2", "sass-embedded-linux-x64": "1.97.2", "sass-embedded-unknown-all": "1.97.2", "sass-embedded-win32-arm64": "1.97.2", "sass-embedded-win32-x64": "1.97.2" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-lKJcskySwAtJ4QRirKrikrWMFa2niAuaGenY2ElHjd55IwHUiur5IdKu6R1hEmGYMs4Qm+6rlRW0RvuAkmcryg=="],
|
||||||
|
|
||||||
|
"sass-embedded-all-unknown": ["sass-embedded-all-unknown@1.97.2", "", { "dependencies": { "sass": "1.97.2" }, "cpu": [ "!arm", "!x64", "!arm64", ] }, "sha512-Fj75+vOIDv1T/dGDwEpQ5hgjXxa2SmMeShPa8yrh2sUz1U44bbmY4YSWPCdg8wb7LnwiY21B2KRFM+HF42yO4g=="],
|
||||||
|
|
||||||
|
"sass-embedded-android-arm": ["sass-embedded-android-arm@1.97.2", "", { "os": "android", "cpu": "arm" }, "sha512-BPT9m19ttY0QVHYYXRa6bmqmS3Fa2EHByNUEtSVcbm5PkIk1ntmYkG9fn5SJpIMbNmFDGwHx+pfcZMmkldhnRg=="],
|
||||||
|
|
||||||
|
"sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.97.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pF6I+R5uThrscd3lo9B3DyNTPyGFsopycdx0tDAESN6s+dBbiRgNgE4Zlpv50GsLocj/lDLCZaabeTpL3ubhYA=="],
|
||||||
|
|
||||||
|
"sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.97.2", "", { "os": "android", "cpu": "none" }, "sha512-fprI8ZTJdz+STgARhg8zReI2QhhGIT9G8nS7H21kc3IkqPRzhfaemSxEtCqZyvDbXPcgYiDLV7AGIReHCuATog=="],
|
||||||
|
|
||||||
|
"sass-embedded-android-x64": ["sass-embedded-android-x64@1.97.2", "", { "os": "android", "cpu": "x64" }, "sha512-RswwSjURZxupsukEmNt2t6RGvuvIw3IAD5sDq1Pc65JFvWFY3eHqCmH0lG0oXqMg6KJcF0eOxHOp2RfmIm2+4w=="],
|
||||||
|
|
||||||
|
"sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.97.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xcsZNnU1XZh21RE/71OOwNqPVcGBU0qT9A4k4QirdA34+ts9cDIaR6W6lgHOBR/Bnnu6w6hXJR4Xth7oFrefPA=="],
|
||||||
|
|
||||||
|
"sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.97.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-T/9DTMpychm6+H4slHCAsYJRJ6eM+9H9idKlBPliPrP4T8JdC2Cs+ZOsYqrObj6eOtAD0fGf+KgyNhnW3xVafA=="],
|
||||||
|
|
||||||
|
"sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.97.2", "", { "os": "linux", "cpu": "arm" }, "sha512-yDRe1yifGHl6kibkDlRIJ2ZzAU03KJ1AIvsAh4dsIDgK5jx83bxZLV1ZDUv7a8KK/iV/80LZnxnu/92zp99cXQ=="],
|
||||||
|
|
||||||
|
"sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.97.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Wh+nQaFer9tyE5xBPv5murSUZE/+kIcg8MyL5uqww6be9Iq+UmZpcJM7LUk+q8klQ9LfTmoDSNFA74uBqxD6IA=="],
|
||||||
|
|
||||||
|
"sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.97.2", "", { "os": "linux", "cpu": "arm" }, "sha512-GIO6xfAtahJAWItvsXZ3MD1HM6s8cKtV1/HL088aUpKJaw/2XjTCveiOO2AdgMpLNztmq9DZ1lx5X5JjqhS45g=="],
|
||||||
|
|
||||||
|
"sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.97.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-NfUqZSjHwnHvpSa7nyNxbWfL5obDjNBqhHUYmqbHUcmqBpFfHIQsUPgXME9DKn1yBlBc3mWnzMxRoucdYTzd2Q=="],
|
||||||
|
|
||||||
|
"sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.97.2", "", { "os": "linux", "cpu": "none" }, "sha512-qtM4dJ5gLfvyTZ3QencfNbsTEShIWImSEpkThz+Y2nsCMbcMP7/jYOA03UWgPfEOKSehQQ7EIau7ncbFNoDNPQ=="],
|
||||||
|
|
||||||
|
"sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.97.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ZAxYOdmexcnxGnzdsDjYmNe3jGj+XW3/pF/n7e7r8y+5c6D2CQRrCUdapLgaqPt1edOPQIlQEZF8q5j6ng21yw=="],
|
||||||
|
|
||||||
|
"sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.97.2", "", { "os": "linux", "cpu": "none" }, "sha512-reVwa9ZFEAOChXpDyNB3nNHHyAkPMD+FTctQKECqKiVJnIzv2EaFF6/t0wzyvPgBKeatA8jszAIeOkkOzbYVkQ=="],
|
||||||
|
|
||||||
|
"sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.97.2", "", { "os": "linux", "cpu": "x64" }, "sha512-bvAdZQsX3jDBv6m4emaU2OMTpN0KndzTAMgJZZrKUgiC0qxBmBqbJG06Oj/lOCoXGCxAvUOheVYpezRTF+Feog=="],
|
||||||
|
|
||||||
|
"sass-embedded-unknown-all": ["sass-embedded-unknown-all@1.97.2", "", { "dependencies": { "sass": "1.97.2" }, "os": [ "!linux", "!win32", "!darwin", "!android", ] }, "sha512-86tcYwohjPgSZtgeU9K4LikrKBJNf8ZW/vfsFbdzsRlvc73IykiqanufwQi5qIul0YHuu9lZtDWyWxM2dH/Rsg=="],
|
||||||
|
|
||||||
|
"sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.97.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Cv28q8qNjAjZfqfzTrQvKf4JjsZ6EOQ5FxyHUQQeNzm73R86nd/8ozDa1Vmn79Hq0kwM15OCM9epanDuTG1ksA=="],
|
||||||
|
|
||||||
|
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.97.2", "", { "os": "win32", "cpu": "x64" }, "sha512-DVxLxkeDCGIYeyHLAvWW3yy9sy5Ruk5p472QWiyfyyG1G1ASAR8fgfIY5pT0vE6Rv+VAKVLwF3WTspUYu7S1/Q=="],
|
||||||
|
|
||||||
|
"set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
|
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
|
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||||
|
|
||||||
|
"sync-child-process": ["sync-child-process@1.0.2", "", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="],
|
||||||
|
|
||||||
|
"sync-message-port": ["sync-message-port@1.1.3", "", {}, "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg=="],
|
||||||
|
|
||||||
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
|
"tslib": ["tslib@2.3.0", "", {}, "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
|
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
|
||||||
|
|
||||||
|
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
||||||
|
|
||||||
|
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
||||||
|
|
||||||
|
"vue": ["vue@3.5.27", "", { "dependencies": { "@vue/compiler-dom": "3.5.27", "@vue/compiler-sfc": "3.5.27", "@vue/runtime-dom": "3.5.27", "@vue/server-renderer": "3.5.27", "@vue/shared": "3.5.27" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw=="],
|
||||||
|
|
||||||
|
"vue-tsc": ["vue-tsc@3.2.2", "", { "dependencies": { "@volar/typescript": "2.4.27", "@vue/language-core": "3.2.2" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-r9YSia/VgGwmbbfC06hDdAatH634XJ9nVl6Zrnz1iK4ucp8Wu78kawplXnIDa3MSu1XdQQePTHLXYwPDWn+nyQ=="],
|
||||||
|
|
||||||
|
"which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
|
||||||
|
|
||||||
|
"wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
|
||||||
|
|
||||||
|
"y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
|
||||||
|
|
||||||
|
"yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
|
||||||
|
|
||||||
|
"yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
|
||||||
|
|
||||||
|
"zrender": ["zrender@6.0.0", "", { "dependencies": { "tslib": "2.3.0" } }, "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
13
index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>motor-ui</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
package.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "motor-ui",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc -b && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/qrcode": "^1.5.6",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
|
"lucide-vue-next": "^0.562.0",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"vue": "^3.5.24"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"@vue/tsconfig": "^0.8.1",
|
||||||
|
"sass-embedded": "^1.97.2",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"vite": "^7.2.4",
|
||||||
|
"vue-tsc": "^3.1.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/vite.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
480
src/App.vue
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, type Ref, watch } from 'vue';
|
||||||
|
import * as api from './api.ts'
|
||||||
|
import Button from './components/Button.vue';
|
||||||
|
import Machine from './components/Machine.vue';
|
||||||
|
import QRCode from 'qrcode';
|
||||||
|
import Panel from './components/Panel.vue';
|
||||||
|
import Window from './components/Window.vue';
|
||||||
|
import Oscilloscope from './components/Oscilloscope.vue';
|
||||||
|
|
||||||
|
const state = reactive({ ...api.getState() });
|
||||||
|
|
||||||
|
api.onStateChange((newState) => {
|
||||||
|
Object.assign(state, newState);
|
||||||
|
});
|
||||||
|
|
||||||
|
const MachineId = '001';
|
||||||
|
|
||||||
|
const qrcodeSrc = ref('');
|
||||||
|
QRCode.toDataURL(`http://svme.xn--876a.net/login/${MachineId}`, {
|
||||||
|
}).then(url => {
|
||||||
|
qrcodeSrc.value = url;
|
||||||
|
});
|
||||||
|
|
||||||
|
const showLogin = ref(false);
|
||||||
|
const showManualAdjust = ref(false);
|
||||||
|
const targetDis = ref('');
|
||||||
|
|
||||||
|
import { Delete, RefreshCcw, Settings, ChevronRight } from 'lucide-vue-next';
|
||||||
|
import { showMessage } from './utils/message';
|
||||||
|
import Numpad from './components/Numpad.vue';
|
||||||
|
import Control from './pages/Control.vue';
|
||||||
|
import History from './pages/History.vue';
|
||||||
|
import TrendChart from './components/TrendChart.vue';
|
||||||
|
|
||||||
|
const showSettings = ref(false);
|
||||||
|
const showSpeedSettings = ref(false);
|
||||||
|
const targetSpeed = ref('');
|
||||||
|
|
||||||
|
const historyData = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 监听新的测量数据
|
||||||
|
watch(() => state.last_measurement, (newVal, oldVal) => {
|
||||||
|
if (newVal && newVal.ts && (!oldVal || newVal.ts !== oldVal.ts)) {
|
||||||
|
// 将当前位置注入到历史记录中
|
||||||
|
historyData.value.push({
|
||||||
|
...newVal,
|
||||||
|
currentDis: state.dis
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function clearHistory() {
|
||||||
|
historyData.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function checkAndRun(callback: (val: number) => void) {
|
||||||
|
const value = targetDis.value;
|
||||||
|
if (!value) {
|
||||||
|
showMessage('请输入目标距离', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const num = parseFloat(value);
|
||||||
|
if (isNaN(num)) {
|
||||||
|
showMessage('请输入有效的数字格式', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num < 0 || num > 300) {
|
||||||
|
showMessage('目标距离超出范围 (0-300mm)\n请重新输入', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkSpeedAndSet() {
|
||||||
|
const value = targetSpeed.value;
|
||||||
|
if (!value) {
|
||||||
|
showMessage('请输入目标速度', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const num = parseFloat(value);
|
||||||
|
if (isNaN(num)) {
|
||||||
|
showMessage('请输入有效的数字格式', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 范围检查: 60 - 2000 um/s (对应约 96 - 3200 Hz)
|
||||||
|
if (num < 60 || num > 2000) {
|
||||||
|
showMessage('速度超出范围 (60-2000 μm/s)\n请重新输入', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换 um/s -> Hz (1600 Hz = 1000 um/s => ratio = 1.6)
|
||||||
|
const hz = Math.round(num * 1.6);
|
||||||
|
|
||||||
|
api.setSpeed(hz);
|
||||||
|
showSpeedSettings.value = false;
|
||||||
|
showMessage('速度设置成功', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSettings() {
|
||||||
|
showSettings.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSpeedSettings() {
|
||||||
|
// 转换 Hz -> um/s
|
||||||
|
const currentUmPs = state.speed ? Math.round(state.speed / 1.6) : 0;
|
||||||
|
targetSpeed.value = currentUmPs.toString();
|
||||||
|
showSpeedSettings.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function locationReload() {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'home' | 'measure' | 'control' | 'history'
|
||||||
|
const pageIndex: Ref<0 | 1 | 2 | 3> = ref(0);
|
||||||
|
|
||||||
|
const mountedPages = ref([true, false, false, false]);
|
||||||
|
let cleanupTimer: any = null;
|
||||||
|
|
||||||
|
watch(pageIndex, (newVal, oldVal) => {
|
||||||
|
// Ensure target page is mounted
|
||||||
|
mountedPages.value[newVal] = true;
|
||||||
|
|
||||||
|
// Ensure old page stays mounted during transition
|
||||||
|
if (oldVal !== undefined) {
|
||||||
|
mountedPages.value[oldVal] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanupTimer) clearTimeout(cleanupTimer);
|
||||||
|
cleanupTimer = setTimeout(() => {
|
||||||
|
// Keep only the current page mounted to save resources
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
mountedPages.value[i] = (i === pageIndex.value);
|
||||||
|
}
|
||||||
|
}, 400); // 0.3s transition + buffer
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<div class="page-label">
|
||||||
|
<Button :class="{ current: pageIndex === 0 }" @click="pageIndex = 0">首页</Button>
|
||||||
|
<Button :class="{ current: pageIndex === 1 }" @click="pageIndex = 1">测量</Button>
|
||||||
|
<Button :class="{ current: pageIndex === 2 }" @click="pageIndex = 2">控制</Button>
|
||||||
|
<Button :class="{ current: pageIndex === 3 }" @click="pageIndex = 3">历史</Button>
|
||||||
|
</div>
|
||||||
|
<div class="connection-state" :class="{ connected: state.connected, disconnected: !state.connected }"></div>
|
||||||
|
<span>自动声速测定仪</span>
|
||||||
|
<div class="actions">
|
||||||
|
<Button class="reload" @click="locationReload">
|
||||||
|
<RefreshCcw />
|
||||||
|
</Button>
|
||||||
|
<Button class="settings" @click="openSettings">
|
||||||
|
<Settings />
|
||||||
|
</Button>
|
||||||
|
<Button class="login" @click="showLogin = true">登录</Button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="page page-home" :style="{ transform: `translateX(${(-pageIndex - 0) * 100}%)` }">
|
||||||
|
<template v-if="mountedPages[0]">
|
||||||
|
<div class="left">
|
||||||
|
<Panel style="flex: 1;">
|
||||||
|
<TrendChart :data="historyData" />
|
||||||
|
</Panel>
|
||||||
|
<Panel>
|
||||||
|
<Machine :dis="state.dis"
|
||||||
|
:task="state.tasks.reduce((acc, task) => (task.type == 'move' ? ({ remaining_steps: acc.remaining_steps + task.remaining_steps }) : acc), { remaining_steps: 0 })" />
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<div class="label">相位差</div>
|
||||||
|
<div class="data">{{ state.phase.toFixed(4) }} rad</div>
|
||||||
|
<div class="label">频率</div>
|
||||||
|
<div class="data">{{ state.freq }} KHz</div>
|
||||||
|
<div class="label">峰峰值</div>
|
||||||
|
<div class="data">{{ state.p2p }} ADC</div>
|
||||||
|
<div class="action">
|
||||||
|
<div class="left">
|
||||||
|
<div class="line">
|
||||||
|
<Button class="man-adj" @long-press="api.move(-40)">◄</Button>
|
||||||
|
<span>手动<br>调整</span>
|
||||||
|
|
||||||
|
<Button class="man-adj" @long-press="api.move(40)">►</Button>
|
||||||
|
</div>
|
||||||
|
<Button class="input-dis" @click="showManualAdjust = true">
|
||||||
|
输入
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<Button class="action-btn" @click="pageIndex = 2" bg="limegreen">开始实验</Button>
|
||||||
|
<!-- <Button class="action-btn" @click="api.measure()" bg="limegreen">单次测量</Button> -->
|
||||||
|
<Button class="action-btn" @click="api.stopAll()" bg="red">停止</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="page page-measure" :style="{ transform: `translateX(${(-pageIndex + 1) * 100}%)` }">
|
||||||
|
<Oscilloscope v-if="mountedPages[1]" :data="state.last_measurement">
|
||||||
|
<template #controls>
|
||||||
|
<Button @click="api.measure()" bg="limegreen" style="height: 52px; width: 100px; border-radius: 8px;">单次测量</Button>
|
||||||
|
</template>
|
||||||
|
</Oscilloscope>
|
||||||
|
</div>
|
||||||
|
<div class="page page-control" :style="{ transform: `translateX(${(-pageIndex + 2) * 100}%)` }">
|
||||||
|
<Control v-if="mountedPages[2]" :state="state" @start="clearHistory" />
|
||||||
|
</div>
|
||||||
|
<div class="page page-history" :style="{ transform: `translateX(${(-pageIndex + 3) * 100}%)` }">
|
||||||
|
<History v-if="mountedPages[3]" :history="historyData" @clear="clearHistory" />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Window v-model="showLogin" title="扫码登录">
|
||||||
|
<img :src="qrcodeSrc" alt="" class="qr-img">
|
||||||
|
<Button @click="showLogin = false">游客模式</Button>
|
||||||
|
</Window>
|
||||||
|
<Window v-model="showManualAdjust" title="手动调整">
|
||||||
|
<div class="manual-adjust-content">
|
||||||
|
<Numpad v-model="targetDis" label="目标距离" unit="mm" />
|
||||||
|
<div class="manual-right">
|
||||||
|
<Button class="manual-btn" @click="checkAndRun((val) => api.setDis(val * 1600))">以目标值校准</Button>
|
||||||
|
<Button class="manual-btn" @click="showManualAdjust = false">取消</Button>
|
||||||
|
<Button class="manual-btn" bg="limegreen" @click="checkAndRun((val) => api.goTo(val * 1600))">启动</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Window>
|
||||||
|
|
||||||
|
<Window v-model="showSettings" title="系统设置">
|
||||||
|
<div class="settings-list">
|
||||||
|
<Button class="settings-item" @click="openSpeedSettings">
|
||||||
|
<span class="settings-label">电机速度</span>
|
||||||
|
<div class="settings-value">
|
||||||
|
{{ state.speed ? Math.round(state.speed / 1.6) : '--' }} μm/s
|
||||||
|
<ChevronRight :size="20" class="arrow" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Window>
|
||||||
|
|
||||||
|
<Window v-model="showSpeedSettings" title="设置电机速度">
|
||||||
|
<div class="manual-adjust-content">
|
||||||
|
<Numpad v-model="targetSpeed" label="电机速度" unit="μm/s" />
|
||||||
|
<div class="manual-right">
|
||||||
|
<Button class="manual-btn" @click="showSpeedSettings = false">取消</Button>
|
||||||
|
<Button class="manual-btn" bg="limegreen" @click="checkSpeedAndSet()">确定</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Window>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
header {
|
||||||
|
background-color: rgb(0, 0, 161);
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.connection-state {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: gray;
|
||||||
|
margin-right: 8px;
|
||||||
|
|
||||||
|
&.connected {
|
||||||
|
background-color: limegreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disconnected {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login {
|
||||||
|
width: 48px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reload {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-label {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 64px;
|
||||||
|
height: 28px;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&.current {
|
||||||
|
background-color: #B5CDE4;
|
||||||
|
border-top: 3px solid forestgreen;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
min-width: 320px;
|
||||||
|
|
||||||
|
.settings-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 52px;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.man-adj {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
height: 150px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 140px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
overflow: hidden;
|
||||||
|
height: calc(100vh - 36px);
|
||||||
|
width: 100vw;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #B5CDE4;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-img {
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-home>.left,
|
||||||
|
.page-home>.right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-home>.left {
|
||||||
|
flex: 3;
|
||||||
|
position: relative;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-home>.right {
|
||||||
|
flex: 2;
|
||||||
|
position: relative;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
align-self: flex-start;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label::after {
|
||||||
|
content: ':';
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-adjust-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.manual-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.manual-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 52px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
188
src/api.ts
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
const API_BASE = 'http://localhost:8000';
|
||||||
|
|
||||||
|
async function ping() {
|
||||||
|
return await fetch(`${API_BASE}/ping`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function bee() {
|
||||||
|
return await fetch(`${API_BASE}/bee`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _state = {
|
||||||
|
// 连接状态
|
||||||
|
connected: false,
|
||||||
|
// 接收器距离 microsteps
|
||||||
|
dis: 16000,
|
||||||
|
// 相位差 rad
|
||||||
|
phase: 0,
|
||||||
|
// 频率 KHz
|
||||||
|
freq: 0,
|
||||||
|
// 峰峰值 mV
|
||||||
|
p2p: 0,
|
||||||
|
// 电机速度 Hz 1600 means 1mm/s or 1000 um/s
|
||||||
|
speed: 1200,
|
||||||
|
// 任务
|
||||||
|
total_tasks: 0,
|
||||||
|
tasks: [] as ({
|
||||||
|
id: string;
|
||||||
|
type: 'move';
|
||||||
|
steps: number;
|
||||||
|
remaining_steps: number;
|
||||||
|
status: 'running' | 'pending' | 'queued';
|
||||||
|
created_at: number;
|
||||||
|
} | {
|
||||||
|
id: string;
|
||||||
|
type: 'measure';
|
||||||
|
status: 'running' | 'pending' | 'queued';
|
||||||
|
created_at: number;
|
||||||
|
})[],
|
||||||
|
last_measurement:{}as {
|
||||||
|
"ts": number,
|
||||||
|
"idn": null,
|
||||||
|
"points_mode": 'NORM',
|
||||||
|
"n": number,
|
||||||
|
"tscale": number,
|
||||||
|
"toffs": number,
|
||||||
|
"f0_hz": number,
|
||||||
|
"amp1_pp_adc": number,
|
||||||
|
"amp2_pp_adc": number,
|
||||||
|
"phi1_rad": number,
|
||||||
|
"phi2_rad": number,
|
||||||
|
"dphi_rad": number,
|
||||||
|
"dphi_deg": number,
|
||||||
|
"dt_s": number,
|
||||||
|
"wave1": number[],
|
||||||
|
"wave2": number[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listeners: ((state: typeof _state) => void)[] = [];
|
||||||
|
|
||||||
|
function notifyListeners() {
|
||||||
|
for (const listener of listeners) {
|
||||||
|
listener(_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSSE() {
|
||||||
|
let reconnectTimer: NodeJS.Timeout;
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
const eventSource = new EventSource(`${API_BASE}/events`);
|
||||||
|
|
||||||
|
eventSource.onopen = () => {
|
||||||
|
console.log('SSE Connected');
|
||||||
|
_state.connected = true;
|
||||||
|
notifyListeners();
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
// 简单地合并数据到 _state
|
||||||
|
Object.assign(_state, data);
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('SSE Parsing Error:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onerror = (err) => {
|
||||||
|
console.error('SSE Error:', err);
|
||||||
|
|
||||||
|
if (_state.connected) {
|
||||||
|
_state.connected = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
eventSource.close();
|
||||||
|
|
||||||
|
clearTimeout(reconnectTimer);
|
||||||
|
reconnectTimer = setTimeout(() => {
|
||||||
|
console.log('Attempting to reconnect SSE...');
|
||||||
|
connect();
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
initSSE();
|
||||||
|
|
||||||
|
function getState() {
|
||||||
|
return _state;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onStateChange(callback: (state: typeof _state) => void) {
|
||||||
|
listeners.push(callback);
|
||||||
|
// 立即回调当前状态
|
||||||
|
callback(_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function move(steps: number) {
|
||||||
|
const url = new URL(`${API_BASE}/action/move`);
|
||||||
|
url.searchParams.append('steps', steps.toString());
|
||||||
|
return await fetch(url.toString(), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function goTo(steps: number) {
|
||||||
|
const targetSteps = Math.round(steps - _state.dis);
|
||||||
|
return move(targetSteps);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setDis(steps: number) {
|
||||||
|
const url = new URL(`${API_BASE}/state/dis`);
|
||||||
|
url.searchParams.append('dis', Math.round(steps).toString());
|
||||||
|
return await fetch(url.toString(), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function setSpeed(hz: number) {
|
||||||
|
const url = new URL(`${API_BASE}/state/speed`);
|
||||||
|
url.searchParams.append('speed', Math.round(hz).toString());
|
||||||
|
return await fetch(url.toString(), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopAll() {
|
||||||
|
return await fetch(`${API_BASE}/action/cancel`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function measure() {
|
||||||
|
return await fetch(`${API_BASE}/action/measure`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function batch(tasks: any[]) {
|
||||||
|
return await fetch(`${API_BASE}/action/batch`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(tasks),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { bee, ping, getState, onStateChange, move, goTo, setDis, setSpeed, stopAll, measure, batch };
|
||||||
BIN
src/assets/_-.woff2
Normal file
35
src/assets/apparatus.svg
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<svg width="600" height="200" viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="rail_layer">
|
||||||
|
<line x1="50" y1="160" x2="550" y2="160" stroke="#4a5568" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<line x1="100" y1="160" x2="100" y2="170" stroke="#4a5568" stroke-width="2"/>
|
||||||
|
<line x1="200" y1="160" x2="200" y2="170" stroke="#4a5568" stroke-width="2"/>
|
||||||
|
<line x1="300" y1="160" x2="300" y2="170" stroke="#4a5568" stroke-width="2"/>
|
||||||
|
<line x1="400" y1="160" x2="400" y2="170" stroke="#4a5568" stroke-width="2"/>
|
||||||
|
<line x1="500" y1="160" x2="500" y2="170" stroke="#4a5568" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="emitter_group" transform="translate(80, 100)">
|
||||||
|
<rect x="-15" y="30" width="30" height="30" rx="4" fill="#2b6cb0" />
|
||||||
|
<line x1="0" y1="30" x2="0" y2="0" stroke="#2b6cb0" stroke-width="4"/>
|
||||||
|
<circle cx="0" cy="0" r="18" fill="#4299e1" stroke="#2b6cb0" stroke-width="3"/>
|
||||||
|
<circle cx="0" cy="0" r="8" fill="#bee3f8"/>
|
||||||
|
<text x="0" y="-30" text-anchor="middle" font-family="Arial" font-size="12" fill="#4299e1" font-weight="bold">S1 发射</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="waves" stroke="#63b3ed" stroke-width="2" stroke-opacity="0.5" fill="none">
|
||||||
|
<path d="M 110 100 Q 130 100 150 100" stroke-dasharray="4,4" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="receiver_group" transform="translate(300, 100)">
|
||||||
|
<line x1="-220" y1="0" x2="-18" y2="0" stroke="#ed8936" stroke-width="1" stroke-dasharray="5,5" opacity="0.6"/>
|
||||||
|
|
||||||
|
<rect x="-15" y="30" width="30" height="30" rx="4" fill="#c05621" />
|
||||||
|
<line x1="0" y1="30" x2="0" y2="0" stroke="#c05621" stroke-width="4"/>
|
||||||
|
<path d="M -16 0 L -8 -14 L 8 -14 L 16 0 L 8 14 L -8 14 Z" fill="#ed8936" stroke="#c05621" stroke-width="3"/>
|
||||||
|
<circle cx="0" cy="0" r="6" fill="#feebc8"/>
|
||||||
|
<text x="0" y="-30" text-anchor="middle" font-family="Arial" font-size="12" fill="#ed8936" font-weight="bold">S2 接收</text>
|
||||||
|
|
||||||
|
<rect x="-30" y="65" width="60" height="20" rx="4" fill="#4a5568"/>
|
||||||
|
<text x="0" y="79" text-anchor="middle" font-family="Monospace" font-size="10" fill="#ffffff">L=Moving</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
8
src/assets/stylesheet.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'PingFang SC';
|
||||||
|
src: url('_-.woff2') format('woff2');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
1
src/assets/vue.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
BIN
src/assets/仪器.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
src/assets/仪器臂.png
Normal file
|
After Width: | Height: | Size: 436 KiB |
BIN
src/assets/发射器.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
src/assets/接收器.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
148
src/components/Button.vue
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<template>
|
||||||
|
<div @click="api.bee();" class="btn" :style="btnStyle" @pointerdown="pDown" @pointerup="pUp" @pointercancel="pUp"
|
||||||
|
@pointerleave="pUp">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import * as api from '../api.ts'
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
bg?: string;
|
||||||
|
onLongPress?: () => void;
|
||||||
|
longPressInterval?: number;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
bg: '#ffffff',
|
||||||
|
longPressInterval: 200,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let longPressTimer: number | null = null;
|
||||||
|
|
||||||
|
type Rgb = { r: number; g: number; b: number };
|
||||||
|
|
||||||
|
const clamp255 = (v: number) => Math.max(0, Math.min(255, Math.round(v)));
|
||||||
|
|
||||||
|
const parseColor = (input: string): Rgb | null => {
|
||||||
|
const s = input.trim().toLowerCase();
|
||||||
|
|
||||||
|
const hex = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
|
||||||
|
if (hex && hex[1]) {
|
||||||
|
const h = hex[1];
|
||||||
|
if (h.length === 3) {
|
||||||
|
const r = parseInt(h[0]! + h[0]!, 16);
|
||||||
|
const g = parseInt(h[1]! + h[1]!, 16);
|
||||||
|
const b = parseInt(h[2]! + h[2]!, 16);
|
||||||
|
return { r, g, b };
|
||||||
|
}
|
||||||
|
const r = parseInt(h.slice(0, 2), 16);
|
||||||
|
const g = parseInt(h.slice(2, 4), 16);
|
||||||
|
const b = parseInt(h.slice(4, 6), 16);
|
||||||
|
return { r, g, b };
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgb = s.match(
|
||||||
|
/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(\d*\.?\d+)\s*)?\)$/
|
||||||
|
);
|
||||||
|
if (rgb) {
|
||||||
|
const r = clamp255(Number(rgb[1]));
|
||||||
|
const g = clamp255(Number(rgb[2]));
|
||||||
|
const b = clamp255(Number(rgb[3]));
|
||||||
|
return { r, g, b };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const relativeLuminance = ({ r, g, b }: Rgb) => {
|
||||||
|
const srgb = [r, g, b].map((v) => v / 255);
|
||||||
|
const lin = srgb.map((c) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)));
|
||||||
|
return 0.2126 * lin[0]! + 0.7152 * lin[1]! + 0.0722 * lin[2]!;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mix = (a: number, b: number, t: number) => a + (b - a) * t;
|
||||||
|
const mixRgb = (c: Rgb, target: Rgb, t: number): Rgb => ({
|
||||||
|
r: clamp255(mix(c.r, target.r, t)),
|
||||||
|
g: clamp255(mix(c.g, target.g, t)),
|
||||||
|
b: clamp255(mix(c.b, target.b, t)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const toCssRgb = (c: Rgb) => `rgb(${c.r}, ${c.g}, ${c.b})`;
|
||||||
|
|
||||||
|
const computeHover = (bg: string) => {
|
||||||
|
const parsed = parseColor(bg);
|
||||||
|
if (!parsed) return null;
|
||||||
|
|
||||||
|
const lum = relativeLuminance(parsed);
|
||||||
|
const t = 0.14;
|
||||||
|
const target = lum > 0.6 ? { r: 0, g: 0, b: 0 } : { r: 255, g: 255, b: 255 };
|
||||||
|
return toCssRgb(mixRgb(parsed, target, t));
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnStyle = computed(() => {
|
||||||
|
const bg = props.bg;
|
||||||
|
const hover = computeHover(bg);
|
||||||
|
return {
|
||||||
|
'--btn-bg': bg,
|
||||||
|
'--btn-hover-bg': hover ?? `color-mix(in srgb, ${bg}, black 14%)`,
|
||||||
|
} as Record<string, string>;
|
||||||
|
});
|
||||||
|
|
||||||
|
const pDown = (e: PointerEvent) => {
|
||||||
|
(e.currentTarget as HTMLElement).classList.add('hover');
|
||||||
|
|
||||||
|
// 如果有长按事件,启动长按计时器
|
||||||
|
if (props.onLongPress) {
|
||||||
|
// 清除可能存在的旧计时器
|
||||||
|
if (longPressTimer !== null) {
|
||||||
|
clearInterval(longPressTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首次触发
|
||||||
|
api.bee();
|
||||||
|
props.onLongPress();
|
||||||
|
|
||||||
|
// 设置重复触发
|
||||||
|
longPressTimer = setInterval(() => {
|
||||||
|
if (props.onLongPress) {
|
||||||
|
if (props.longPressInterval > 150) api.bee();
|
||||||
|
props.onLongPress();
|
||||||
|
}
|
||||||
|
}, props.longPressInterval) as unknown as number;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pUp = (e: PointerEvent) => {
|
||||||
|
(e.currentTarget as HTMLElement).classList.remove('hover');
|
||||||
|
|
||||||
|
// 清除长按计时器
|
||||||
|
if (longPressTimer !== null) {
|
||||||
|
clearInterval(longPressTimer);
|
||||||
|
longPressTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.btn {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 96px;
|
||||||
|
height: 48px;
|
||||||
|
background-color: var(--btn-bg, white);
|
||||||
|
border: 1px solid black;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #111111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.hover {
|
||||||
|
background-color: var(--btn-hover-bg, #DDDDDD);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
145
src/components/Lissajous.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="chart" class="echart-container"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, watch, nextTick } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
// Define the shape based on provided data
|
||||||
|
interface MeasurementData {
|
||||||
|
ts: number;
|
||||||
|
n: number;
|
||||||
|
tscale: number;
|
||||||
|
toffs: number;
|
||||||
|
f0_hz: number;
|
||||||
|
amp1_pp_adc: number;
|
||||||
|
amp2_pp_adc: number;
|
||||||
|
phi1_rad: number;
|
||||||
|
phi2_rad: number;
|
||||||
|
dphi_rad: number;
|
||||||
|
dphi_deg: number;
|
||||||
|
wave1: number[];
|
||||||
|
wave2: number[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
data: MeasurementData
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const chart = ref<HTMLElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (chart.value && !chartInstance) {
|
||||||
|
chartInstance = echarts.init(chart.value);
|
||||||
|
}
|
||||||
|
updateChart();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!props.data || !props.data.wave1 || !props.data.wave2) {
|
||||||
|
chartInstance?.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { wave1, wave2, n } = props.data;
|
||||||
|
const len = Math.min(wave1.length, wave2.length, n || 10000);
|
||||||
|
|
||||||
|
if (len === 0) return;
|
||||||
|
|
||||||
|
if (chartInstance) {
|
||||||
|
// Prepare [x, y] data
|
||||||
|
const lissajousData = [];
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
lissajousData.push([wave1[i], wave2[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
animation: false,
|
||||||
|
tooltip: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: true,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderColor: '#333',
|
||||||
|
borderWidth: 1,
|
||||||
|
left: '4%',
|
||||||
|
right: '8%',
|
||||||
|
bottom: '4%',
|
||||||
|
top: '8%',
|
||||||
|
containLabel: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: 'CH1 (ADC)',
|
||||||
|
nameLocation: 'middle',
|
||||||
|
nameGap: 0,
|
||||||
|
nameTextStyle: { color: '#333', fontSize: 11 },
|
||||||
|
min: 0,
|
||||||
|
max: 255,
|
||||||
|
axisTick: { show: true },
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#333' } },
|
||||||
|
axisLabel: { color: '#333' },
|
||||||
|
splitLine: { show: true, lineStyle: { color: '#ccc' } }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: 'CH2 (ADC)',
|
||||||
|
nameLocation: 'middle',
|
||||||
|
nameGap: 0,
|
||||||
|
nameTextStyle: { color: '#333', fontSize: 11 },
|
||||||
|
min: 0,
|
||||||
|
max: 255,
|
||||||
|
axisTick: { show: true },
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#333' } },
|
||||||
|
axisLabel: { color: '#333' },
|
||||||
|
splitLine: { show: true, lineStyle: { color: '#ccc' } }
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
type: 'line',
|
||||||
|
smooth: 0.3,
|
||||||
|
symbol: 'none',
|
||||||
|
data: lissajousData,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#749f83',
|
||||||
|
width: 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.data, () => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (!chartInstance) {
|
||||||
|
initChart();
|
||||||
|
} else {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.echart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
106
src/components/Machine.vue
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { ArrowRight, ArrowLeft, MapPin } from 'lucide-vue-next';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
dis: number; // 0 - 300
|
||||||
|
task?: {
|
||||||
|
remaining_steps: number;
|
||||||
|
} | undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const targetDis = computed(() => {
|
||||||
|
if (!props.task) return null;
|
||||||
|
return (props.dis + props.task.remaining_steps) / 1600;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isMovingRight = computed(() => {
|
||||||
|
return props.task && props.task.remaining_steps > 0;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="machine-panel">
|
||||||
|
<div class="panel-title">装置位置示意图</div>
|
||||||
|
<div class="machine">
|
||||||
|
<div class="sender part">
|
||||||
|
发射器
|
||||||
|
<img src="../assets/发射器.png" alt="" draggable="false">
|
||||||
|
</div>
|
||||||
|
<div class="placeholder" :style="{ width: 80 + dis / 2 / 1600 + 'px' }">
|
||||||
|
<div class="current-val">{{ (dis / 1600).toFixed(3) }} mm</div>
|
||||||
|
|
||||||
|
<div class="arrow-container" :class="{ right: isMovingRight }"
|
||||||
|
v-if="task && task.remaining_steps !== 0">
|
||||||
|
<ArrowRight v-if="isMovingRight" :size="14" />
|
||||||
|
<ArrowLeft v-else :size="16" />
|
||||||
|
<span class="target-val">{{ targetDis?.toFixed(3) }}mm</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="receiver part">
|
||||||
|
接收器
|
||||||
|
<img src="../assets/接收器.png" alt="" draggable="false">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.machine-panel {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.part {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
position: relative;
|
||||||
|
border-bottom: 2px dashed #333333;
|
||||||
|
top: -19px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-val {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.machine {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 6px 0;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.arrow-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 14px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
118
src/components/Numpad.vue
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Button from './Button.vue';
|
||||||
|
import { Delete } from 'lucide-vue-next';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
modelValue: string;
|
||||||
|
label: string;
|
||||||
|
unit: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: string): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const inputNumber = (val: string, current: string) => {
|
||||||
|
if (val === 'del') {
|
||||||
|
emit('update:modelValue', current.slice(0, -1));
|
||||||
|
} else if (val === '.') {
|
||||||
|
if (!current.includes('.')) {
|
||||||
|
emit('update:modelValue', current + val);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit('update:modelValue', current + val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="numpad-container">
|
||||||
|
<div class="label">{{ label }}</div>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="text" :value="modelValue" disabled class="target-input">
|
||||||
|
<span class="unit">{{ unit }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-panel">
|
||||||
|
<Button class="num-key" @click="inputNumber('1', modelValue)">1</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('2', modelValue)">2</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('3', modelValue)">3</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('4', modelValue)">4</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('5', modelValue)">5</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('6', modelValue)">6</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('7', modelValue)">7</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('8', modelValue)">8</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('9', modelValue)">9</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('.', modelValue)">.</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('0', modelValue)">0</Button>
|
||||||
|
<Button class="num-key" @click="inputNumber('del', modelValue)">
|
||||||
|
<Delete :size="20" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.numpad-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label::after {
|
||||||
|
content: ':';
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 20px;
|
||||||
|
color: #666;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
border: 2px solid #808080;
|
||||||
|
background-color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-panel {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num-key {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
200
src/components/Oscilloscope.vue
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<template>
|
||||||
|
<div class="oscilloscope-container">
|
||||||
|
<!-- Header / Metrics -->
|
||||||
|
<div class="top-row">
|
||||||
|
<Panel class="metrics-panel">
|
||||||
|
<div class="metric-item">
|
||||||
|
<div class="label">频率 F0</div>
|
||||||
|
<div class="value">{{ formatNum(data.f0_hz, 1) }} <span class="unit">Hz</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-item">
|
||||||
|
<div class="label">相位差 Δφ</div>
|
||||||
|
<div class="value">{{ formatNum(data.dphi_deg, 2) }} <span class="unit">°</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-item">
|
||||||
|
<div class="label">CH1 Vpp</div>
|
||||||
|
<div class="value">{{ formatNum(data.amp1_pp_adc, 0) }} <span class="unit">ADC</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="metric-item">
|
||||||
|
<div class="label">CH2 Vpp</div>
|
||||||
|
<div class="value">{{ formatNum(data.amp2_pp_adc, 0) }} <span class="unit">ADC</span></div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
<div class="controls">
|
||||||
|
<slot name="controls"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Charts Table -->
|
||||||
|
<div class="charts-area">
|
||||||
|
<Panel class="chart-wrapper time-domain">
|
||||||
|
<div class="chart-header">
|
||||||
|
<div class="chart-title">原始波形</div>
|
||||||
|
<div class="chart-legend">
|
||||||
|
<span class="legend-item ch1">CH1</span>
|
||||||
|
<span class="legend-item ch2">CH2</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<WaveformChart :data="data" />
|
||||||
|
</Panel>
|
||||||
|
<Panel class="chart-wrapper lissajous">
|
||||||
|
<div class="chart-title">李萨如图形</div>
|
||||||
|
<Lissajous :data="data" />
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted, nextTick } from 'vue';
|
||||||
|
import Panel from './Panel.vue';
|
||||||
|
import Lissajous from './Lissajous.vue';
|
||||||
|
import WaveformChart from './WaveformChart.vue';
|
||||||
|
|
||||||
|
// Define the shape based on provided data
|
||||||
|
interface MeasurementData {
|
||||||
|
ts: number;
|
||||||
|
n: number;
|
||||||
|
tscale: number;
|
||||||
|
toffs: number;
|
||||||
|
f0_hz: number;
|
||||||
|
amp1_pp_adc: number;
|
||||||
|
amp2_pp_adc: number;
|
||||||
|
phi1_rad: number;
|
||||||
|
phi2_rad: number;
|
||||||
|
dphi_rad: number;
|
||||||
|
dphi_deg: number;
|
||||||
|
wave1: number[];
|
||||||
|
wave2: number[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
data: MeasurementData;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const formatNum = (num: number | undefined | null, digits: number) => {
|
||||||
|
if (num === undefined || num === null) return '--';
|
||||||
|
return num.toFixed(digits);
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.oscilloscope-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px;
|
||||||
|
gap: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// Inherits background from parent or set explicitly if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 12px 16px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.metric-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #64748b;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #0f172a;
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-area {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
min-height: 0; // Fix flex overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&.time-domain {
|
||||||
|
flex: 3; // Takes more width
|
||||||
|
}
|
||||||
|
|
||||||
|
&.lissajous {
|
||||||
|
flex: 2; // Takes less width
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 3px;
|
||||||
|
margin-right: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
&.ch1::before { background-color: #f59e0b; }
|
||||||
|
&.ch2::before { background-color: #3b82f6; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.echart-container {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
src/components/Panel.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="panel">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel {
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border: 1px solid #8ec4f7;
|
||||||
|
box-shadow: 0 0px 2px rgba(0, 0, 0, 0.384);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
169
src/components/TrendChart.vue
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="chart" class="chart-container"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, watch, nextTick } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
interface HistoryItem {
|
||||||
|
ts: number;
|
||||||
|
currentDis: number;
|
||||||
|
dphi_deg: number;
|
||||||
|
amp2_pp_adc: number;
|
||||||
|
f0_hz: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
data: HistoryItem[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'select', index: number): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const chart = ref<HTMLElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (chart.value && !chartInstance) {
|
||||||
|
chartInstance = echarts.init(chart.value);
|
||||||
|
chartInstance.on('click', (params) => {
|
||||||
|
if (params.dataIndex !== undefined) {
|
||||||
|
emit('select', params.dataIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
|
||||||
|
const dataPhase = props.data.map(item => [(item.currentDis / 1600).toFixed(3), item.dphi_deg]);
|
||||||
|
const dataP2P = props.data.map(item => [(item.currentDis / 1600).toFixed(3), item.amp2_pp_adc]);
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
animation: false,
|
||||||
|
legend: {
|
||||||
|
data: ['相位差 (Deg)', '峰峰值 (ADC)'],
|
||||||
|
show: true,
|
||||||
|
top: 0,
|
||||||
|
left: 'center',
|
||||||
|
textStyle: { color: '#333' }
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: true,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderColor: '#333',
|
||||||
|
borderWidth: 1,
|
||||||
|
left: '8%',
|
||||||
|
right: '8%',
|
||||||
|
bottom: '10%',
|
||||||
|
top: '24',
|
||||||
|
containLabel: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
showContent: false,
|
||||||
|
axisPointer: { type: 'cross' }
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
name: '距离 (mm)',
|
||||||
|
nameLocation: 'middle',
|
||||||
|
nameGap: 3,
|
||||||
|
nameTextStyle: { color: '#333', fontSize: 12 },
|
||||||
|
axisTick: { show: true, alignWithLabel: true },
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#333' } },
|
||||||
|
axisLabel: { color: '#333' },
|
||||||
|
splitLine: { show: true, lineStyle: { color: '#ccc' } }
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '相位差 (deg)',
|
||||||
|
nameLocation: 'middle',
|
||||||
|
nameGap: 45,
|
||||||
|
nameTextStyle: { color: '#333', fontSize: 12 },
|
||||||
|
position: 'left',
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#333' } },
|
||||||
|
axisLabel: { color: '#333' },
|
||||||
|
splitLine: { show: true, lineStyle: { color: '#ccc' } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '峰峰值 (ADC)',
|
||||||
|
nameLocation: 'middle',
|
||||||
|
nameGap: 45,
|
||||||
|
nameTextStyle: { color: '#333', fontSize: 12 },
|
||||||
|
position: 'right',
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#333' } },
|
||||||
|
axisLabel: { color: '#333' },
|
||||||
|
splitLine: { show: false }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '相位差 (Deg)',
|
||||||
|
type: 'line',
|
||||||
|
data: dataPhase,
|
||||||
|
yAxisIndex: 0,
|
||||||
|
smooth: 0.3,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
itemStyle: { color: '#c23531' },
|
||||||
|
lineStyle: { color: '#c23531', width: 2 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '峰峰值 (ADC)',
|
||||||
|
type: 'line',
|
||||||
|
data: dataP2P,
|
||||||
|
yAxisIndex: 1,
|
||||||
|
smooth: 0.3,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 4,
|
||||||
|
itemStyle: { color: '#2f4554' },
|
||||||
|
lineStyle: { color: '#2f4554', width: 2 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showTip = (index: number) => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
chartInstance.dispatchAction({
|
||||||
|
type: 'showTip',
|
||||||
|
seriesIndex: 0,
|
||||||
|
dataIndex: index
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
});
|
||||||
|
if (props.data.length > 0) {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.data, () => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (!chartInstance) initChart();
|
||||||
|
updateChart();
|
||||||
|
});
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
defineExpose({ showTip });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
146
src/components/WaveformChart.vue
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="chart" class="echart-container"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, watch, nextTick } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
interface MeasurementData {
|
||||||
|
ts: number;
|
||||||
|
n: number;
|
||||||
|
tscale: number;
|
||||||
|
toffs: number;
|
||||||
|
f0_hz: number;
|
||||||
|
amp1_pp_adc: number;
|
||||||
|
amp2_pp_adc: number;
|
||||||
|
phi1_rad: number;
|
||||||
|
phi2_rad: number;
|
||||||
|
dphi_rad: number;
|
||||||
|
dphi_deg: number;
|
||||||
|
wave1: number[];
|
||||||
|
wave2: number[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
data: MeasurementData
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const chart = ref<HTMLElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
const initChart = () => {
|
||||||
|
if (chart.value && !chartInstance) {
|
||||||
|
chartInstance = echarts.init(chart.value);
|
||||||
|
}
|
||||||
|
updateChart();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!props.data || !props.data.wave1 || !props.data.wave2) {
|
||||||
|
chartInstance?.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { wave1, wave2, n } = props.data;
|
||||||
|
const len = Math.min(wave1.length, wave2.length, n || 10000);
|
||||||
|
|
||||||
|
if (len === 0) return;
|
||||||
|
|
||||||
|
if (chartInstance) {
|
||||||
|
const optionTime = {
|
||||||
|
animation: false,
|
||||||
|
tooltip: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: true,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderColor: '#333',
|
||||||
|
borderWidth: 1,
|
||||||
|
left: '4%',
|
||||||
|
right: '8%',
|
||||||
|
bottom: '4%',
|
||||||
|
top: '8%',
|
||||||
|
containLabel: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: Array.from({length: len}, (_, i) => i),
|
||||||
|
boundaryGap: false,
|
||||||
|
name: '采样点',
|
||||||
|
nameLocation: 'middle',
|
||||||
|
nameGap: 0,
|
||||||
|
nameTextStyle: { color: '#333', fontSize: 11 },
|
||||||
|
axisTick: { show: true, alignWithLabel: true },
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#333' } },
|
||||||
|
axisLabel: { show: true, interval: 'auto', color: '#333' },
|
||||||
|
splitLine: { show: true, lineStyle: { color: '#ccc' } }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: 'ADC 值',
|
||||||
|
nameLocation: 'middle',
|
||||||
|
nameGap: 0,
|
||||||
|
nameTextStyle: { color: '#333', fontSize: 11 },
|
||||||
|
min: 0,
|
||||||
|
max: 255,
|
||||||
|
axisLine: { show: true, lineStyle: { color: '#333' } },
|
||||||
|
axisLabel: { color: '#333' },
|
||||||
|
splitLine: { show: true, lineStyle: { color: '#ccc' } },
|
||||||
|
inverse: true
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'CH1',
|
||||||
|
type: 'line',
|
||||||
|
data: wave1.slice(0, len),
|
||||||
|
smooth: 0.3,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: { width: 2, color: '#d48265' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'CH2',
|
||||||
|
type: 'line',
|
||||||
|
data: wave2.slice(0, len),
|
||||||
|
smooth: 0.3,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: { width: 2, color: '#61a0a8' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
chartInstance.setOption(optionTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.data, () => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (!chartInstance) {
|
||||||
|
initChart();
|
||||||
|
} else {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.echart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
133
src/components/Window.vue
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="window"
|
||||||
|
:style="{ left: position.x + 'px', top: position.y + 'px' }"
|
||||||
|
v-if="modelValue"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="title-bar"
|
||||||
|
@pointerdown="startDrag"
|
||||||
|
>
|
||||||
|
<div class="title-text">{{ title }}</div>
|
||||||
|
<Button class="close-btn" @click="close">×</Button>
|
||||||
|
</div>
|
||||||
|
<div class="window-body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, onUnmounted } from 'vue';
|
||||||
|
import Button from './Button.vue';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
title?: string;
|
||||||
|
modelValue: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
title: '窗口',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: boolean];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const position = reactive({ x: 0, y: 0 });
|
||||||
|
const dragState = reactive({
|
||||||
|
isDragging: false,
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
initialX: 0,
|
||||||
|
initialY: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 居中显示
|
||||||
|
position.x = (window.innerWidth - 300) / 2;
|
||||||
|
position.y = (window.innerHeight - 250) / 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
const startDrag = (e: PointerEvent) => {
|
||||||
|
if ((e.target as HTMLElement).closest('.close-btn')) return;
|
||||||
|
dragState.isDragging = true;
|
||||||
|
dragState.startX = e.clientX;
|
||||||
|
dragState.startY = e.clientY;
|
||||||
|
dragState.initialX = position.x;
|
||||||
|
dragState.initialY = position.y;
|
||||||
|
|
||||||
|
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
||||||
|
document.addEventListener('pointermove', onDrag);
|
||||||
|
document.addEventListener('pointerup', stopDrag);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDrag = (e: PointerEvent) => {
|
||||||
|
if (!dragState.isDragging) return;
|
||||||
|
|
||||||
|
const deltaX = e.clientX - dragState.startX;
|
||||||
|
const deltaY = e.clientY - dragState.startY;
|
||||||
|
|
||||||
|
position.x = dragState.initialX + deltaX;
|
||||||
|
position.y = dragState.initialY + deltaY;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopDrag = () => {
|
||||||
|
dragState.isDragging = false;
|
||||||
|
document.removeEventListener('pointermove', onDrag);
|
||||||
|
document.removeEventListener('pointerup', stopDrag);
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emit('update:modelValue', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('pointermove', onDrag);
|
||||||
|
document.removeEventListener('pointerup', stopDrag);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.window {
|
||||||
|
position: fixed;
|
||||||
|
width: 400px;
|
||||||
|
border: 1px solid;
|
||||||
|
box-shadow: 1px 1px 0 #808080;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-bar {
|
||||||
|
background: rgb(0, 0, 161);
|
||||||
|
color: white;
|
||||||
|
padding: 2px 2px 2px 8px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: move;
|
||||||
|
user-select: none;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.window-body {
|
||||||
|
padding: 12px;
|
||||||
|
background-color: rgb(221, 221, 221);
|
||||||
|
border-top: 1px solid #808080;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
src/env.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
9
src/main.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import './style.css'
|
||||||
|
import './assets/stylesheet.css'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
|
|
||||||
|
document.body.addEventListener('contextmenu', event => event.preventDefault());
|
||||||
|
// alert(window.innerWidth + ", " + window.innerHeight);
|
||||||
403
src/pages/Control.vue
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import * as api from '../api.ts';
|
||||||
|
import Panel from '../components/Panel.vue';
|
||||||
|
import Button from '../components/Button.vue';
|
||||||
|
import Machine from '../components/Machine.vue';
|
||||||
|
import Lissajous from '../components/Lissajous.vue';
|
||||||
|
import Window from '../components/Window.vue';
|
||||||
|
import Numpad from '../components/Numpad.vue';
|
||||||
|
import { showMessage } from '../utils/message';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
state: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const controlStart = ref('10');
|
||||||
|
const controlEnd = ref('20');
|
||||||
|
const controlStep = ref('0.1');
|
||||||
|
|
||||||
|
const showNumpad = ref(false);
|
||||||
|
const numpadValue = ref('');
|
||||||
|
const numpadTitle = ref('');
|
||||||
|
let currentField: 'start' | 'end' | 'step' | null = null;
|
||||||
|
|
||||||
|
function openInput(field: 'start' | 'end' | 'step') {
|
||||||
|
currentField = field;
|
||||||
|
if (field === 'start') {
|
||||||
|
numpadTitle.value = '设置起点';
|
||||||
|
numpadValue.value = controlStart.value;
|
||||||
|
} else if (field === 'end') {
|
||||||
|
numpadTitle.value = '设置终点';
|
||||||
|
numpadValue.value = controlEnd.value;
|
||||||
|
} else if (field === 'step') {
|
||||||
|
numpadTitle.value = '设置步进';
|
||||||
|
numpadValue.value = controlStep.value;
|
||||||
|
}
|
||||||
|
showNumpad.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmInput() {
|
||||||
|
if (currentField === 'start') controlStart.value = numpadValue.value;
|
||||||
|
else if (currentField === 'end') controlEnd.value = numpadValue.value;
|
||||||
|
else if (currentField === 'step') controlStep.value = numpadValue.value;
|
||||||
|
showNumpad.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'start'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function startBatch(currentDis: number) {
|
||||||
|
const start = parseFloat(controlStart.value);
|
||||||
|
const end = parseFloat(controlEnd.value);
|
||||||
|
const step = parseFloat(controlStep.value);
|
||||||
|
|
||||||
|
if (isNaN(start) || isNaN(end) || isNaN(step)) {
|
||||||
|
showMessage('请输入有效的数字格式', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (step <= 0) {
|
||||||
|
showMessage('步进必须大于0', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('start');
|
||||||
|
|
||||||
|
const tasks = [];
|
||||||
|
const currentSteps = currentDis;
|
||||||
|
const startSteps = start * 1600;
|
||||||
|
const diff = startSteps - currentSteps;
|
||||||
|
|
||||||
|
// 1. 移动到起点
|
||||||
|
if (Math.abs(diff) > 10) {
|
||||||
|
tasks.push({
|
||||||
|
cmd: 'move',
|
||||||
|
args: { steps: diff },
|
||||||
|
repeat: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 测量循环
|
||||||
|
const totalDis = end - start;
|
||||||
|
const count = Math.floor(Math.abs(totalDis) / step);
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
const dir = totalDis >= 0 ? 1 : -1;
|
||||||
|
const stepSteps = step * 1600 * dir;
|
||||||
|
tasks.push({
|
||||||
|
cmd: 'move_measure',
|
||||||
|
args: { steps: stepSteps },
|
||||||
|
repeat: count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
api.batch(tasks).then(() => {
|
||||||
|
showMessage('批量测量任务已下发', 'success');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="control-page-content">
|
||||||
|
<div class="left-section">
|
||||||
|
<!-- 顶部:装置位置示意图 -->
|
||||||
|
<Panel class="machine-panel-wrapper">
|
||||||
|
<Machine :dis="state.dis"
|
||||||
|
:task="state.tasks.reduce((acc: any, task: any) => (task.type == 'move' ? ({ remaining_steps: acc.remaining_steps + task.remaining_steps }) : acc), { remaining_steps: 0 })" />
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<!-- 下方:李萨如 + 控制面板 -->
|
||||||
|
<div class="bottom-row">
|
||||||
|
<Panel class="oscilloscope-panel-wrapper">
|
||||||
|
<div class="chart-container">
|
||||||
|
<Lissajous :data="state.last_measurement" />
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
<Panel class="inputs-panel-wrapper">
|
||||||
|
<div class="input-container">
|
||||||
|
<div class="input-group">
|
||||||
|
<label>起点</label>
|
||||||
|
<div class="input-wrapper" @click="openInput('start')">
|
||||||
|
<div class="input-display">{{ controlStart || '10' }}</div>
|
||||||
|
<span class="unit">mm</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label>终点</label>
|
||||||
|
<div class="input-wrapper" @click="openInput('end')">
|
||||||
|
<div class="input-display">{{ controlEnd || '30' }}</div>
|
||||||
|
<span class="unit">mm</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label>步进</label>
|
||||||
|
<div class="input-wrapper" @click="openInput('step')">
|
||||||
|
<div class="input-display">{{ controlStep || '0.1' }}</div>
|
||||||
|
<span class="unit">mm</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button @click="startBatch(state.dis)" bg="limegreen" class="start-btn">开始任务</Button>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-section">
|
||||||
|
<Panel class="task-list-panel">
|
||||||
|
<div class="task-head">任务列表 ({{ state.total_tasks }})</div>
|
||||||
|
<div class="task-list">
|
||||||
|
<div v-for="task in state.tasks.slice(-5)" :key="task.id" class="task-item">
|
||||||
|
<div class="task-info">
|
||||||
|
<span class="type" :class="task.type">{{ task.type === 'move' ? '移动' : '测量' }}</span>
|
||||||
|
<span class="status" :class="task.status">{{ task.status }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="task-detail" v-if="task.type === 'move'">
|
||||||
|
{{ (task.steps / 1600).toFixed(1) }}mm
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="state.tasks.length === 0" class="empty-tasks">暂无任务</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Window v-model="showNumpad" :title="numpadTitle">
|
||||||
|
<div class="numpad-content">
|
||||||
|
<Numpad v-model="numpadValue" :label="numpadTitle" unit="mm" />
|
||||||
|
<div class="numpad-actions">
|
||||||
|
<Button class="action-btn" @click="showNumpad = false">取消</Button>
|
||||||
|
<Button class="action-btn" bg="limegreen" @click="confirmInput">确定</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Window>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.control-page-content {
|
||||||
|
display: flex;
|
||||||
|
padding: 8px;
|
||||||
|
gap: 8px;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section {
|
||||||
|
flex: 3;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 0; // Prevent flex overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-section {
|
||||||
|
flex: 1.5;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.machine-panel-wrapper {
|
||||||
|
flex: 0 0 auto; // 不要压缩
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-row {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 0; // Fix nested flex overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
.oscilloscope-panel-wrapper {
|
||||||
|
flex: 3; // 左侧稍微大一点
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
/* ensure inner absolute positioning works if needed, usually oscilloscope uses flex */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #334155;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.inputs-panel-wrapper {
|
||||||
|
flex: 2; // 控制区
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-display {
|
||||||
|
width: 80px;
|
||||||
|
height: 40px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
background: white;
|
||||||
|
line-height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
height: 48px;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numpad-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numpad-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 52px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.task-head {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item {
|
||||||
|
background: #f8fafc;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-left: 4px solid #cbd5e1;
|
||||||
|
|
||||||
|
.task-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&.move {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.measure {
|
||||||
|
color: #8b5cf6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #e2e8f0;
|
||||||
|
|
||||||
|
&.running {
|
||||||
|
background: #dbface;
|
||||||
|
color: #166534;
|
||||||
|
border: 1px solid #166534;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pending {
|
||||||
|
background: #fef9c3;
|
||||||
|
color: #854d0e;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.queued {
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tasks {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
301
src/pages/History.vue
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, watch, computed, nextTick } from 'vue';
|
||||||
|
import Panel from '../components/Panel.vue';
|
||||||
|
import Button from '../components/Button.vue';
|
||||||
|
import Lissajous from '../components/Lissajous.vue';
|
||||||
|
import WaveformChart from '../components/WaveformChart.vue';
|
||||||
|
import TrendChart from '../components/TrendChart.vue';
|
||||||
|
|
||||||
|
interface HistoryItem {
|
||||||
|
ts: number;
|
||||||
|
currentDis: number;
|
||||||
|
dphi_deg: number; // Phase
|
||||||
|
amp2_pp_adc: number; // P2P
|
||||||
|
f0_hz: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
history: HistoryItem[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'clear'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const selectedIndex = ref(0);
|
||||||
|
const trendChartRef = ref<InstanceType<typeof TrendChart> | null>(null);
|
||||||
|
|
||||||
|
const selectedItem = computed(() => {
|
||||||
|
if (selectedIndex.value >= 0 && selectedIndex.value < props.history.length) {
|
||||||
|
return props.history[selectedIndex.value];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatNum = (num: number | undefined, digits: number) => {
|
||||||
|
if (num === undefined || num === null) return '--';
|
||||||
|
return num.toFixed(digits);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prev = () => {
|
||||||
|
if (selectedIndex.value > 0) {
|
||||||
|
selectedIndex.value--;
|
||||||
|
updateHighlight();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const next = () => {
|
||||||
|
if (selectedIndex.value < props.history.length - 1) {
|
||||||
|
selectedIndex.value++;
|
||||||
|
updateHighlight();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectPoint = (index: number) => {
|
||||||
|
selectedIndex.value = index;
|
||||||
|
updateHighlight();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateHighlight = () => {
|
||||||
|
trendChartRef.value?.showTip(selectedIndex.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => props.history, () => {
|
||||||
|
nextTick(() => {
|
||||||
|
// Auto select last point if none selected or just appended
|
||||||
|
if (props.history.length > 0 && (selectedIndex.value === -1 || selectedIndex.value === props.history.length - 2)) {
|
||||||
|
selectedIndex.value = props.history.length - 1;
|
||||||
|
updateHighlight();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="history-page">
|
||||||
|
<div class="charts-section">
|
||||||
|
<Panel class="trend-chart-panel">
|
||||||
|
<TrendChart ref="trendChartRef" :data="history" @select="onSelectPoint" />
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
<div class="detail-section">
|
||||||
|
<Panel class="waveform-panel">
|
||||||
|
<div class="waveform-container">
|
||||||
|
<WaveformChart v-if="selectedItem" :data="selectedItem" />
|
||||||
|
<div v-else class="no-data">暂无数据 / 未选择</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
<Panel class="lissajous-panel">
|
||||||
|
<div class="lissajous-container">
|
||||||
|
<Lissajous v-if="selectedItem" :data="selectedItem" />
|
||||||
|
<div v-else class="no-data">暂无数据 / 未选择</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
<Panel class="info-panel">
|
||||||
|
<div class="info-grid" v-if="selectedItem">
|
||||||
|
<div class="grid-item">
|
||||||
|
<div class="label">位置</div>
|
||||||
|
<div class="value">{{ (selectedItem.currentDis / 1600).toFixed(3) }} <span
|
||||||
|
class="unit">mm</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<div class="label">相位差</div>
|
||||||
|
<div class="value">{{ formatNum(selectedItem.dphi_deg, 2) }} <span class="unit">°</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="grid-item">
|
||||||
|
<div class="label">CH2 Vpp</div>
|
||||||
|
<div class="value">{{ formatNum(selectedItem.amp2_pp_adc, 0) }} <span class="unit">ADC</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<div class="nav-btns">
|
||||||
|
<Button @long-press="prev" :long-press-interval="100" :disabled="selectedIndex <= 0"
|
||||||
|
class="nav-btn">◀</Button>
|
||||||
|
<Button @long-press="next" :long-press-interval="100"
|
||||||
|
:disabled="selectedIndex >= history.length - 1" class="nav-btn">▶</Button>
|
||||||
|
</div>
|
||||||
|
<Button bg="red" @click="$emit('clear')" class="clear-btn">清除历史</Button>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.history-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charts-section {
|
||||||
|
flex: 3.5;
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-chart-panel {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section {
|
||||||
|
flex: 4;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lissajous-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #334155;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lissajous-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waveform-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.chart-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 3px;
|
||||||
|
margin-right: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ch1::before {
|
||||||
|
background-color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ch2::before {
|
||||||
|
background-color: #3b82f6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.waveform-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.grid-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btns {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.nav-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 38px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
0
src/pages/Home.vue
Normal file
15
src/style.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
font-family: 'PingFang SC', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title{
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
84
src/utils/message.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { h, render, ref } from 'vue';
|
||||||
|
import Window from '../components/Window.vue';
|
||||||
|
import Button from '../components/Button.vue';
|
||||||
|
import { AlertTriangle, XOctagon, Info, CheckCircle2 } from 'lucide-vue-next';
|
||||||
|
|
||||||
|
export function showMessage(message: string, type: 'info' | 'error' | 'success' | 'warning' = 'info', title?: string) {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
|
||||||
|
const destroy = () => {
|
||||||
|
render(null, container);
|
||||||
|
container.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCloneWindow = () => {
|
||||||
|
// 先触发 Window 内部的关闭逻辑(如果是 v-if 控制,外部设为 false 即可)
|
||||||
|
// 这里直接销毁整个挂载节点
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Icon = Info;
|
||||||
|
let color = '#3b82f6';
|
||||||
|
let defaultTitle = '提示';
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'error':
|
||||||
|
Icon = XOctagon;
|
||||||
|
color = '#ef4444';
|
||||||
|
defaultTitle = '错误';
|
||||||
|
break;
|
||||||
|
case 'success':
|
||||||
|
Icon = CheckCircle2;
|
||||||
|
color = '#22c55e';
|
||||||
|
defaultTitle = '成功';
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
Icon = AlertTriangle;
|
||||||
|
color = '#f59e0b';
|
||||||
|
defaultTitle = '警告';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window 组件接收 modelValue 控制显示
|
||||||
|
// 我们创建一个 ref 传进去,虽然我们主要是靠销毁容器来关闭
|
||||||
|
const isVisible = ref(true);
|
||||||
|
|
||||||
|
const vnode = h(Window, {
|
||||||
|
modelValue: isVisible.value,
|
||||||
|
title: title || defaultTitle,
|
||||||
|
'onUpdate:modelValue': (val: boolean) => {
|
||||||
|
if (!val) onCloneWindow();
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
default: () => h('div', {
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '20px',
|
||||||
|
gap: '15px',
|
||||||
|
minWidth: '240px'
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
h(Icon, { size: 48, color }),
|
||||||
|
h('div', {
|
||||||
|
style: {
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#333',
|
||||||
|
textAlign: 'center',
|
||||||
|
whiteSpace: 'pre-wrap'
|
||||||
|
}
|
||||||
|
}, message),
|
||||||
|
h(Button, {
|
||||||
|
onClick: onCloneWindow,
|
||||||
|
style: {
|
||||||
|
marginTop: '10px',
|
||||||
|
minWidth: '80px'
|
||||||
|
}
|
||||||
|
}, () => '确定')
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
render(vnode, container);
|
||||||
|
}
|
||||||
14
tsconfig.app.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/*.d.ts"]
|
||||||
|
}
|
||||||
7
tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
26
tsconfig.node.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
7
vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
})
|
||||||