refactor(web): migrate to TypeScript + standard Vue3 structure
This commit is contained in:
@@ -8,6 +8,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
552
memora-web/package-lock.json
generated
552
memora-web/package-lock.json
generated
@@ -14,8 +14,12 @@
|
|||||||
"vue-router": "^4.3.0"
|
"vue-router": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^25.3.1",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@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": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
@@ -504,6 +508,316 @@
|
|||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@popperjs/core": {
|
||||||
"name": "@sxzz/popperjs-es",
|
"name": "@sxzz/popperjs-es",
|
||||||
"version": "2.11.8",
|
"version": "2.11.8",
|
||||||
@@ -887,6 +1201,16 @@
|
|||||||
"@types/lodash": "*"
|
"@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": {
|
"node_modules/@types/web-bluetooth": {
|
||||||
"version": "0.0.20",
|
"version": "0.0.20",
|
||||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
||||||
@@ -907,6 +1231,35 @@
|
|||||||
"vue": "^3.2.25"
|
"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": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.5.29",
|
"version": "3.5.29",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz",
|
||||||
@@ -963,6 +1316,22 @@
|
|||||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@vue/reactivity": {
|
||||||
"version": "3.5.29",
|
"version": "3.5.29",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz",
|
"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": {
|
"node_modules/async-validator": {
|
||||||
"version": "4.2.5",
|
"version": "4.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
||||||
@@ -1137,6 +1513,22 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -1170,6 +1562,17 @@
|
|||||||
"node": ">=0.4.0"
|
"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": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -1459,6 +1862,38 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.23",
|
"version": "4.17.23",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||||
@@ -1527,6 +1962,13 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
"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": "^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": {
|
"node_modules/normalize-wheel-es": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
|
||||||
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
|
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
|
||||||
"license": "BSD-3-Clause"
|
"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": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"license": "ISC"
|
"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": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
@@ -1591,6 +2061,20 @@
|
|||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/rollup": {
|
||||||
"version": "4.59.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||||
@@ -1636,6 +2120,27 @@
|
|||||||
"fsevents": "~2.3.2"
|
"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": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -1645,6 +2150,27 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.21",
|
"version": "5.4.21",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
"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": {
|
"node_modules/vue": {
|
||||||
"version": "3.5.29",
|
"version": "3.5.29",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz",
|
||||||
@@ -1740,6 +2273,23 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.5.0"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
"typecheck": "vue-tsc --noEmit",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
@@ -15,7 +16,11 @@
|
|||||||
"vue-router": "^4.3.0"
|
"vue-router": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^25.3.1",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import ElementPlus from 'element-plus'
|
|
||||||
import 'element-plus/dist/index.css'
|
|
||||||
|
|
||||||
export function registerPlugins(app) {
|
|
||||||
app.use(ElementPlus)
|
|
||||||
}
|
|
||||||
9
memora-web/src/app/plugins.ts
Normal file
9
memora-web/src/app/plugins.ts
Normal file
@@ -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)
|
||||||
|
}
|
||||||
43
memora-web/src/components/base/MetricCard.vue
Normal file
43
memora-web/src/components/base/MetricCard.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<div class="metric-card">
|
||||||
|
<div class="metric-label">{{ label }}</div>
|
||||||
|
<div class="metric-value">{{ value }}</div>
|
||||||
|
<div v-if="icon" class="metric-icon">
|
||||||
|
<span>{{ icon }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
label: string
|
||||||
|
value: number | string
|
||||||
|
icon?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.metric-card {
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eceef3;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 18px;
|
||||||
|
min-height: 86px;
|
||||||
|
}
|
||||||
|
.metric-label { color: #6b7280; font-size: 14px; }
|
||||||
|
.metric-value { margin-top: 8px; font-size: 34px; font-weight: 700; color: #111827; }
|
||||||
|
.metric-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 14px;
|
||||||
|
top: 14px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f3f4f6;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
memora-web/src/env.d.ts
vendored
Normal file
7
memora-web/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
@@ -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()}`
|
|
||||||
}
|
|
||||||
4
memora-web/src/services/api/index.ts
Normal file
4
memora-web/src/services/api/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './types'
|
||||||
|
export * from './stats'
|
||||||
|
export * from './words'
|
||||||
|
export * from './review'
|
||||||
16
memora-web/src/services/api/review.ts
Normal file
16
memora-web/src/services/api/review.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
7
memora-web/src/services/api/stats.ts
Normal file
7
memora-web/src/services/api/stats.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
35
memora-web/src/services/api/types.ts
Normal file
35
memora-web/src/services/api/types.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
17
memora-web/src/services/api/words.ts
Normal file
17
memora-web/src/services/api/words.ts
Normal file
@@ -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()}`
|
||||||
|
}
|
||||||
14
memora-web/src/services/http.ts
Normal file
14
memora-web/src/services/http.ts
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
18
memora-web/src/styles/index.scss
Normal file
18
memora-web/src/styles/index.scss
Normal file
@@ -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";
|
||||||
|
}
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
<div class="sub">欢迎回来,继续你的学习之旅</div>
|
<div class="sub">欢迎回来,继续你的学习之旅</div>
|
||||||
|
|
||||||
<el-row :gutter="16" class="cards">
|
<el-row :gutter="16" class="cards">
|
||||||
<el-col :span="6"><MetricCard label="今日复习数" :value="stats.today_reviewed ?? 0" /></el-col>
|
<el-col :span="6"><MetricCard label="今日复习数" :value="stats.today_reviewed ?? 0" icon="🎓" /></el-col>
|
||||||
<el-col :span="6"><MetricCard label="待复习数" :value="stats.need_review ?? 0" /></el-col>
|
<el-col :span="6"><MetricCard label="待复习数" :value="stats.need_review ?? 0" icon="📖" /></el-col>
|
||||||
<el-col :span="6"><MetricCard label="已掌握" :value="stats.mastered_words ?? 0" /></el-col>
|
<el-col :span="6"><MetricCard label="已掌握" :value="stats.mastered_words ?? 0" icon="🎯" /></el-col>
|
||||||
<el-col :span="6"><MetricCard label="总词汇" :value="stats.total_words ?? 0" /></el-col>
|
<el-col :span="6"><MetricCard label="总词汇" :value="stats.total_words ?? 0" icon="📚" /></el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
@@ -17,11 +17,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { defineComponent, h, onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { getStatistics } from '../services/api'
|
import { getStatistics } from '../services/api'
|
||||||
|
import MetricCard from '../components/base/MetricCard.vue'
|
||||||
|
|
||||||
const stats = ref({})
|
import type { Stats } from '../services/api'
|
||||||
|
|
||||||
|
const stats = ref<Stats>({
|
||||||
|
total_words: 0,
|
||||||
|
mastered_words: 0,
|
||||||
|
need_review: 0,
|
||||||
|
today_reviewed: 0
|
||||||
|
})
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
const res = await getStatistics()
|
const res = await getStatistics()
|
||||||
@@ -30,15 +38,7 @@ async function refresh() {
|
|||||||
|
|
||||||
onMounted(() => refresh().catch(console.error))
|
onMounted(() => refresh().catch(console.error))
|
||||||
|
|
||||||
const MetricCard = defineComponent({
|
|
||||||
props: { label: String, value: Number },
|
|
||||||
setup(props) {
|
|
||||||
return () => h('div', { class: 'metric-card' }, [
|
|
||||||
h('div', { class: 'metric-label' }, props.label),
|
|
||||||
h('div', { class: 'metric-value' }, String(props.value ?? 0))
|
|
||||||
])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -46,12 +46,4 @@ const MetricCard = defineComponent({
|
|||||||
.sub { margin-top: 4px; color: #6b7280; }
|
.sub { margin-top: 4px; color: #6b7280; }
|
||||||
.cards { margin-top: 20px; }
|
.cards { margin-top: 20px; }
|
||||||
.actions { margin-top: 18px; display: flex; gap: 10px; }
|
.actions { margin-top: 18px; display: flex; gap: 10px; }
|
||||||
.metric-card {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #eceef3;
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 18px;
|
|
||||||
}
|
|
||||||
.metric-label { color: #6b7280; font-size: 14px; }
|
|
||||||
.metric-value { margin-top: 8px; font-size: 34px; font-weight: 700; color: #111827; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -39,14 +39,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { addWord } from '../services/api'
|
import { addWord } from '../services/api'
|
||||||
|
|
||||||
const word = ref('')
|
const word = ref('')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
const saved = ref(null)
|
import type { Word } from '../services/api'
|
||||||
|
|
||||||
|
const saved = ref<Word | null>(null)
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
const w = word.value.trim()
|
const w = word.value.trim()
|
||||||
@@ -59,7 +61,7 @@ async function submit() {
|
|||||||
// 后端建议返回 { data: Word }
|
// 后端建议返回 { data: Word }
|
||||||
saved.value = res.data ?? res
|
saved.value = res.data ?? res
|
||||||
word.value = ''
|
word.value = ''
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
error.value = e?.response?.data?.error || e?.message || '请求失败'
|
error.value = e?.response?.data?.error || e?.message || '请求失败'
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
|||||||
@@ -22,10 +22,10 @@
|
|||||||
<el-button @click="play">播放读音(uk)</el-button>
|
<el-button @click="play">播放读音(uk)</el-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="mode === 'en2cn'">
|
<template v-else-if="mode === 'en2cn'">
|
||||||
<div class="q">{{ record.word.word }}</div>
|
<div class="q">{{ record.word?.word }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="q">{{ record.word.definition }}</div>
|
<div class="q">{{ record.word?.definition }}</div>
|
||||||
</template>
|
</template>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
@@ -46,15 +46,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { audioUrl, getReviewWords, submitReview } from '../services/api'
|
import { audioUrl, getReviewWords, submitReview } from '../services/api'
|
||||||
|
|
||||||
const mode = ref('spelling')
|
import type { MemoryRecord, ReviewMode, ReviewResult } from '../services/api'
|
||||||
const record = ref(null) // MemoryRecord (含 word)
|
|
||||||
|
const mode = ref<ReviewMode>('spelling')
|
||||||
|
const record = ref<MemoryRecord | null>(null) // MemoryRecord (含 word)
|
||||||
const answer = ref('')
|
const answer = ref('')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const result = ref(null)
|
const result = ref<ReviewResult | null>(null)
|
||||||
|
|
||||||
const modeHint = computed(() => {
|
const modeHint = computed(() => {
|
||||||
if (mode.value === 'spelling') return '听读音,拼写单词'
|
if (mode.value === 'spelling') return '听读音,拼写单词'
|
||||||
@@ -65,14 +67,14 @@ const modeHint = computed(() => {
|
|||||||
async function loadOne() {
|
async function loadOne() {
|
||||||
result.value = null
|
result.value = null
|
||||||
answer.value = ''
|
answer.value = ''
|
||||||
const res = await getReviewWords({ mode: mode.value, limit: 1 })
|
const res = await getReviewWords({ mode: mode.value, limit: 1 })
|
||||||
const arr = res.data ?? res
|
const arr = (res as any).data ?? (res as any)
|
||||||
record.value = Array.isArray(arr) && arr.length ? arr[0] : null
|
record.value = Array.isArray(arr) && arr.length ? (arr[0] as MemoryRecord) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function play() {
|
function play() {
|
||||||
if (!record.value?.word?.word) return
|
if (!record.value?.word?.word) return
|
||||||
const a = new Audio(audioUrl({ word: record.value.word.word, type: 'uk' }))
|
const a = new Audio(audioUrl(record.value.word.word, 'uk'))
|
||||||
a.play()
|
a.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +84,7 @@ async function submit() {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await submitReview({ recordId: record.value.id, answer: answer.value, mode: mode.value })
|
const res = await submitReview({ recordId: record.value.id, answer: answer.value, mode: mode.value })
|
||||||
result.value = res.data ?? res
|
result.value = (res as any).data ?? (res as any)
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,11 +18,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { getStatistics } from '../services/api'
|
import { getStatistics } from '../services/api'
|
||||||
|
|
||||||
const stats = ref({})
|
import type { Stats } from '../services/api'
|
||||||
|
|
||||||
|
const stats = ref<Stats>({
|
||||||
|
total_words: 0,
|
||||||
|
mastered_words: 0,
|
||||||
|
need_review: 0,
|
||||||
|
today_reviewed: 0
|
||||||
|
})
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
const res = await getStatistics()
|
const res = await getStatistics()
|
||||||
stats.value = res.data ?? res
|
stats.value = res.data ?? res
|
||||||
|
|||||||
@@ -10,12 +10,14 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { getWords } from '../services/api'
|
import { getWords } from '../services/api'
|
||||||
|
|
||||||
const kw = ref('')
|
const kw = ref('')
|
||||||
const rows = ref([])
|
import type { Word } from '../services/api'
|
||||||
|
|
||||||
|
const rows = ref<Word[]>([])
|
||||||
|
|
||||||
const filtered = computed(() => {
|
const filtered = computed(() => {
|
||||||
const q = kw.value.trim().toLowerCase()
|
const q = kw.value.trim().toLowerCase()
|
||||||
|
|||||||
23
memora-web/tsconfig.json
Normal file
23
memora-web/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"lib": ["ES2022", "DOM"],
|
||||||
|
"types": ["node"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
10
memora-web/tsconfig.node.json
Normal file
10
memora-web/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.js"]
|
||||||
|
}
|
||||||
@@ -3,6 +3,11 @@ import vue from '@vitejs/plugin-vue'
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': new URL('./src', import.meta.url).pathname
|
||||||
|
}
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
Reference in New Issue
Block a user