From e2a9ebc7b7d74a81b547a81b4338403fa8d4720f Mon Sep 17 00:00:00 2001 From: wsy182 <2392948297@qq.com> Date: Thu, 26 Feb 2026 12:29:25 +0800 Subject: [PATCH] refactor(web): migrate to TypeScript + standard Vue3 structure --- memora-web/index.html | 2 +- memora-web/package-lock.json | 552 +++++++++++++++++- memora-web/package.json | 7 +- memora-web/src/app/{index.js => index.ts} | 0 memora-web/src/app/plugins.js | 6 - memora-web/src/app/plugins.ts | 9 + memora-web/src/app/{routes.js => routes.ts} | 0 memora-web/src/components/base/MetricCard.vue | 43 ++ memora-web/src/env.d.ts | 7 + memora-web/src/{main.js => main.ts} | 0 memora-web/src/router/{index.js => index.ts} | 0 memora-web/src/services/api.js | 36 -- memora-web/src/services/api/index.ts | 4 + memora-web/src/services/api/review.ts | 16 + memora-web/src/services/api/stats.ts | 7 + memora-web/src/services/api/types.ts | 35 ++ memora-web/src/services/api/words.ts | 17 + memora-web/src/services/http.ts | 14 + memora-web/src/styles/index.scss | 18 + memora-web/src/views/Home.vue | 40 +- memora-web/src/views/Memory.vue | 8 +- memora-web/src/views/Review.vue | 24 +- memora-web/src/views/Settings.vue | 2 +- memora-web/src/views/Statistics.vue | 11 +- memora-web/src/views/Words.vue | 6 +- memora-web/tsconfig.json | 23 + memora-web/tsconfig.node.json | 10 + memora-web/vite.config.js | 5 + 28 files changed, 814 insertions(+), 88 deletions(-) rename memora-web/src/app/{index.js => index.ts} (100%) delete mode 100644 memora-web/src/app/plugins.js create mode 100644 memora-web/src/app/plugins.ts rename memora-web/src/app/{routes.js => routes.ts} (100%) create mode 100644 memora-web/src/components/base/MetricCard.vue create mode 100644 memora-web/src/env.d.ts rename memora-web/src/{main.js => main.ts} (100%) rename memora-web/src/router/{index.js => index.ts} (100%) delete mode 100644 memora-web/src/services/api.js create mode 100644 memora-web/src/services/api/index.ts create mode 100644 memora-web/src/services/api/review.ts create mode 100644 memora-web/src/services/api/stats.ts create mode 100644 memora-web/src/services/api/types.ts create mode 100644 memora-web/src/services/api/words.ts create mode 100644 memora-web/src/services/http.ts create mode 100644 memora-web/src/styles/index.scss create mode 100644 memora-web/tsconfig.json create mode 100644 memora-web/tsconfig.node.json diff --git a/memora-web/index.html b/memora-web/index.html index 363126e..e6f29fa 100644 --- a/memora-web/index.html +++ b/memora-web/index.html @@ -8,6 +8,6 @@
- + diff --git a/memora-web/package-lock.json b/memora-web/package-lock.json index f985401..5c51af7 100644 --- a/memora-web/package-lock.json +++ b/memora-web/package-lock.json @@ -14,8 +14,12 @@ "vue-router": "^4.3.0" }, "devDependencies": { + "@types/node": "^25.3.1", "@vitejs/plugin-vue": "^5.0.4", - "vite": "^5.1.6" + "sass": "^1.97.3", + "typescript": "^5.9.3", + "vite": "^5.1.6", + "vue-tsc": "^3.2.5" } }, "node_modules/@babel/helper-string-parser": { @@ -504,6 +508,316 @@ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@popperjs/core": { "name": "@sxzz/popperjs-es", "version": "2.11.8", @@ -887,6 +1201,16 @@ "@types/lodash": "*" } }, + "node_modules/@types/node": { + "version": "25.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.1.tgz", + "integrity": "sha512-hj9YIJimBCipHVfHKRMnvmHg+wfhKc0o4mTtXh9pKBjC8TLJzz0nzGmLi5UJsYAUgSvXFHgb0V2oY10DUFtImw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", @@ -907,6 +1231,35 @@ "vue": "^3.2.25" } }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, "node_modules/@vue/compiler-core": { "version": "3.5.29", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz", @@ -963,6 +1316,22 @@ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", "license": "MIT" }, + "node_modules/@vue/language-core": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.5.tgz", + "integrity": "sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + } + }, "node_modules/@vue/reactivity": { "version": "3.5.29", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz", @@ -1101,6 +1470,13 @@ } } }, + "node_modules/alien-signals": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", + "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", @@ -1137,6 +1513,22 @@ "node": ">= 0.4" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1170,6 +1562,17 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1459,6 +1862,38 @@ "node": ">= 0.4" } }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/lodash": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", @@ -1527,6 +1962,13 @@ "node": ">= 0.6" } }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1545,18 +1987,46 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/normalize-wheel-es": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", "license": "BSD-3-Clause" }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -1591,6 +2061,20 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", @@ -1636,6 +2120,27 @@ "fsevents": "~2.3.2" } }, + "node_modules/sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1645,6 +2150,27 @@ "node": ">=0.10.0" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", @@ -1705,6 +2231,13 @@ } } }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vue": { "version": "3.5.29", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", @@ -1740,6 +2273,23 @@ "peerDependencies": { "vue": "^3.5.0" } + }, + "node_modules/vue-tsc": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.5.tgz", + "integrity": "sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.2.5" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } } } } diff --git a/memora-web/package.json b/memora-web/package.json index 3bd08d5..4483580 100644 --- a/memora-web/package.json +++ b/memora-web/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite", + "typecheck": "vue-tsc --noEmit", "build": "vite build", "preview": "vite preview" }, @@ -15,7 +16,11 @@ "vue-router": "^4.3.0" }, "devDependencies": { + "@types/node": "^25.3.1", "@vitejs/plugin-vue": "^5.0.4", - "vite": "^5.1.6" + "sass": "^1.97.3", + "typescript": "^5.9.3", + "vite": "^5.1.6", + "vue-tsc": "^3.2.5" } } diff --git a/memora-web/src/app/index.js b/memora-web/src/app/index.ts similarity index 100% rename from memora-web/src/app/index.js rename to memora-web/src/app/index.ts diff --git a/memora-web/src/app/plugins.js b/memora-web/src/app/plugins.js deleted file mode 100644 index 1ec2ff1..0000000 --- a/memora-web/src/app/plugins.js +++ /dev/null @@ -1,6 +0,0 @@ -import ElementPlus from 'element-plus' -import 'element-plus/dist/index.css' - -export function registerPlugins(app) { - app.use(ElementPlus) -} diff --git a/memora-web/src/app/plugins.ts b/memora-web/src/app/plugins.ts new file mode 100644 index 0000000..9bce280 --- /dev/null +++ b/memora-web/src/app/plugins.ts @@ -0,0 +1,9 @@ +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import '../styles/index.scss' + +import type { App } from 'vue' + +export function registerPlugins(app: App) { + app.use(ElementPlus) +} diff --git a/memora-web/src/app/routes.js b/memora-web/src/app/routes.ts similarity index 100% rename from memora-web/src/app/routes.js rename to memora-web/src/app/routes.ts diff --git a/memora-web/src/components/base/MetricCard.vue b/memora-web/src/components/base/MetricCard.vue new file mode 100644 index 0000000..52c235d --- /dev/null +++ b/memora-web/src/components/base/MetricCard.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/memora-web/src/env.d.ts b/memora-web/src/env.d.ts new file mode 100644 index 0000000..323c78a --- /dev/null +++ b/memora-web/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/memora-web/src/main.js b/memora-web/src/main.ts similarity index 100% rename from memora-web/src/main.js rename to memora-web/src/main.ts diff --git a/memora-web/src/router/index.js b/memora-web/src/router/index.ts similarity index 100% rename from memora-web/src/router/index.js rename to memora-web/src/router/index.ts diff --git a/memora-web/src/services/api.js b/memora-web/src/services/api.js deleted file mode 100644 index 5a7e5cc..0000000 --- a/memora-web/src/services/api.js +++ /dev/null @@ -1,36 +0,0 @@ -import axios from 'axios' - -export const api = axios.create({ - baseURL: '/api', - timeout: 15000 -}) - -export async function addWord(word) { - const res = await api.post('/words', { word }) - return res.data -} - -export async function getWords({ limit = 20, offset = 0 } = {}) { - const res = await api.get('/words', { params: { limit, offset } }) - return res.data -} - -export async function getReviewWords({ mode = 'spelling', limit = 10 } = {}) { - const res = await api.get('/review', { params: { mode, limit } }) - return res.data -} - -export async function submitReview({ recordId, answer, mode }) { - const res = await api.post('/review', { record_id: recordId, answer, mode }) - return res.data -} - -export async function getStatistics() { - const res = await api.get('/stats') - return res.data -} - -export function audioUrl({ word, type = 'uk' }) { - const q = new URLSearchParams({ word, type }) - return `/api/audio?${q.toString()}` -} diff --git a/memora-web/src/services/api/index.ts b/memora-web/src/services/api/index.ts new file mode 100644 index 0000000..d1d29eb --- /dev/null +++ b/memora-web/src/services/api/index.ts @@ -0,0 +1,4 @@ +export * from './types' +export * from './stats' +export * from './words' +export * from './review' diff --git a/memora-web/src/services/api/review.ts b/memora-web/src/services/api/review.ts new file mode 100644 index 0000000..65a3c21 --- /dev/null +++ b/memora-web/src/services/api/review.ts @@ -0,0 +1,16 @@ +import { http } from '../http' +import type { MemoryRecord, ReviewMode, ReviewResult } from './types' + +export async function getReviewWords(params: { mode?: ReviewMode; limit?: number } = {}) { + const res = await http.get<{ data: MemoryRecord[] }>('/review', { params }) + return res.data +} + +export async function submitReview(payload: { recordId: number; answer: string; mode: ReviewMode }) { + const res = await http.post<{ data: ReviewResult }>('/review', { + record_id: payload.recordId, + answer: payload.answer, + mode: payload.mode + }) + return res.data +} diff --git a/memora-web/src/services/api/stats.ts b/memora-web/src/services/api/stats.ts new file mode 100644 index 0000000..1177641 --- /dev/null +++ b/memora-web/src/services/api/stats.ts @@ -0,0 +1,7 @@ +import { http } from '../http' +import type { Stats } from './types' + +export async function getStatistics() { + const res = await http.get<{ data: Stats }>('/stats') + return res.data +} diff --git a/memora-web/src/services/api/types.ts b/memora-web/src/services/api/types.ts new file mode 100644 index 0000000..37af4f7 --- /dev/null +++ b/memora-web/src/services/api/types.ts @@ -0,0 +1,35 @@ +export interface Word { + id: number + word: string + phonetic_uk?: string + phonetic_us?: string + audio_uk?: string + audio_us?: string + part_of_speech?: string + definition?: string +} + +export interface MemoryRecord { + id: number + word_id: number + correct_count: number + total_count: number + mastery_level: number + word?: Word +} + +export interface Stats { + total_words: number + mastered_words: number + need_review: number + today_reviewed: number +} + +export type ReviewMode = 'spelling' | 'en2cn' | 'cn2en' + +export interface ReviewResult { + word: Word + correct: boolean + answer: string + correct_ans?: string +} diff --git a/memora-web/src/services/api/words.ts b/memora-web/src/services/api/words.ts new file mode 100644 index 0000000..fd5bb89 --- /dev/null +++ b/memora-web/src/services/api/words.ts @@ -0,0 +1,17 @@ +import { http } from '../http' +import type { Word } from './types' + +export async function addWord(word: string) { + const res = await http.post<{ data: Word }>('/words', { word }) + return res.data +} + +export async function getWords(params: { limit?: number; offset?: number } = {}) { + const res = await http.get<{ data: Word[]; total: number }>('/words', { params }) + return res.data +} + +export function audioUrl(word: string, type: 'uk' | 'us' = 'uk') { + const q = new URLSearchParams({ word, type }) + return `/api/audio?${q.toString()}` +} diff --git a/memora-web/src/services/http.ts b/memora-web/src/services/http.ts new file mode 100644 index 0000000..6e146ec --- /dev/null +++ b/memora-web/src/services/http.ts @@ -0,0 +1,14 @@ +import axios from 'axios' + +export const http = axios.create({ + baseURL: '/api', + timeout: 15000 +}) + +http.interceptors.response.use( + (res) => res, + (err) => { + // 统一错误抛出 + return Promise.reject(err) + } +) diff --git a/memora-web/src/styles/index.scss b/memora-web/src/styles/index.scss new file mode 100644 index 0000000..cbfe7ab --- /dev/null +++ b/memora-web/src/styles/index.scss @@ -0,0 +1,18 @@ +:root { + --memora-bg: #f4f5fb; + --memora-border: #eceef3; + --memora-text: #111827; + --memora-sub: #6b7280; + --memora-primary: #2f7d32; +} + +html, body { + height: 100%; +} + +body { + margin: 0; + background: var(--memora-bg); + color: var(--memora-text); + font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; +} diff --git a/memora-web/src/views/Home.vue b/memora-web/src/views/Home.vue index f6fe914..0fc36f0 100644 --- a/memora-web/src/views/Home.vue +++ b/memora-web/src/views/Home.vue @@ -4,10 +4,10 @@
欢迎回来,继续你的学习之旅
- - - - + + + +
@@ -17,11 +17,19 @@
- diff --git a/memora-web/src/views/Memory.vue b/memora-web/src/views/Memory.vue index 2df5865..b4befc0 100644 --- a/memora-web/src/views/Memory.vue +++ b/memora-web/src/views/Memory.vue @@ -39,14 +39,16 @@ - diff --git a/memora-web/src/views/Statistics.vue b/memora-web/src/views/Statistics.vue index 3a43839..f26858f 100644 --- a/memora-web/src/views/Statistics.vue +++ b/memora-web/src/views/Statistics.vue @@ -18,11 +18,18 @@ -