记录与数据处理
This commit is contained in:
parent
73f948d4fc
commit
a228647152
46
bun.lock
46
bun.lock
@ -7,6 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
|
"katex": "^0.16.22",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"vue": "^3.5.24",
|
"vue": "^3.5.24",
|
||||||
@ -16,6 +17,7 @@
|
|||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"sass-embedded": "^1.97.2",
|
"sass-embedded": "^1.97.2",
|
||||||
|
"ssh2-sftp-client": "^11.0.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.2.4",
|
"vite": "^7.2.4",
|
||||||
"vue-tsc": "^3.1.4",
|
"vue-tsc": "^3.1.4",
|
||||||
@ -209,8 +211,16 @@
|
|||||||
|
|
||||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
|
"asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
|
||||||
|
|
||||||
|
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
|
||||||
|
|
||||||
"buffer-builder": ["buffer-builder@0.2.0", "", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="],
|
"buffer-builder": ["buffer-builder@0.2.0", "", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="],
|
||||||
|
|
||||||
|
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||||
|
|
||||||
|
"buildcheck": ["buildcheck@0.0.7", "", {}, "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA=="],
|
||||||
|
|
||||||
"camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
"camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||||
|
|
||||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||||
@ -223,6 +233,12 @@
|
|||||||
|
|
||||||
"colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="],
|
"colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="],
|
||||||
|
|
||||||
|
"commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
|
||||||
|
|
||||||
|
"concat-stream": ["concat-stream@2.0.0", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="],
|
||||||
|
|
||||||
|
"cpu-features": ["cpu-features@0.0.10", "", { "dependencies": { "buildcheck": "~0.0.6", "nan": "^2.19.0" } }, "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA=="],
|
||||||
|
|
||||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
||||||
@ -237,6 +253,8 @@
|
|||||||
|
|
||||||
"entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="],
|
"entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="],
|
||||||
|
|
||||||
|
"err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="],
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
@ -253,12 +271,16 @@
|
|||||||
|
|
||||||
"immutable": ["immutable@5.1.4", "", {}, "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="],
|
"immutable": ["immutable@5.1.4", "", {}, "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="],
|
||||||
|
|
||||||
|
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
"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-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=="],
|
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||||
|
|
||||||
|
"katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="],
|
||||||
|
|
||||||
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
"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=="],
|
"lucide-vue-next": ["lucide-vue-next@0.562.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-LN0BLGKMFulv0lnfK29r14DcngRUhIqdcaL0zXTt2o0oS9odlrjCGaU3/X9hIihOjjN8l8e+Y9G/famcNYaI7Q=="],
|
||||||
@ -267,6 +289,8 @@
|
|||||||
|
|
||||||
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
|
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
|
||||||
|
|
||||||
|
"nan": ["nan@2.25.0", "", {}, "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g=="],
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
"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=="],
|
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||||
@ -289,18 +313,28 @@
|
|||||||
|
|
||||||
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
"promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="],
|
||||||
|
|
||||||
"qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
|
"qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
|
||||||
|
|
||||||
|
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||||
|
|
||||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
"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-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=="],
|
"require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
|
||||||
|
|
||||||
|
"retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
|
||||||
|
|
||||||
|
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||||
|
|
||||||
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
"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": ["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": ["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=="],
|
||||||
@ -345,8 +379,14 @@
|
|||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"ssh2": ["ssh2@1.17.0", "", { "dependencies": { "asn1": "^0.2.6", "bcrypt-pbkdf": "^1.0.2" }, "optionalDependencies": { "cpu-features": "~0.0.10", "nan": "^2.23.0" } }, "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ=="],
|
||||||
|
|
||||||
|
"ssh2-sftp-client": ["ssh2-sftp-client@11.0.0", "", { "dependencies": { "concat-stream": "^2.0.0", "promise-retry": "^2.0.1", "ssh2": "^1.15.0" } }, "sha512-lOjgNYtioYquhtgyHwPryFNhllkuENjvCKkUXo18w/Q4UpEffCnEUBfiOTlwFdKIhG1rhrOGnA6DeKPSF2CP6w=="],
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||||
|
|
||||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"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=="],
|
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||||
@ -359,10 +399,16 @@
|
|||||||
|
|
||||||
"tslib": ["tslib@2.3.0", "", {}, "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="],
|
"tslib": ["tslib@2.3.0", "", {}, "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="],
|
||||||
|
|
||||||
|
"tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
|
||||||
|
|
||||||
|
"typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"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=="],
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
|
"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=="],
|
"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=="],
|
||||||
|
|||||||
@ -2,9 +2,8 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>motor-ui</title>
|
<title>自动声速测定仪 - 首页</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@ -6,11 +6,14 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc -b && vite build",
|
"build": "vue-tsc -b && vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"upload": "bun scripts/deploy.mjs",
|
||||||
|
"deploy": "bun run build && bun run upload"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
|
"katex": "^0.16.22",
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"vue": "^3.5.24"
|
"vue": "^3.5.24"
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"sass-embedded": "^1.97.2",
|
"sass-embedded": "^1.97.2",
|
||||||
|
"ssh2-sftp-client": "^11.0.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.2.4",
|
"vite": "^7.2.4",
|
||||||
"vue-tsc": "^3.1.4"
|
"vue-tsc": "^3.1.4"
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
<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>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
49
scripts/deploy.mjs
Normal file
49
scripts/deploy.mjs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import Client from 'ssh2-sftp-client';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const HOST = process.env.DEPLOY_HOST ?? '192.168.1.71';
|
||||||
|
const USERNAME = process.env.DEPLOY_USER ?? 'feie9454';
|
||||||
|
const REMOTE_DIR = process.env.DEPLOY_PATH ?? '/var/www/html';
|
||||||
|
const PASSWORD = process.env.DEPLOY_PASSWORD;
|
||||||
|
|
||||||
|
if (!PASSWORD) {
|
||||||
|
console.error('Missing DEPLOY_PASSWORD env var.');
|
||||||
|
console.error('Example (PowerShell): $env:DEPLOY_PASSWORD="<your password>"; npm run deploy');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const localDist = path.resolve(__dirname, '..', 'dist');
|
||||||
|
|
||||||
|
const sftp = new Client();
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`Deploying ${localDist} -> ${USERNAME}@${HOST}:${REMOTE_DIR}`);
|
||||||
|
|
||||||
|
await sftp.connect({
|
||||||
|
host: HOST,
|
||||||
|
username: USERNAME,
|
||||||
|
password: PASSWORD,
|
||||||
|
readyTimeout: 20000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure remote directory exists
|
||||||
|
await sftp.mkdir(REMOTE_DIR, true);
|
||||||
|
|
||||||
|
// Upload dist contents into REMOTE_DIR
|
||||||
|
await sftp.uploadDir(localDist, REMOTE_DIR);
|
||||||
|
|
||||||
|
console.log('Deploy complete.');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Deploy failed:', err?.message ?? err);
|
||||||
|
process.exitCode = 1;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
await sftp.end();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
242
src/App.vue
242
src/App.vue
@ -1,12 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, type Ref, watch } from 'vue';
|
import { reactive, ref, type Ref, watch } from 'vue';
|
||||||
import * as api from './api.ts'
|
import * as api from './api.ts'
|
||||||
|
import type { HistoryItem } from './types.ts'
|
||||||
import Button from './components/Button.vue';
|
import Button from './components/Button.vue';
|
||||||
import Machine from './components/Machine.vue';
|
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
import Panel from './components/Panel.vue';
|
|
||||||
import Window from './components/Window.vue';
|
import Window from './components/Window.vue';
|
||||||
import Oscilloscope from './components/Oscilloscope.vue';
|
|
||||||
|
|
||||||
const state = reactive({ ...api.getState() });
|
const state = reactive({ ...api.getState() });
|
||||||
|
|
||||||
@ -26,18 +24,21 @@ const showLogin = ref(false);
|
|||||||
const showManualAdjust = ref(false);
|
const showManualAdjust = ref(false);
|
||||||
const targetDis = ref('');
|
const targetDis = ref('');
|
||||||
|
|
||||||
import { Delete, RefreshCcw, Settings, ChevronRight } from 'lucide-vue-next';
|
import { Delete, RefreshCcw, Settings, ChevronRight, LogOut } from 'lucide-vue-next';
|
||||||
import { showMessage } from './utils/message';
|
import { showMessage } from './utils/message';
|
||||||
import Numpad from './components/Numpad.vue';
|
import Numpad from './components/Numpad.vue';
|
||||||
import Control from './pages/Control.vue';
|
import Control from './pages/Control.vue';
|
||||||
import History from './pages/History.vue';
|
import History from './pages/History.vue';
|
||||||
|
import Home from './pages/Home.vue';
|
||||||
|
import Measure from './pages/Measure.vue';
|
||||||
import TrendChart from './components/TrendChart.vue';
|
import TrendChart from './components/TrendChart.vue';
|
||||||
|
import DataProcess from './pages/DataProcess.vue';
|
||||||
|
|
||||||
const showSettings = ref(false);
|
const showSettings = ref(false);
|
||||||
const showSpeedSettings = ref(false);
|
const showSpeedSettings = ref(false);
|
||||||
const targetSpeed = ref('');
|
const targetSpeed = ref('');
|
||||||
|
|
||||||
const historyData = ref<any[]>([]);
|
const historyData = ref<HistoryItem[]>([]);
|
||||||
|
|
||||||
// 监听新的测量数据
|
// 监听新的测量数据
|
||||||
watch(() => state.last_measurement, (newVal, oldVal) => {
|
watch(() => state.last_measurement, (newVal, oldVal) => {
|
||||||
@ -116,28 +117,34 @@ function locationReload() {
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'home' | 'measure' | 'control' | 'history'
|
function exitSystem() {
|
||||||
const pageIndex: Ref<0 | 1 | 2 | 3> = ref(0);
|
document.exitFullscreen();
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
const mountedPages = ref([true, false, false, false]);
|
const pageNames = ['首页', '波形', '控制', '记录', '处理'];
|
||||||
|
const pageIndex: Ref<0 | 1 | 2 | 3 | 4> = ref(0);
|
||||||
|
|
||||||
|
const mountedPages = ref([true, false, false, false, false]);
|
||||||
let cleanupTimer: any = null;
|
let cleanupTimer: any = null;
|
||||||
|
|
||||||
watch(pageIndex, (newVal, oldVal) => {
|
watch(pageIndex, (newVal, oldVal) => {
|
||||||
// Ensure target page is mounted
|
// Ensure target page is mounted
|
||||||
mountedPages.value[newVal] = true;
|
document.title = `自动声速测定仪 - ${pageNames[newVal]}`;
|
||||||
|
mountedPages.value[newVal] = true;
|
||||||
|
|
||||||
// Ensure old page stays mounted during transition
|
// Ensure old page stays mounted during transition
|
||||||
if (oldVal !== undefined) {
|
if (oldVal !== undefined) {
|
||||||
mountedPages.value[oldVal] = true;
|
mountedPages.value[oldVal] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanupTimer) clearTimeout(cleanupTimer);
|
||||||
|
cleanupTimer = setTimeout(() => {
|
||||||
|
// Keep only the current page mounted to save resources
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
mountedPages.value[i] = (i === pageIndex.value);
|
||||||
}
|
}
|
||||||
|
}, 400); // 0.3s transition + buffer
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
@ -145,12 +152,16 @@ watch(pageIndex, (newVal, oldVal) => {
|
|||||||
<header>
|
<header>
|
||||||
<div class="page-label">
|
<div class="page-label">
|
||||||
<Button :class="{ current: pageIndex === 0 }" @click="pageIndex = 0">首页</Button>
|
<Button :class="{ current: pageIndex === 0 }" @click="pageIndex = 0">首页</Button>
|
||||||
<Button :class="{ current: pageIndex === 1 }" @click="pageIndex = 1">测量</Button>
|
<Button :class="{ current: pageIndex === 1 }" @click="pageIndex = 1">波形</Button>
|
||||||
<Button :class="{ current: pageIndex === 2 }" @click="pageIndex = 2">控制</Button>
|
<Button :class="{ current: pageIndex === 2 }" @click="pageIndex = 2">控制</Button>
|
||||||
<Button :class="{ current: pageIndex === 3 }" @click="pageIndex = 3">历史</Button>
|
<Button :class="{ current: pageIndex === 3 }" @click="pageIndex = 3">记录</Button>
|
||||||
|
<Button :class="{ current: pageIndex === 4 }" @click="pageIndex = 4">处理</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-title">
|
||||||
|
<div class="connection-state" :class="{ connected: state.connected, disconnected: !state.connected }"></div>
|
||||||
|
自动声速测定仪
|
||||||
</div>
|
</div>
|
||||||
<div class="connection-state" :class="{ connected: state.connected, disconnected: !state.connected }"></div>
|
|
||||||
<span>自动声速测定仪</span>
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<Button class="reload" @click="locationReload">
|
<Button class="reload" @click="locationReload">
|
||||||
<RefreshCcw />
|
<RefreshCcw />
|
||||||
@ -163,57 +174,22 @@ watch(pageIndex, (newVal, oldVal) => {
|
|||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<div class="page page-home" :style="{ transform: `translateX(${(-pageIndex - 0) * 100}%)` }">
|
<div class="page page-home" :style="{ transform: `translateX(${(-pageIndex - 0) * 100}%)` }">
|
||||||
<template v-if="mountedPages[0]">
|
<Home v-if="mountedPages[0]" :state="state" :history-data="historyData"
|
||||||
<div class="left">
|
@show-manual-adjust="showManualAdjust = true" @change-page="pageIndex = $event" />
|
||||||
<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>
|
||||||
<div class="page page-measure" :style="{ transform: `translateX(${(-pageIndex + 1) * 100}%)` }">
|
<div class="page page-measure" :style="{ transform: `translateX(${(-pageIndex + 1) * 100}%)` }">
|
||||||
<Oscilloscope v-if="mountedPages[1]" :data="state.last_measurement">
|
<Measure v-if="mountedPages[1]" :state="state" />
|
||||||
<template #controls>
|
|
||||||
<Button @click="api.measure()" bg="limegreen" style="height: 52px; width: 100px; border-radius: 8px;">单次测量</Button>
|
|
||||||
</template>
|
|
||||||
</Oscilloscope>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="page page-control" :style="{ transform: `translateX(${(-pageIndex + 2) * 100}%)` }">
|
<div class="page page-control" :style="{ transform: `translateX(${(-pageIndex + 2) * 100}%)` }">
|
||||||
<Control v-if="mountedPages[2]" :state="state" @start="clearHistory" />
|
<Control v-if="mountedPages[2]" :state="state" @start="clearHistory"
|
||||||
|
@show-motor-speed-setting="showSpeedSettings = true" @show-manual-adjust="showManualAdjust = true" />
|
||||||
</div>
|
</div>
|
||||||
<div class="page page-history" :style="{ transform: `translateX(${(-pageIndex + 3) * 100}%)` }">
|
<div class="page page-history" :style="{ transform: `translateX(${(-pageIndex + 3) * 100}%)` }">
|
||||||
<History v-if="mountedPages[3]" :history="historyData" @clear="clearHistory" />
|
<History v-if="mountedPages[3]" :history="historyData" @clear="clearHistory" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="page page-data-process" :style="{ transform: `translateX(${(-pageIndex + 4) * 100}%)` }">
|
||||||
|
<DataProcess v-if="mountedPages[4]" :history-data="historyData" />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<Window v-model="showLogin" title="扫码登录">
|
<Window v-model="showLogin" title="扫码登录">
|
||||||
<img :src="qrcodeSrc" alt="" class="qr-img">
|
<img :src="qrcodeSrc" alt="" class="qr-img">
|
||||||
@ -239,6 +215,12 @@ watch(pageIndex, (newVal, oldVal) => {
|
|||||||
<ChevronRight :size="20" class="arrow" />
|
<ChevronRight :size="20" class="arrow" />
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button class="settings-item" @click="exitSystem">
|
||||||
|
<span class="settings-label">退出系统</span>
|
||||||
|
<div class="settings-value">
|
||||||
|
<LogOut :size="20" class="arrow" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Window>
|
</Window>
|
||||||
|
|
||||||
@ -262,22 +244,8 @@ header {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
color: white;
|
color: white;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
padding-left: 40px;
|
||||||
|
|
||||||
.connection-state {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: gray;
|
|
||||||
margin-right: 8px;
|
|
||||||
|
|
||||||
&.connected {
|
|
||||||
background-color: limegreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disconnected {
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -305,6 +273,29 @@ header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.connection-state {
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: gray;
|
||||||
|
|
||||||
|
&.connected {
|
||||||
|
background-color: limegreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disconnected {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.page-label {
|
.page-label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 12px;
|
left: 12px;
|
||||||
@ -314,7 +305,7 @@ header {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
width: 64px;
|
width: 56px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
@ -354,52 +345,10 @@ header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
main {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: calc(100vh - 36px);
|
height: calc(100% - 36px);
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,43 +368,6 @@ main {
|
|||||||
height: 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 {
|
.manual-adjust-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|||||||
71
src/api.ts
71
src/api.ts
@ -1,4 +1,8 @@
|
|||||||
const API_BASE = 'http://localhost:8000';
|
import type { AppState, BatchTask } from './types';
|
||||||
|
|
||||||
|
const url = new URL(window.location.origin);
|
||||||
|
url.port = '8000';
|
||||||
|
const API_BASE = url.toString().endsWith('/') ? url.toString().slice(0, -1) : url.toString();
|
||||||
|
|
||||||
async function ping() {
|
async function ping() {
|
||||||
return await fetch(`${API_BASE}/ping`);
|
return await fetch(`${API_BASE}/ping`);
|
||||||
@ -8,55 +12,36 @@ async function bee() {
|
|||||||
return await fetch(`${API_BASE}/bee`);
|
return await fetch(`${API_BASE}/bee`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const _state = {
|
const _state: AppState = {
|
||||||
// 连接状态
|
|
||||||
connected: false,
|
connected: false,
|
||||||
// 接收器距离 microsteps
|
|
||||||
dis: 16000,
|
dis: 16000,
|
||||||
// 相位差 rad
|
|
||||||
phase: 0,
|
phase: 0,
|
||||||
// 频率 KHz
|
|
||||||
freq: 0,
|
freq: 0,
|
||||||
// 峰峰值 mV
|
|
||||||
p2p: 0,
|
p2p: 0,
|
||||||
// 电机速度 Hz 1600 means 1mm/s or 1000 um/s
|
|
||||||
speed: 1200,
|
speed: 1200,
|
||||||
// 任务
|
|
||||||
total_tasks: 0,
|
total_tasks: 0,
|
||||||
tasks: [] as ({
|
tasks: [],
|
||||||
id: string;
|
last_measurement: {
|
||||||
type: 'move';
|
ts: 0,
|
||||||
steps: number;
|
idn: null,
|
||||||
remaining_steps: number;
|
points_mode: 'NORM',
|
||||||
status: 'running' | 'pending' | 'queued';
|
n: 0,
|
||||||
created_at: number;
|
tscale: 0,
|
||||||
} | {
|
toffs: 0,
|
||||||
id: string;
|
f0_hz: 0,
|
||||||
type: 'measure';
|
amp1_pp_adc: 0,
|
||||||
status: 'running' | 'pending' | 'queued';
|
amp2_pp_adc: 0,
|
||||||
created_at: number;
|
phi1_rad: 0,
|
||||||
})[],
|
phi2_rad: 0,
|
||||||
last_measurement:{}as {
|
dphi_rad: 0,
|
||||||
"ts": number,
|
dphi_deg: 0,
|
||||||
"idn": null,
|
dt_s: 0,
|
||||||
"points_mode": 'NORM',
|
wave1: [],
|
||||||
"n": number,
|
wave2: [],
|
||||||
"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)[] = [];
|
const listeners: ((state: AppState) => void)[] = [];
|
||||||
|
|
||||||
function notifyListeners() {
|
function notifyListeners() {
|
||||||
for (const listener of listeners) {
|
for (const listener of listeners) {
|
||||||
@ -65,7 +50,7 @@ function notifyListeners() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initSSE() {
|
function initSSE() {
|
||||||
let reconnectTimer: NodeJS.Timeout;
|
let reconnectTimer: any;
|
||||||
|
|
||||||
const connect = () => {
|
const connect = () => {
|
||||||
const eventSource = new EventSource(`${API_BASE}/events`);
|
const eventSource = new EventSource(`${API_BASE}/events`);
|
||||||
@ -114,7 +99,7 @@ function getState() {
|
|||||||
return _state;
|
return _state;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onStateChange(callback: (state: typeof _state) => void) {
|
async function onStateChange(callback: (state: AppState) => void) {
|
||||||
listeners.push(callback);
|
listeners.push(callback);
|
||||||
// 立即回调当前状态
|
// 立即回调当前状态
|
||||||
callback(_state);
|
callback(_state);
|
||||||
@ -175,7 +160,7 @@ async function measure() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function batch(tasks: any[]) {
|
async function batch(tasks: BatchTask[]) {
|
||||||
return await fetch(`${API_BASE}/action/batch`, {
|
return await fetch(`${API_BASE}/action/batch`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
Binary file not shown.
@ -1,35 +0,0 @@
|
|||||||
<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>
|
|
||||||
|
Before Width: | Height: | Size: 2.0 KiB |
@ -1,8 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'PingFang SC';
|
|
||||||
src: url('_-.woff2') format('woff2');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
<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>
|
|
||||||
|
Before Width: | Height: | Size: 496 B |
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 436 KiB |
43
src/components/Formula.vue
Normal file
43
src/components/Formula.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import katex from 'katex';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
tex: string;
|
||||||
|
displayMode?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
displayMode: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const html = computed(() => {
|
||||||
|
try {
|
||||||
|
return katex.renderToString(props.tex, {
|
||||||
|
displayMode: props.displayMode,
|
||||||
|
throwOnError: false,
|
||||||
|
strict: 'ignore',
|
||||||
|
trust: false,
|
||||||
|
output: 'html',
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span class="formula" :class="{ block: displayMode }" v-html="html"></span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.formula {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: -0.12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formula.block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -84,6 +84,7 @@ img {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.machine {
|
.machine {
|
||||||
|
|||||||
50
src/components/ManualAdjust.vue
Normal file
50
src/components/ManualAdjust.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as api from '../api.ts';
|
||||||
|
import Button from './Button.vue';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'showInput'): void;
|
||||||
|
}>();
|
||||||
|
import { ArrowLeft, ArrowRight } from 'lucide-vue-next';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="manual-adjust">
|
||||||
|
<div class="line">
|
||||||
|
<Button class="man-adj" @long-press="api.move(-40)"><ArrowLeft /></Button>
|
||||||
|
<span>手动<br>调整</span>
|
||||||
|
<Button class="man-adj" @long-press="api.move(40)"><ArrowRight /></Button>
|
||||||
|
</div>
|
||||||
|
<Button class="input-dis" @click="emit('showInput')">
|
||||||
|
输入
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.manual-adjust {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.man-adj {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-dis {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -29,7 +29,7 @@ const inputNumber = (val: string, current: string) => {
|
|||||||
<div class="numpad-container">
|
<div class="numpad-container">
|
||||||
<div class="label">{{ label }}</div>
|
<div class="label">{{ label }}</div>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<input type="text" :value="modelValue" disabled class="target-input">
|
<div class="target-input" aria-disabled="true">{{ modelValue || '\u00A0' }}</div>
|
||||||
<span class="unit">{{ unit }}</span>
|
<span class="unit">{{ unit }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-panel">
|
<div class="input-panel">
|
||||||
@ -86,10 +86,12 @@ const inputNumber = (val: string, current: string) => {
|
|||||||
.target-input {
|
.target-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
text-align: center;
|
|
||||||
border: 2px solid #808080;
|
border: 2px solid #808080;
|
||||||
background-color: white;
|
background-color: white !important;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|||||||
@ -19,29 +19,41 @@ const props = defineProps<{
|
|||||||
data: HistoryItem[];
|
data: HistoryItem[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'select', index: number): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const chart = ref<HTMLElement | null>(null);
|
const chart = ref<HTMLElement | null>(null);
|
||||||
let chartInstance: echarts.ECharts | null = null;
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let chartIndexToRawIndex: number[] = [];
|
||||||
|
let rawIndexToChartIndex: number[] = [];
|
||||||
|
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
if (chart.value && !chartInstance) {
|
if (chart.value && !chartInstance) {
|
||||||
chartInstance = echarts.init(chart.value);
|
chartInstance = echarts.init(chart.value);
|
||||||
chartInstance.on('click', (params) => {
|
|
||||||
if (params.dataIndex !== undefined) {
|
|
||||||
emit('select', params.dataIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateChart = () => {
|
const updateChart = () => {
|
||||||
if (!chartInstance) return;
|
if (!chartInstance) return;
|
||||||
|
|
||||||
const dataPhase = props.data.map(item => [(item.currentDis / 1600).toFixed(3), item.dphi_deg]);
|
// 处理相位差数据,检测跳变并断开连线
|
||||||
|
const dataPhase: ([(string | number), number] | null)[] = [];
|
||||||
const dataP2P = props.data.map(item => [(item.currentDis / 1600).toFixed(3), item.amp2_pp_adc]);
|
const dataP2P = props.data.map(item => [(item.currentDis / 1600).toFixed(3), item.amp2_pp_adc]);
|
||||||
|
chartIndexToRawIndex = [];
|
||||||
|
rawIndexToChartIndex = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < props.data.length; i++) {
|
||||||
|
const item = props.data[i]!;
|
||||||
|
const x = (item.currentDis / 1600).toFixed(3);
|
||||||
|
const y = item.dphi_deg;
|
||||||
|
|
||||||
|
// 检测相位跳变(阈值设为 180 度)
|
||||||
|
if (i > 0 && Math.abs(y - props.data[i - 1]!.dphi_deg) > 180) {
|
||||||
|
// 在跳变处插入 null 以断开线条
|
||||||
|
dataPhase.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
rawIndexToChartIndex[i] = dataPhase.length;
|
||||||
|
chartIndexToRawIndex[dataPhase.length] = i;
|
||||||
|
dataPhase.push([x, y]);
|
||||||
|
}
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
animation: false,
|
animation: false,
|
||||||
@ -112,6 +124,7 @@ const updateChart = () => {
|
|||||||
smooth: 0.3,
|
smooth: 0.3,
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
symbolSize: 4,
|
symbolSize: 4,
|
||||||
|
connectNulls: false,
|
||||||
itemStyle: { color: '#c23531' },
|
itemStyle: { color: '#c23531' },
|
||||||
lineStyle: { color: '#c23531', width: 2 }
|
lineStyle: { color: '#c23531', width: 2 }
|
||||||
},
|
},
|
||||||
@ -134,10 +147,12 @@ const updateChart = () => {
|
|||||||
|
|
||||||
const showTip = (index: number) => {
|
const showTip = (index: number) => {
|
||||||
if (!chartInstance) return;
|
if (!chartInstance) return;
|
||||||
|
const chartDataIndex = rawIndexToChartIndex[index];
|
||||||
|
if (chartDataIndex === undefined) return;
|
||||||
chartInstance.dispatchAction({
|
chartInstance.dispatchAction({
|
||||||
type: 'showTip',
|
type: 'showTip',
|
||||||
seriesIndex: 0,
|
seriesIndex: 0,
|
||||||
dataIndex: index
|
dataIndex: chartDataIndex
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,16 @@
|
|||||||
class="window"
|
class="window"
|
||||||
:style="{ left: position.x + 'px', top: position.y + 'px' }"
|
:style="{ left: position.x + 'px', top: position.y + 'px' }"
|
||||||
v-if="modelValue"
|
v-if="modelValue"
|
||||||
|
ref="windowEl"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="title-bar"
|
class="title-bar"
|
||||||
@pointerdown="startDrag"
|
@pointerdown="startDrag"
|
||||||
>
|
>
|
||||||
<div class="title-text">{{ title }}</div>
|
<div class="title-text">{{ title }}</div>
|
||||||
<Button class="close-btn" @click="close">×</Button>
|
<Button class="close-btn" @click="close">
|
||||||
|
<X color="black" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="window-body">
|
<div class="window-body">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@ -18,8 +21,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, onMounted, onUnmounted } from 'vue';
|
import { reactive, onMounted, onUnmounted, ref } from 'vue';
|
||||||
import Button from './Button.vue';
|
import Button from './Button.vue';
|
||||||
|
import { X } from 'lucide-vue-next'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@ -35,6 +39,8 @@ const emit = defineEmits<{
|
|||||||
'update:modelValue': [value: boolean];
|
'update:modelValue': [value: boolean];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const windowEl = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const position = reactive({ x: 0, y: 0 });
|
const position = reactive({ x: 0, y: 0 });
|
||||||
const dragState = reactive({
|
const dragState = reactive({
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
@ -42,12 +48,64 @@ const dragState = reactive({
|
|||||||
startY: 0,
|
startY: 0,
|
||||||
initialX: 0,
|
initialX: 0,
|
||||||
initialY: 0,
|
initialY: 0,
|
||||||
|
invLinearMatrix: null as DOMMatrix | null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getLinearTransformMatrix = (el: HTMLElement): DOMMatrix => {
|
||||||
|
const style = window.getComputedStyle(el);
|
||||||
|
const t = style.transform;
|
||||||
|
const matrix = t && t !== 'none' ? new DOMMatrix(t) : new DOMMatrix();
|
||||||
|
// 只需要线性部分(旋转/缩放/倾斜),平移对“增量向量”没有影响
|
||||||
|
matrix.m41 = 0;
|
||||||
|
matrix.m42 = 0;
|
||||||
|
matrix.m43 = 0;
|
||||||
|
return matrix;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findTransformedAncestor = (el: HTMLElement | null): HTMLElement | null => {
|
||||||
|
let cur = el?.parentElement ?? null;
|
||||||
|
while (cur) {
|
||||||
|
const style = window.getComputedStyle(cur);
|
||||||
|
if (
|
||||||
|
(style.transform && style.transform !== 'none') ||
|
||||||
|
(style.perspective && style.perspective !== 'none') ||
|
||||||
|
(style.filter && style.filter !== 'none')
|
||||||
|
) {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
cur = cur.parentElement;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCumulativeLinearMatrixToViewport = (fromEl: HTMLElement | null): DOMMatrix => {
|
||||||
|
// 累乘 fromEl 以及其祖先的 transform(只保留线性部分),得到“本地 -> 屏幕”的线性变换
|
||||||
|
let cur = fromEl;
|
||||||
|
let combined = new DOMMatrix();
|
||||||
|
while (cur) {
|
||||||
|
const t = getLinearTransformMatrix(cur);
|
||||||
|
combined = t.multiply(combined);
|
||||||
|
cur = cur.parentElement;
|
||||||
|
}
|
||||||
|
return combined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const screenDeltaToLocalDelta = (invLinearMatrix: DOMMatrix | null, dx: number, dy: number) => {
|
||||||
|
if (!invLinearMatrix) return { x: dx, y: dy };
|
||||||
|
// 用两个点相减的方式消掉平移(虽然我们已将平移清零,但这样更稳)
|
||||||
|
const p0 = new DOMPoint(0, 0);
|
||||||
|
const p1 = new DOMPoint(dx, dy);
|
||||||
|
const lp0 = p0.matrixTransform(invLinearMatrix);
|
||||||
|
const lp1 = p1.matrixTransform(invLinearMatrix);
|
||||||
|
return { x: lp1.x - lp0.x, y: lp1.y - lp0.y };
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 居中显示
|
// 居中显示:优先使用“最近的变换祖先”的本地尺寸(更符合 rotate 容器内的坐标系)
|
||||||
position.x = (window.innerWidth - 300) / 2;
|
const container = findTransformedAncestor(windowEl.value) ?? windowEl.value?.parentElement;
|
||||||
position.y = (window.innerHeight - 250) / 2;
|
const w = container?.clientWidth ?? window.innerWidth;
|
||||||
|
position.x = (w - 400) / 2;
|
||||||
|
position.y = 50;
|
||||||
});
|
});
|
||||||
|
|
||||||
const startDrag = (e: PointerEvent) => {
|
const startDrag = (e: PointerEvent) => {
|
||||||
@ -58,6 +116,15 @@ const startDrag = (e: PointerEvent) => {
|
|||||||
dragState.initialX = position.x;
|
dragState.initialX = position.x;
|
||||||
dragState.initialY = position.y;
|
dragState.initialY = position.y;
|
||||||
|
|
||||||
|
// 若父容器(或其祖先)有 rotate/scale 等 transform,需要把“屏幕增量”逆变换回本地增量
|
||||||
|
const container = findTransformedAncestor(windowEl.value);
|
||||||
|
const toViewport = getCumulativeLinearMatrixToViewport(container);
|
||||||
|
try {
|
||||||
|
dragState.invLinearMatrix = toViewport.inverse();
|
||||||
|
} catch {
|
||||||
|
dragState.invLinearMatrix = null;
|
||||||
|
}
|
||||||
|
|
||||||
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
||||||
document.addEventListener('pointermove', onDrag);
|
document.addEventListener('pointermove', onDrag);
|
||||||
document.addEventListener('pointerup', stopDrag);
|
document.addEventListener('pointerup', stopDrag);
|
||||||
@ -69,12 +136,14 @@ const onDrag = (e: PointerEvent) => {
|
|||||||
const deltaX = e.clientX - dragState.startX;
|
const deltaX = e.clientX - dragState.startX;
|
||||||
const deltaY = e.clientY - dragState.startY;
|
const deltaY = e.clientY - dragState.startY;
|
||||||
|
|
||||||
position.x = dragState.initialX + deltaX;
|
const localDelta = screenDeltaToLocalDelta(dragState.invLinearMatrix, deltaX, deltaY);
|
||||||
position.y = dragState.initialY + deltaY;
|
position.x = dragState.initialX + localDelta.x;
|
||||||
|
position.y = dragState.initialY + localDelta.y;
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopDrag = () => {
|
const stopDrag = () => {
|
||||||
dragState.isDragging = false;
|
dragState.isDragging = false;
|
||||||
|
dragState.invLinearMatrix = null;
|
||||||
document.removeEventListener('pointermove', onDrag);
|
document.removeEventListener('pointermove', onDrag);
|
||||||
document.removeEventListener('pointerup', stopDrag);
|
document.removeEventListener('pointerup', stopDrag);
|
||||||
};
|
};
|
||||||
@ -108,6 +177,7 @@ onUnmounted(() => {
|
|||||||
cursor: move;
|
cursor: move;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-text {
|
.title-text {
|
||||||
@ -123,8 +193,8 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.window-body {
|
.window-body {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background-color: rgb(221, 221, 221);
|
background-color: rgb(226, 226, 226);
|
||||||
border-top: 1px solid #808080;
|
border-top: 1px solid #c8c8c8;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
18
src/main.ts
18
src/main.ts
@ -1,9 +1,25 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
import './assets/stylesheet.css'
|
import 'katex/dist/katex.min.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const app = document.getElementById('app');
|
||||||
|
if (!app) return;
|
||||||
|
let { innerWidth: w, innerHeight: h } = window;
|
||||||
|
let isRotate = false;
|
||||||
|
if (h > w) { [w, h] = [h, w]; isRotate = true; };
|
||||||
|
let scaleRatio = 1
|
||||||
|
if (h < 500) { scaleRatio = h / 500; h = 500; w = w / scaleRatio; }
|
||||||
|
app.style.width = w + 'px';
|
||||||
|
app.style.height = h + 'px';
|
||||||
|
app.style.transform = isRotate ? `translateX(${h * scaleRatio}px) rotate(90deg) scale(${scaleRatio})` : 'none';
|
||||||
|
}
|
||||||
|
resize();
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
createApp(App).mount('#app')
|
||||||
|
|
||||||
document.body.addEventListener('contextmenu', event => event.preventDefault());
|
document.body.addEventListener('contextmenu', event => event.preventDefault());
|
||||||
// alert(window.innerWidth + ", " + window.innerHeight);
|
// alert(window.innerWidth + ", " + window.innerHeight);
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import * as api from '../api.ts';
|
import * as api from '../api.ts';
|
||||||
|
import type { AppState, BatchTask } from '../types.ts';
|
||||||
import Panel from '../components/Panel.vue';
|
import Panel from '../components/Panel.vue';
|
||||||
import Button from '../components/Button.vue';
|
import Button from '../components/Button.vue';
|
||||||
import Machine from '../components/Machine.vue';
|
import Machine from '../components/Machine.vue';
|
||||||
|
import ManualAdjust from '../components/ManualAdjust.vue';
|
||||||
import Lissajous from '../components/Lissajous.vue';
|
import Lissajous from '../components/Lissajous.vue';
|
||||||
import Window from '../components/Window.vue';
|
import Window from '../components/Window.vue';
|
||||||
import Numpad from '../components/Numpad.vue';
|
import Numpad from '../components/Numpad.vue';
|
||||||
import { showMessage } from '../utils/message';
|
import { showMessage } from '../utils/message';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
state: any;
|
state: AppState;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const controlStart = ref('10');
|
const controlStart = ref('10');
|
||||||
@ -46,6 +48,8 @@ function confirmInput() {
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'start'): void;
|
(e: 'start'): void;
|
||||||
|
(e: 'showManualAdjust'): void;
|
||||||
|
(e: 'showMotorSpeedSetting'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function startBatch(currentDis: number) {
|
function startBatch(currentDis: number) {
|
||||||
@ -64,7 +68,7 @@ function startBatch(currentDis: number) {
|
|||||||
|
|
||||||
emit('start');
|
emit('start');
|
||||||
|
|
||||||
const tasks = [];
|
const tasks: BatchTask[] = [];
|
||||||
const currentSteps = currentDis;
|
const currentSteps = currentDis;
|
||||||
const startSteps = start * 1600;
|
const startSteps = start * 1600;
|
||||||
const diff = startSteps - currentSteps;
|
const diff = startSteps - currentSteps;
|
||||||
@ -83,13 +87,13 @@ function startBatch(currentDis: number) {
|
|||||||
const count = Math.floor(Math.abs(totalDis) / step);
|
const count = Math.floor(Math.abs(totalDis) / step);
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
const dir = totalDis >= 0 ? 1 : -1;
|
const dir = totalDis >= 0 ? 1 : -1;
|
||||||
const stepSteps = step * 1600 * dir;
|
const stepSteps = step * 1600 * dir;
|
||||||
tasks.push({
|
tasks.push({
|
||||||
cmd: 'move_measure',
|
cmd: 'move_measure',
|
||||||
args: { steps: stepSteps },
|
args: { steps: stepSteps },
|
||||||
repeat: count
|
repeat: count
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
api.batch(tasks).then(() => {
|
api.batch(tasks).then(() => {
|
||||||
@ -100,76 +104,87 @@ function startBatch(currentDis: number) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="control-page-content">
|
<div class="control-page-content">
|
||||||
<div class="left-section">
|
<div class="left-section">
|
||||||
<!-- 顶部:装置位置示意图 -->
|
<!-- 顶部:装置位置示意图 -->
|
||||||
<Panel class="machine-panel-wrapper">
|
<Panel class="machine-panel-wrapper">
|
||||||
<Machine :dis="state.dis"
|
<Machine :dis="state.dis" :task="state.tasks.find(t => t.type == 'move')" />
|
||||||
:task="state.tasks.reduce((acc: any, task: any) => (task.type == 'move' ? ({ remaining_steps: acc.remaining_steps + task.remaining_steps }) : acc), { remaining_steps: 0 })" />
|
<ManualAdjust @show-input="emit('showManualAdjust')" class="manual-adjust-wrapper" />
|
||||||
|
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<!-- 下方:李萨如 + 控制面板 -->
|
||||||
|
<div class="bottom-row">
|
||||||
|
<Panel class="oscilloscope-panel-wrapper">
|
||||||
|
<div class="chart-container">
|
||||||
|
<Lissajous :data="state.last_measurement" />
|
||||||
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
<Panel class="inputs-panel-wrapper">
|
||||||
<!-- 下方:李萨如 + 控制面板 -->
|
<div class="input-container">
|
||||||
<div class="bottom-row">
|
<div class="input-group">
|
||||||
<Panel class="oscilloscope-panel-wrapper">
|
<label>起点</label>
|
||||||
<div class="chart-container">
|
<div class="input-wrapper">
|
||||||
<Lissajous :data="state.last_measurement" />
|
<Button class="input-display" @click="openInput('start')">{{ controlStart || '10' }}</Button>
|
||||||
</div>
|
<span class="unit">mm</span>
|
||||||
</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>
|
</div>
|
||||||
<div v-if="state.tasks.length === 0" class="empty-tasks">暂无任务</div>
|
<div class="input-group">
|
||||||
|
<label>终点</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<Button class="input-display" @click="openInput('end')">{{ controlEnd || '30' }}</Button>
|
||||||
|
<span class="unit">mm</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label>步进</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<Button class="input-display" @click="openInput('step')">{{ controlStep || '0.1' }}</Button>
|
||||||
|
<span class="unit">mm</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label>电机速度</label>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<Button class="input-display" @click="emit('showMotorSpeedSetting')">{{ state.speed ? Math.round(state.speed / 1.6) : '--'
|
||||||
|
}}</Button>
|
||||||
|
<span class="unit">μm/s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button @click="startBatch(state.dis)" bg="limegreen" class="start-btn">开始任务</Button>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Window v-model="showNumpad" :title="numpadTitle">
|
<div class="right-section">
|
||||||
<div class="numpad-content">
|
<Panel class="task-list-panel">
|
||||||
<Numpad v-model="numpadValue" :label="numpadTitle" unit="mm" />
|
<div class="task-head">任务列表 ({{ state.total_tasks }})</div>
|
||||||
<div class="numpad-actions">
|
<div class="task-list">
|
||||||
<Button class="action-btn" @click="showNumpad = false">取消</Button>
|
<div v-for="task in state.tasks.slice(-5)" :key="task.id" class="task-item">
|
||||||
<Button class="action-btn" bg="limegreen" @click="confirmInput">确定</Button>
|
<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>
|
||||||
|
<div class="task-detail" v-if="task.type === 'move'">
|
||||||
|
{{ (task.steps / 1600).toFixed(3) }}mm
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="state.tasks.length === 0" class="empty-tasks">暂无任务</div>
|
||||||
</div>
|
</div>
|
||||||
</Window>
|
<Button class="action-btn" @click="api.stopAll()" bg="red">停止</Button>
|
||||||
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -185,219 +200,217 @@ function startBatch(currentDis: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.left-section {
|
.left-section {
|
||||||
flex: 3;
|
flex: 3;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 0; // Prevent flex overflow
|
min-width: 0; // Prevent flex overflow
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-section {
|
.right-section {
|
||||||
flex: 1.5;
|
flex: 1.5;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.machine-panel-wrapper {
|
.machine-panel-wrapper {
|
||||||
flex: 0 0 auto; // 不要压缩
|
flex: 0 0 auto; // 不要压缩
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-adjust-wrapper {
|
||||||
|
width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-row {
|
.bottom-row {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
min-height: 0; // Fix nested flex overflow
|
min-height: 0; // Fix nested flex overflow
|
||||||
}
|
}
|
||||||
|
|
||||||
.oscilloscope-panel-wrapper {
|
.oscilloscope-panel-wrapper {
|
||||||
flex: 3; // 左侧稍微大一点
|
flex: 3; // 左侧稍微大一点
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
/* ensure inner absolute positioning works if needed, usually oscilloscope uses flex */
|
/* ensure inner absolute positioning works if needed, usually oscilloscope uses flex */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-title {
|
.chart-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #334155;
|
color: #334155;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.inputs-panel-wrapper {
|
.inputs-panel-wrapper {
|
||||||
flex: 2; // 控制区
|
flex: 2.5; // 控制区
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-container {
|
.input-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 6px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group {
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
width: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
.input-display {
|
||||||
font-weight: bold;
|
width: 80px;
|
||||||
font-size: 16px;
|
height: 40px;
|
||||||
color: #333;
|
}
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper {
|
.unit {
|
||||||
display: flex;
|
font-size: 14px;
|
||||||
align-items: center;
|
color: #666;
|
||||||
gap: 6px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
.start-btn {
|
||||||
margin-top: 10px;
|
margin-top: 4px;
|
||||||
height: 48px;
|
height: 36px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.numpad-content {
|
.numpad-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.numpad-actions {
|
.numpad-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-panel {
|
.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;
|
flex: 1;
|
||||||
|
overflow-y: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 10px;
|
gap: 6px;
|
||||||
height: 100%;
|
}
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.task-head {
|
.task-item {
|
||||||
font-weight: bold;
|
background: #f8fafc;
|
||||||
margin-bottom: 8px;
|
padding: 8px;
|
||||||
font-size: 16px;
|
border-radius: 4px;
|
||||||
border-bottom: 1px solid #eee;
|
display: flex;
|
||||||
padding-bottom: 6px;
|
justify-content: space-between;
|
||||||
}
|
align-items: center;
|
||||||
|
border-left: 4px solid #cbd5e1;
|
||||||
|
|
||||||
.task-list {
|
.task-info {
|
||||||
flex: 1;
|
|
||||||
overflow-y: hidden;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
gap: 8px;
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-item {
|
|
||||||
background: #f8fafc;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-left: 4px solid #cbd5e1;
|
}
|
||||||
|
|
||||||
.task-info {
|
.type {
|
||||||
display: flex;
|
font-weight: bold;
|
||||||
gap: 8px;
|
font-size: 14px;
|
||||||
align-items: center;
|
|
||||||
|
&.move {
|
||||||
|
color: #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type {
|
&.measure {
|
||||||
font-weight: bold;
|
color: #8b5cf6;
|
||||||
font-size: 14px;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.move {
|
.status {
|
||||||
color: #3b82f6;
|
font-size: 12px;
|
||||||
}
|
padding: 2px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #e2e8f0;
|
||||||
|
|
||||||
&.measure {
|
&.running {
|
||||||
color: #8b5cf6;
|
background: #dbface;
|
||||||
}
|
color: #166534;
|
||||||
|
border: 1px solid #166534;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
&.pending {
|
||||||
font-size: 12px;
|
background: #fef9c3;
|
||||||
padding: 2px 6px;
|
color: #854d0e;
|
||||||
border-radius: 999px;
|
}
|
||||||
|
|
||||||
|
&.queued {
|
||||||
background: #e2e8f0;
|
background: #e2e8f0;
|
||||||
|
color: #475569;
|
||||||
&.running {
|
|
||||||
background: #dbface;
|
|
||||||
color: #166534;
|
|
||||||
border: 1px solid #166534;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.pending {
|
|
||||||
background: #fef9c3;
|
|
||||||
color: #854d0e;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.queued {
|
|
||||||
background: #e2e8f0;
|
|
||||||
color: #475569;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.empty-tasks {
|
.empty-tasks {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #999;
|
color: #999;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
1006
src/pages/DataProcess.vue
Normal file
1006
src/pages/DataProcess.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch, computed, nextTick } from 'vue';
|
import { ref, onMounted, watch, computed, nextTick } from 'vue';
|
||||||
|
import type { HistoryItem } from '../types.ts';
|
||||||
import Panel from '../components/Panel.vue';
|
import Panel from '../components/Panel.vue';
|
||||||
import Button from '../components/Button.vue';
|
import Button from '../components/Button.vue';
|
||||||
import Lissajous from '../components/Lissajous.vue';
|
import Lissajous from '../components/Lissajous.vue';
|
||||||
import WaveformChart from '../components/WaveformChart.vue';
|
import WaveformChart from '../components/WaveformChart.vue';
|
||||||
import TrendChart from '../components/TrendChart.vue';
|
import TrendChart from '../components/TrendChart.vue';
|
||||||
|
import { ArrowLeft, ArrowRight } from 'lucide-vue-next';
|
||||||
interface HistoryItem {
|
|
||||||
ts: number;
|
|
||||||
currentDis: number;
|
|
||||||
dphi_deg: number; // Phase
|
|
||||||
amp2_pp_adc: number; // P2P
|
|
||||||
f0_hz: number;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
history: HistoryItem[];
|
history: HistoryItem[];
|
||||||
@ -52,22 +45,14 @@ const next = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelectPoint = (index: number) => {
|
|
||||||
selectedIndex.value = index;
|
|
||||||
updateHighlight();
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateHighlight = () => {
|
const updateHighlight = () => {
|
||||||
trendChartRef.value?.showTip(selectedIndex.value);
|
trendChartRef.value?.showTip(selectedIndex.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(() => props.history, () => {
|
watch(() => props.history, () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// Auto select last point if none selected or just appended
|
selectedIndex.value = props.history.length - 1;
|
||||||
if (props.history.length > 0 && (selectedIndex.value === -1 || selectedIndex.value === props.history.length - 2)) {
|
updateHighlight();
|
||||||
selectedIndex.value = props.history.length - 1;
|
|
||||||
updateHighlight();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
@ -77,7 +62,7 @@ watch(() => props.history, () => {
|
|||||||
<div class="history-page">
|
<div class="history-page">
|
||||||
<div class="charts-section">
|
<div class="charts-section">
|
||||||
<Panel class="trend-chart-panel">
|
<Panel class="trend-chart-panel">
|
||||||
<TrendChart ref="trendChartRef" :data="history" @select="onSelectPoint" />
|
<TrendChart ref="trendChartRef" :data="history" />
|
||||||
</Panel>
|
</Panel>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
@ -113,9 +98,13 @@ watch(() => props.history, () => {
|
|||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="nav-btns">
|
<div class="nav-btns">
|
||||||
<Button @long-press="prev" :long-press-interval="100" :disabled="selectedIndex <= 0"
|
<Button @long-press="prev" :long-press-interval="100" :disabled="selectedIndex <= 0"
|
||||||
class="nav-btn">◀</Button>
|
class="nav-btn">
|
||||||
|
<ArrowLeft />
|
||||||
|
</Button>
|
||||||
<Button @long-press="next" :long-press-interval="100"
|
<Button @long-press="next" :long-press-interval="100"
|
||||||
:disabled="selectedIndex >= history.length - 1" class="nav-btn">▶</Button>
|
:disabled="selectedIndex >= history.length - 1" class="nav-btn">
|
||||||
|
<ArrowRight />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button bg="red" @click="$emit('clear')" class="clear-btn">清除历史</Button>
|
<Button bg="red" @click="$emit('clear')" class="clear-btn">清除历史</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -0,0 +1,118 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as api from '../api.ts';
|
||||||
|
import type { AppState, HistoryItem } from '../types.ts';
|
||||||
|
import Button from '../components/Button.vue';
|
||||||
|
import Panel from '../components/Panel.vue';
|
||||||
|
import Machine from '../components/Machine.vue';
|
||||||
|
import ManualAdjust from '../components/ManualAdjust.vue';
|
||||||
|
import WaveformChart from '../components/WaveformChart.vue';
|
||||||
|
|
||||||
|
import { RefreshCcw } from 'lucide-vue-next';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
state: AppState;
|
||||||
|
historyData: HistoryItem[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'showManualAdjust'): void;
|
||||||
|
(e: 'changePage', page: 2): void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="page-home-content">
|
||||||
|
<div class="left">
|
||||||
|
<Panel style="flex: 1;">
|
||||||
|
<WaveformChart :data="state.last_measurement" />
|
||||||
|
<Button @click="api.measure()" style="position: absolute; top: 10px; right: 10px; width: 32px; height: 32px;">
|
||||||
|
<RefreshCcw />
|
||||||
|
</Button>
|
||||||
|
</Panel>
|
||||||
|
<Panel>
|
||||||
|
<Machine :dis="state.dis" :task="state.tasks.find(t => t.type == 'move')" />
|
||||||
|
</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">
|
||||||
|
<ManualAdjust @show-input="emit('showManualAdjust')" />
|
||||||
|
<div class="right">
|
||||||
|
<Button class="action-btn" @click="emit('changePage', 2)" bg="limegreen">开始实验</Button>
|
||||||
|
<Button class="action-btn" @click="api.stopAll()" bg="red">停止</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.page-home-content {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left,
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
flex: 3;
|
||||||
|
position: relative;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
28
src/pages/Measure.vue
Normal file
28
src/pages/Measure.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as api from '../api.ts';
|
||||||
|
import type { AppState } from '../types.ts';
|
||||||
|
import Button from '../components/Button.vue';
|
||||||
|
import Oscilloscope from '../components/Oscilloscope.vue';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
state: AppState;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="page-measure-content">
|
||||||
|
<Oscilloscope :data="state.last_measurement">
|
||||||
|
<template #controls>
|
||||||
|
<Button @click="api.measure()" bg="limegreen"
|
||||||
|
style="height: 52px; width: 100px; border-radius: 8px;">单次测量</Button>
|
||||||
|
</template>
|
||||||
|
</Oscilloscope>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.page-measure-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,15 +1,26 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
-webkit-touch-callout: none !important;
|
||||||
|
/* 禁用链接/图片长按菜单 */
|
||||||
|
-webkit-user-select: none !important;
|
||||||
|
/* 禁止文本选择(最关键) */
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
touch-action: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
font-family: 'PingFang SC', sans-serif;
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-title{
|
.panel-title {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
min-height: 500px;
|
||||||
|
transform-origin: 0 0;
|
||||||
}
|
}
|
||||||
61
src/types.ts
Normal file
61
src/types.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 测量数据类型
|
||||||
|
export interface MeasurementData {
|
||||||
|
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[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务类型
|
||||||
|
export type Task = {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 应用状态类型
|
||||||
|
export interface AppState {
|
||||||
|
connected: boolean;
|
||||||
|
dis: number;
|
||||||
|
phase: number;
|
||||||
|
freq: number;
|
||||||
|
p2p: number;
|
||||||
|
speed: number;
|
||||||
|
total_tasks: number;
|
||||||
|
tasks: Task[];
|
||||||
|
last_measurement: MeasurementData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 历史记录项类型
|
||||||
|
export interface HistoryItem extends MeasurementData {
|
||||||
|
currentDis: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量任务类型
|
||||||
|
export interface BatchTask {
|
||||||
|
cmd: 'move' | 'move_measure';
|
||||||
|
args: {
|
||||||
|
steps: number;
|
||||||
|
};
|
||||||
|
repeat: number;
|
||||||
|
}
|
||||||
@ -5,7 +5,9 @@ import { AlertTriangle, XOctagon, Info, CheckCircle2 } from 'lucide-vue-next';
|
|||||||
|
|
||||||
export function showMessage(message: string, type: 'info' | 'error' | 'success' | 'warning' = 'info', title?: string) {
|
export function showMessage(message: string, type: 'info' | 'error' | 'success' | 'warning' = 'info', title?: string) {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
document.body.appendChild(container);
|
const app = document.getElementById('app');
|
||||||
|
if (!app) return;
|
||||||
|
app.appendChild(container);
|
||||||
|
|
||||||
const destroy = () => {
|
const destroy = () => {
|
||||||
render(null, container);
|
render(null, container);
|
||||||
|
|||||||
@ -4,4 +4,14 @@ import vue from '@vitejs/plugin-vue'
|
|||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user