diff --git a/.env b/.env new file mode 100644 index 0000000..dc8edf8 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DEPLOY_PASSWORD=zjh94544549ok \ No newline at end of file diff --git a/bun.lock b/bun.lock index 4ddb7a8..3c16eef 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,7 @@ "dependencies": { "@types/qrcode": "^1.5.6", "echarts": "^6.0.0", + "katex": "^0.16.22", "lucide-vue-next": "^0.562.0", "qrcode": "^1.5.4", "vue": "^3.5.24", @@ -16,6 +17,7 @@ "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", "sass-embedded": "^1.97.2", + "ssh2-sftp-client": "^11.0.0", "typescript": "~5.9.3", "vite": "^7.2.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=="], + "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-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=="], "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=="], + "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=="], "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], @@ -237,6 +253,8 @@ "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=="], "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=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "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=="], "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=="], + "nan": ["nan@2.25.0", "", {}, "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g=="], + "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=="], @@ -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=="], + "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=="], + "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=="], "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=="], + "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=="], "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-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=="], + "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_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=="], "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=="], + "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=="], "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=="], "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=="], diff --git a/index.html b/index.html index 87638c0..1fc5941 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,8 @@ - - motor-ui + 自动声速测定仪 - 首页
diff --git a/package.json b/package.json index 1c3ef42..68b3e6e 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,14 @@ "scripts": { "dev": "vite", "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": { "@types/qrcode": "^1.5.6", "echarts": "^6.0.0", + "katex": "^0.16.22", "lucide-vue-next": "^0.562.0", "qrcode": "^1.5.4", "vue": "^3.5.24" @@ -20,6 +23,7 @@ "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", "sass-embedded": "^1.97.2", + "ssh2-sftp-client": "^11.0.0", "typescript": "~5.9.3", "vite": "^7.2.4", "vue-tsc": "^3.1.4" diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/scripts/deploy.mjs b/scripts/deploy.mjs new file mode 100644 index 0000000..f139d1d --- /dev/null +++ b/scripts/deploy.mjs @@ -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=""; 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 + } +} diff --git a/src/App.vue b/src/App.vue index 1ca6172..34277dd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,12 +1,10 @@ @@ -145,12 +152,16 @@ watch(pageIndex, (newVal, oldVal) => {
- + - + + +
+ +
+
+ 自动声速测定仪
-
- 自动声速测定仪
- +
- - - +
- +
+
+ +
@@ -239,6 +215,12 @@ watch(pageIndex, (newVal, oldVal) => { + @@ -262,22 +244,8 @@ header { align-items: center; color: white; 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 { 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 { position: absolute; left: 12px; @@ -314,7 +305,7 @@ header { gap: 4px; .btn { - width: 64px; + width: 56px; height: 28px; 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 { overflow: hidden; - height: calc(100vh - 36px); - width: 100vw; + height: calc(100% - 36px); + width: 100%; position: relative; } @@ -419,43 +368,6 @@ main { height: 160px; } -.page-home>.left, -.page-home>.right { - display: flex; - flex-direction: column; - padding: 10px; -} - -.page-home>.left { - flex: 3; - position: relative; - align-items: stretch; - gap: 8px; -} - -.page-home>.right { - flex: 2; - position: relative; - align-items: center; - - .label { - align-self: flex-start; - font-size: 24px; - } - - .label::after { - content: ':'; - margin-left: 4px; - } - - .data { - font-size: 32px; - font-weight: bold; - margin-bottom: 12px; - margin-top: -6px; - } -} - .manual-adjust-content { display: flex; gap: 16px; diff --git a/src/api.ts b/src/api.ts index 448df79..a22a61b 100644 --- a/src/api.ts +++ b/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() { return await fetch(`${API_BASE}/ping`); @@ -8,55 +12,36 @@ async function bee() { return await fetch(`${API_BASE}/bee`); } -const _state = { - // 连接状态 +const _state: AppState = { connected: false, - // 接收器距离 microsteps dis: 16000, - // 相位差 rad phase: 0, - // 频率 KHz freq: 0, - // 峰峰值 mV p2p: 0, - // 电机速度 Hz 1600 means 1mm/s or 1000 um/s speed: 1200, - // 任务 total_tasks: 0, - tasks: [] as ({ - id: string; - type: 'move'; - steps: number; - remaining_steps: number; - status: 'running' | 'pending' | 'queued'; - created_at: number; - } | { - id: string; - type: 'measure'; - status: 'running' | 'pending' | 'queued'; - created_at: number; - })[], - last_measurement:{}as { - "ts": number, - "idn": null, - "points_mode": 'NORM', - "n": number, - "tscale": number, - "toffs": number, - "f0_hz": number, - "amp1_pp_adc": number, - "amp2_pp_adc": number, - "phi1_rad": number, - "phi2_rad": number, - "dphi_rad": number, - "dphi_deg": number, - "dt_s": number, - "wave1": number[], - "wave2": number[], + tasks: [], + last_measurement: { + ts: 0, + idn: null, + points_mode: 'NORM', + n: 0, + tscale: 0, + toffs: 0, + f0_hz: 0, + amp1_pp_adc: 0, + amp2_pp_adc: 0, + phi1_rad: 0, + phi2_rad: 0, + dphi_rad: 0, + dphi_deg: 0, + dt_s: 0, + wave1: [], + wave2: [], } } -const listeners: ((state: typeof _state) => void)[] = []; +const listeners: ((state: AppState) => void)[] = []; function notifyListeners() { for (const listener of listeners) { @@ -65,7 +50,7 @@ function notifyListeners() { } function initSSE() { - let reconnectTimer: NodeJS.Timeout; + let reconnectTimer: any; const connect = () => { const eventSource = new EventSource(`${API_BASE}/events`); @@ -114,7 +99,7 @@ function getState() { return _state; } -async function onStateChange(callback: (state: typeof _state) => void) { +async function onStateChange(callback: (state: AppState) => void) { listeners.push(callback); // 立即回调当前状态 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`, { method: 'POST', headers: { diff --git a/src/assets/_-.woff2 b/src/assets/_-.woff2 deleted file mode 100644 index 626f5d9..0000000 Binary files a/src/assets/_-.woff2 and /dev/null differ diff --git a/src/assets/apparatus.svg b/src/assets/apparatus.svg deleted file mode 100644 index c46e29d..0000000 --- a/src/assets/apparatus.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - S1 发射 - - - - - - - - - - - - - - S2 接收 - - - L=Moving - - \ No newline at end of file diff --git a/src/assets/stylesheet.css b/src/assets/stylesheet.css deleted file mode 100644 index 34c122d..0000000 --- a/src/assets/stylesheet.css +++ /dev/null @@ -1,8 +0,0 @@ -@font-face { - font-family: 'PingFang SC'; - src: url('_-.woff2') format('woff2'); - font-weight: normal; - font-style: normal; - font-display: swap; -} - diff --git a/src/assets/vue.svg b/src/assets/vue.svg deleted file mode 100644 index 770e9d3..0000000 --- a/src/assets/vue.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/仪器.png b/src/assets/仪器.png deleted file mode 100644 index 5be0e37..0000000 Binary files a/src/assets/仪器.png and /dev/null differ diff --git a/src/assets/仪器臂.png b/src/assets/仪器臂.png deleted file mode 100644 index efcb53f..0000000 Binary files a/src/assets/仪器臂.png and /dev/null differ diff --git a/src/components/Formula.vue b/src/components/Formula.vue new file mode 100644 index 0000000..b059405 --- /dev/null +++ b/src/components/Formula.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/components/Machine.vue b/src/components/Machine.vue index b392909..c4cbb2b 100644 --- a/src/components/Machine.vue +++ b/src/components/Machine.vue @@ -84,6 +84,7 @@ img { width: 100%; text-align: center; font-weight: bold; + font-size: 13px; } .machine { diff --git a/src/components/ManualAdjust.vue b/src/components/ManualAdjust.vue new file mode 100644 index 0000000..6a26df8 --- /dev/null +++ b/src/components/ManualAdjust.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/components/Numpad.vue b/src/components/Numpad.vue index c11559d..85f4598 100644 --- a/src/components/Numpad.vue +++ b/src/components/Numpad.vue @@ -29,7 +29,7 @@ const inputNumber = (val: string, current: string) => {
{{ label }}
- +
{{ modelValue || '\u00A0' }}
{{ unit }}
@@ -86,10 +86,12 @@ const inputNumber = (val: string, current: string) => { .target-input { width: 100%; height: 40px; + display: flex; + align-items: center; + justify-content: center; font-size: 24px; - text-align: center; border: 2px solid #808080; - background-color: white; + background-color: white !important; padding: 4px 8px; box-sizing: border-box; border-radius: 4px; diff --git a/src/components/TrendChart.vue b/src/components/TrendChart.vue index 76cf3ad..e4f37a4 100644 --- a/src/components/TrendChart.vue +++ b/src/components/TrendChart.vue @@ -19,29 +19,41 @@ const props = defineProps<{ data: HistoryItem[]; }>(); -const emit = defineEmits<{ - (e: 'select', index: number): void; -}>(); - const chart = ref(null); let chartInstance: echarts.ECharts | null = null; +let chartIndexToRawIndex: number[] = []; +let rawIndexToChartIndex: number[] = []; const initChart = () => { if (chart.value && !chartInstance) { chartInstance = echarts.init(chart.value); - chartInstance.on('click', (params) => { - if (params.dataIndex !== undefined) { - emit('select', params.dataIndex); - } - }); } }; const updateChart = () => { if (!chartInstance) return; - const dataPhase = props.data.map(item => [(item.currentDis / 1600).toFixed(3), item.dphi_deg]); + // 处理相位差数据,检测跳变并断开连线 + const dataPhase: ([(string | number), number] | null)[] = []; 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 = { animation: false, @@ -112,6 +124,7 @@ const updateChart = () => { smooth: 0.3, symbol: 'circle', symbolSize: 4, + connectNulls: false, itemStyle: { color: '#c23531' }, lineStyle: { color: '#c23531', width: 2 } }, @@ -134,10 +147,12 @@ const updateChart = () => { const showTip = (index: number) => { if (!chartInstance) return; + const chartDataIndex = rawIndexToChartIndex[index]; + if (chartDataIndex === undefined) return; chartInstance.dispatchAction({ type: 'showTip', seriesIndex: 0, - dataIndex: index + dataIndex: chartDataIndex }); }; diff --git a/src/components/Window.vue b/src/components/Window.vue index 2c220af..8799315 100644 --- a/src/components/Window.vue +++ b/src/components/Window.vue @@ -3,13 +3,16 @@ class="window" :style="{ left: position.x + 'px', top: position.y + 'px' }" v-if="modelValue" + ref="windowEl" >
{{ title }}
- +
@@ -18,8 +21,9 @@ + + + + diff --git a/src/pages/History.vue b/src/pages/History.vue index b08888c..1544c87 100644 --- a/src/pages/History.vue +++ b/src/pages/History.vue @@ -1,19 +1,12 @@ + + + + diff --git a/src/pages/Measure.vue b/src/pages/Measure.vue new file mode 100644 index 0000000..0a024bf --- /dev/null +++ b/src/pages/Measure.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/src/style.css b/src/style.css index 0d64f3c..d6072d3 100644 --- a/src/style.css +++ b/src/style.css @@ -1,15 +1,26 @@ body { margin: 0; + height: 100vh; + height: 100dvh; + width: 100vw; } * { + -webkit-touch-callout: none !important; + /* 禁用链接/图片长按菜单 */ + -webkit-user-select: none !important; + /* 禁止文本选择(最关键) */ box-sizing: border-box; - touch-action: none; user-select: none; -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{ - font-size: 20px; +.panel-title { + font-size: 20px; +} + +#app { + min-height: 500px; + transform-origin: 0 0; } \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..c17d93d --- /dev/null +++ b/src/types.ts @@ -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; +} diff --git a/src/utils/message.ts b/src/utils/message.ts index 1a1bb0b..0debda7 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -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) { const container = document.createElement('div'); - document.body.appendChild(container); + const app = document.getElementById('app'); + if (!app) return; + app.appendChild(container); const destroy = () => { render(null, container); @@ -62,13 +64,13 @@ export function showMessage(message: string, type: 'info' | 'error' | 'success' } }, [ h(Icon, { size: 48, color }), - h('div', { - style: { - fontSize: '18px', + h('div', { + style: { + fontSize: '18px', color: '#333', textAlign: 'center', whiteSpace: 'pre-wrap' - } + } }, message), h(Button, { onClick: onCloneWindow, diff --git a/vite.config.ts b/vite.config.ts index bbcf80c..e8a4a0e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,4 +4,14 @@ import vue from '@vitejs/plugin-vue' // https://vite.dev/config/ export default defineConfig({ plugins: [vue()], + server: { + host: '0.0.0.0', + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + } })