From b6f9a4c912dc4bbf08c648f44da81a1787cb27f9 Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Fri, 14 Aug 2026 17:31:26 +0800 Subject: [PATCH] 20260814173126 --- code/fms/fms-vue/jsconfig.json | 16 + code/fms/fms-vue/package.json | 6 + code/fms/fms-vue/pnpm-lock.yaml | 501 +++++++++++++++++- code/fms/fms-vue/src/App.vue | 83 ++- code/fms/fms-vue/src/components/ui/Button.vue | 26 - .../src/components/ui/ColorThemePicker.vue | 143 ++++- .../fms-vue/src/components/ui/ThemeToggle.vue | 4 +- .../src/components/ui/button/button.vue | 22 + .../src/components/ui/button/index.scss | 102 ++++ code/fms/fms-vue/src/composables/useTheme.js | 96 ---- code/fms/fms-vue/src/main.js | 5 +- code/fms/fms-vue/src/style.css | 384 -------------- code/fms/fms-vue/src/styles/index.css | 86 +++ code/fms/fms-vue/src/theme/color.js | 196 +++++++ code/fms/fms-vue/src/theme/useTheme.js | 72 +++ code/fms/fms-vue/vite.config.js | 11 + code/test/soybean-admin | 1 + 17 files changed, 1210 insertions(+), 544 deletions(-) create mode 100644 code/fms/fms-vue/jsconfig.json delete mode 100644 code/fms/fms-vue/src/components/ui/Button.vue create mode 100644 code/fms/fms-vue/src/components/ui/button/button.vue create mode 100644 code/fms/fms-vue/src/components/ui/button/index.scss delete mode 100644 code/fms/fms-vue/src/composables/useTheme.js delete mode 100644 code/fms/fms-vue/src/style.css create mode 100644 code/fms/fms-vue/src/styles/index.css create mode 100644 code/fms/fms-vue/src/theme/color.js create mode 100644 code/fms/fms-vue/src/theme/useTheme.js create mode 160000 code/test/soybean-admin diff --git a/code/fms/fms-vue/jsconfig.json b/code/fms/fms-vue/jsconfig.json new file mode 100644 index 00000000..e025759b --- /dev/null +++ b/code/fms/fms-vue/jsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + }, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "lib": ["esnext", "dom", "dom.iterable"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "build"] +} diff --git a/code/fms/fms-vue/package.json b/code/fms/fms-vue/package.json index cd5897f9..a5335cc0 100644 --- a/code/fms/fms-vue/package.json +++ b/code/fms/fms-vue/package.json @@ -15,14 +15,20 @@ }, "dependencies": { "@fontsource-variable/noto-sans-sc": "^5.3.0", + "axios": "^1.19.0", + "nprogress": "^0.2.0", + "pinia": "^4.0.3", + "pinia-plugin-persistedstate": "^4.7.1", "stk-table-vue": "^1.1.0", "vue": "3.5.41", "vue-router": "^5.2.0" }, "devDependencies": { + "@types/nprogress": "^0.2.3", "@vitejs/plugin-vue": "^6.0.8", "oxfmt": "^0.63.0", "oxlint": "^1.78.0", + "sass": "^1.102.0", "vite": "^8.2.0" } } diff --git a/code/fms/fms-vue/pnpm-lock.yaml b/code/fms/fms-vue/pnpm-lock.yaml index ce3385a6..3ed49e77 100644 --- a/code/fms/fms-vue/pnpm-lock.yaml +++ b/code/fms/fms-vue/pnpm-lock.yaml @@ -11,6 +11,18 @@ importers: '@fontsource-variable/noto-sans-sc': specifier: ^5.3.0 version: 5.3.0 + axios: + specifier: ^1.19.0 + version: 1.19.0 + nprogress: + specifier: ^0.2.0 + version: 0.2.0 + pinia: + specifier: ^4.0.3 + version: 4.0.3(@vue/devtools-api@8.2.1)(vue@3.5.41) + pinia-plugin-persistedstate: + specifier: ^4.7.1 + version: 4.7.1(pinia@4.0.3(@vue/devtools-api@8.2.1)(vue@3.5.41)) stk-table-vue: specifier: ^1.1.0 version: 1.1.0 @@ -19,20 +31,26 @@ importers: version: 3.5.41 vue-router: specifier: ^5.2.0 - version: 5.2.0(@vue/compiler-sfc@3.5.41)(rolldown@1.2.4)(vite@8.2.1(yaml@2.9.0))(vue@3.5.41) + version: 5.2.0(@vue/compiler-sfc@3.5.41)(pinia@4.0.3(@vue/devtools-api@8.2.1)(vue@3.5.41))(rolldown@1.2.4)(vite@8.2.1(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41) devDependencies: + '@types/nprogress': + specifier: ^0.2.3 + version: 0.2.3 '@vitejs/plugin-vue': specifier: ^6.0.8 - version: 6.0.8(vite@8.2.1(yaml@2.9.0))(vue@3.5.41) + version: 6.0.8(vite@8.2.1(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41) oxfmt: specifier: ^0.63.0 version: 0.63.0 oxlint: specifier: ^1.78.0 version: 1.78.0 + sass: + specifier: ^1.102.0 + version: 1.102.0 vite: specifier: ^8.2.0 - version: 8.2.1(yaml@2.9.0) + version: 8.2.1(sass@1.102.0)(yaml@2.9.0) packages: @@ -340,6 +358,88 @@ packages: cpu: [x64] os: [win32] + '@parcel/watcher-android-arm64@2.6.0': + resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.6.0': + resolution: {integrity: sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.6.0': + resolution: {integrity: sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.6.0': + resolution: {integrity: sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.6.0': + resolution: {integrity: sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.6.0': + resolution: {integrity: sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.6.0': + resolution: {integrity: sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.6.0': + resolution: {integrity: sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.6.0': + resolution: {integrity: sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.6.0': + resolution: {integrity: sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.6.0': + resolution: {integrity: sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-x64@2.6.0': + resolution: {integrity: sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.6.0': + resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} + engines: {node: '>= 10.0.0'} + '@rolldown/binding-android-arm64@1.2.4': resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -436,6 +536,9 @@ packages: '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/nprogress@0.2.3': + resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} + '@vitejs/plugin-vue@6.0.8': resolution: {integrity: sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==} engines: {node: ^20.19.0 || >=22.12.0} @@ -493,6 +596,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ast-kit@2.2.0: resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} engines: {node: '>=20.19.0'} @@ -501,13 +608,27 @@ packages: resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==} engines: {node: '>=20.19.0'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.19.0: + resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==} + birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -517,14 +638,50 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -540,14 +697,69 @@ packages: picomatch: optional: true + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + immutable@5.1.9: + resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -643,9 +855,24 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -654,9 +881,15 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + nostics@1.2.0: resolution: {integrity: sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==} + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + oxfmt@0.63.0: resolution: {integrity: sha512-kgdDwv35wvVf6554U2Ab8Jnd0zTM+TsEQWwaB70RAjK3gICFAFGO+2Hd3Be27GMoXj3XRL9IKSNRVl7KBQL6iw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -696,6 +929,30 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} + pinia-plugin-persistedstate@4.7.1: + resolution: {integrity: sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==} + peerDependencies: + '@nuxt/kit': '>=3.0.0' + '@pinia/nuxt': '>=0.10.0' + pinia: '>=3.0.0' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@pinia/nuxt': + optional: true + pinia: + optional: true + + pinia@4.0.3: + resolution: {integrity: sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA==} + peerDependencies: + '@vue/devtools-api': ^8.1.5 + typescript: '>=5.6.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -706,6 +963,10 @@ packages: resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -718,6 +979,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + sass@1.102.0: + resolution: {integrity: sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==} + engines: {node: '>=20.19.0'} + hasBin: true + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -1027,6 +1293,63 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.78.0': optional: true + '@parcel/watcher-android-arm64@2.6.0': + optional: true + + '@parcel/watcher-darwin-arm64@2.6.0': + optional: true + + '@parcel/watcher-darwin-x64@2.6.0': + optional: true + + '@parcel/watcher-freebsd-x64@2.6.0': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-arm-musl@2.6.0': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.6.0': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-x64-musl@2.6.0': + optional: true + + '@parcel/watcher-win32-arm64@2.6.0': + optional: true + + '@parcel/watcher-win32-x64@2.6.0': + optional: true + + '@parcel/watcher@2.6.0': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.5 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.6.0 + '@parcel/watcher-darwin-arm64': 2.6.0 + '@parcel/watcher-darwin-x64': 2.6.0 + '@parcel/watcher-freebsd-x64': 2.6.0 + '@parcel/watcher-linux-arm-glibc': 2.6.0 + '@parcel/watcher-linux-arm-musl': 2.6.0 + '@parcel/watcher-linux-arm64-glibc': 2.6.0 + '@parcel/watcher-linux-arm64-musl': 2.6.0 + '@parcel/watcher-linux-x64-glibc': 2.6.0 + '@parcel/watcher-linux-x64-musl': 2.6.0 + '@parcel/watcher-win32-arm64': 2.6.0 + '@parcel/watcher-win32-x64': 2.6.0 + optional: true + '@rolldown/binding-android-arm64@1.2.4': optional: true @@ -1073,10 +1396,12 @@ snapshots: '@types/jsesc@2.5.1': {} - '@vitejs/plugin-vue@6.0.8(vite@8.2.1(yaml@2.9.0))(vue@3.5.41)': + '@types/nprogress@0.2.3': {} + + '@vitejs/plugin-vue@6.0.8(vite@8.2.1(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41)': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.2.1(yaml@2.9.0) + vite: 8.2.1(sass@1.102.0)(yaml@2.9.0) vue: 3.5.41 '@vue-macros/common@3.1.4(vue@3.5.41)': @@ -1158,6 +1483,12 @@ snapshots: acorn@8.18.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ast-kit@2.2.0: dependencies: '@babel/parser': 7.29.8 @@ -1169,22 +1500,72 @@ snapshots: '@babel/types': 7.29.8 ast-kit: 2.2.0 + asynckit@0.4.0: {} + + axios@1.19.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + birpc@2.9.0: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + chokidar@5.0.0: dependencies: readdirp: 5.1.1 + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + confbox@0.1.8: {} confbox@0.2.4: {} csstype@3.2.3: {} + debug@4.4.3: + dependencies: + ms: 2.1.3 + + defu@6.1.7: {} + + delayed-stream@1.0.0: {} + detect-libc@2.1.2: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + entities@7.0.1: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + estree-walker@2.0.2: {} exsolve@1.1.1: {} @@ -1193,11 +1574,70 @@ snapshots: optionalDependencies: picomatch: 4.0.5 + follow-redirects@1.16.0: {} + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hookable@5.5.3: {} + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + immutable@5.1.9: {} + + is-extglob@2.1.1: + optional: true + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + optional: true + jsesc@3.1.0: {} json5@2.2.3: {} @@ -1265,6 +1705,14 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mlly@1.8.2: dependencies: acorn: 8.18.0 @@ -1272,12 +1720,19 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.4 + ms@2.1.3: {} + muggle-string@0.4.1: {} nanoid@3.3.18: {} + node-addon-api@7.1.1: + optional: true + nostics@1.2.0: {} + nprogress@0.2.0: {} + oxfmt@0.63.0: dependencies: tinypool: 2.1.0 @@ -1332,6 +1787,18 @@ snapshots: picomatch@4.0.5: {} + pinia-plugin-persistedstate@4.7.1(pinia@4.0.3(@vue/devtools-api@8.2.1)(vue@3.5.41)): + dependencies: + defu: 6.1.7 + optionalDependencies: + pinia: 4.0.3(@vue/devtools-api@8.2.1)(vue@3.5.41) + + pinia@4.0.3(@vue/devtools-api@8.2.1)(vue@3.5.41): + dependencies: + '@vue/devtools-api': 8.2.1 + nostics: 1.2.0 + vue: 3.5.41 + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -1350,6 +1817,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + proxy-from-env@2.1.0: {} + quansync@0.2.11: {} readdirp@5.1.1: {} @@ -1374,6 +1843,14 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.4 '@rolldown/binding-win32-x64-msvc': 1.2.4 + sass@1.102.0: + dependencies: + chokidar: 5.0.0 + immutable: 5.1.9 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.6.0 + scule@1.3.0: {} source-map-js@1.2.1: {} @@ -1394,16 +1871,16 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.5 - unplugin@3.3.0(rolldown@1.2.4)(vite@8.2.1(yaml@2.9.0)): + unplugin@3.3.0(rolldown@1.2.4)(vite@8.2.1(sass@1.102.0)(yaml@2.9.0)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.5 webpack-virtual-modules: 0.6.2 optionalDependencies: rolldown: 1.2.4 - vite: 8.2.1(yaml@2.9.0) + vite: 8.2.1(sass@1.102.0)(yaml@2.9.0) - vite@8.2.1(yaml@2.9.0): + vite@8.2.1(sass@1.102.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -1412,9 +1889,10 @@ snapshots: tinyglobby: 0.2.17 optionalDependencies: fsevents: 2.3.3 + sass: 1.102.0 yaml: 2.9.0 - vue-router@5.2.0(@vue/compiler-sfc@3.5.41)(rolldown@1.2.4)(vite@8.2.1(yaml@2.9.0))(vue@3.5.41): + vue-router@5.2.0(@vue/compiler-sfc@3.5.41)(pinia@4.0.3(@vue/devtools-api@8.2.1)(vue@3.5.41))(rolldown@1.2.4)(vite@8.2.1(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41): dependencies: '@babel/generator': 8.0.0 '@vue-macros/common': 3.1.4(vue@3.5.41) @@ -1431,13 +1909,14 @@ snapshots: picomatch: 4.0.5 scule: 1.3.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(rolldown@1.2.4)(vite@8.2.1(yaml@2.9.0)) + unplugin: 3.3.0(rolldown@1.2.4)(vite@8.2.1(sass@1.102.0)(yaml@2.9.0)) unplugin-utils: 0.3.2 vue: 3.5.41 yaml: 2.9.0 optionalDependencies: '@vue/compiler-sfc': 3.5.41 - vite: 8.2.1(yaml@2.9.0) + pinia: 4.0.3(@vue/devtools-api@8.2.1)(vue@3.5.41) + vite: 8.2.1(sass@1.102.0)(yaml@2.9.0) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' diff --git a/code/fms/fms-vue/src/App.vue b/code/fms/fms-vue/src/App.vue index 35b15dd3..fa533fd5 100644 --- a/code/fms/fms-vue/src/App.vue +++ b/code/fms/fms-vue/src/App.vue @@ -1,5 +1,5 @@ @@ -25,14 +25,83 @@ import ThemeToggle from './components/ui/ThemeToggle.vue'
- - - - - + + + + + +
- + diff --git a/code/fms/fms-vue/src/components/ui/Button.vue b/code/fms/fms-vue/src/components/ui/Button.vue deleted file mode 100644 index 47392500..00000000 --- a/code/fms/fms-vue/src/components/ui/Button.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/code/fms/fms-vue/src/components/ui/ColorThemePicker.vue b/code/fms/fms-vue/src/components/ui/ColorThemePicker.vue index 23d8d94a..7a2ce6b5 100644 --- a/code/fms/fms-vue/src/components/ui/ColorThemePicker.vue +++ b/code/fms/fms-vue/src/components/ui/ColorThemePicker.vue @@ -1,24 +1,133 @@ + + diff --git a/code/fms/fms-vue/src/components/ui/ThemeToggle.vue b/code/fms/fms-vue/src/components/ui/ThemeToggle.vue index 89425f94..f40424ca 100644 --- a/code/fms/fms-vue/src/components/ui/ThemeToggle.vue +++ b/code/fms/fms-vue/src/components/ui/ThemeToggle.vue @@ -1,6 +1,6 @@ diff --git a/code/fms/fms-vue/src/components/ui/button/button.vue b/code/fms/fms-vue/src/components/ui/button/button.vue new file mode 100644 index 00000000..ec6d6dc3 --- /dev/null +++ b/code/fms/fms-vue/src/components/ui/button/button.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/code/fms/fms-vue/src/components/ui/button/index.scss b/code/fms/fms-vue/src/components/ui/button/index.scss new file mode 100644 index 00000000..d1f10152 --- /dev/null +++ b/code/fms/fms-vue/src/components/ui/button/index.scss @@ -0,0 +1,102 @@ +.button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + white-space: nowrap; + border: 1px solid transparent; + border-radius: var(--radius); + font-weight: 500; + line-height: 1; + height: 2.5rem; + padding: 0 1rem; + font-size: 0.875rem; + outline: none; + transition: + color 120ms ease, + background-color 120ms ease, + border-color 120ms ease, + transform 80ms ease; +} + +.button:active:not(:disabled) { + transform: scale(0.985); +} + +.button-default { + color: var(--primary-foreground); + background: var(--primary); +} + +.button-default:hover { + background: var(--primary-hover); +} + +.button-default:active { + background: var(--primary-active); +} + +.button-secondary { + color: var(--secondary-foreground); + background: var(--secondary); +} + +.button-secondary:hover, +.button-ghost:hover { + background: var(--accent); + color: var(--accent-foreground); +} + +.button-secondary:active, +.button-ghost:active { + background: color-mix(in srgb, var(--accent) 88%, var(--foreground)); + color: var(--accent-foreground); +} + +.button-outline { + color: var(--foreground); + border-color: var(--input); + background: var(--background); +} + +.button-outline:hover { + color: var(--accent-foreground); + background: var(--accent); +} + +.button-outline:active { + color: var(--accent-foreground); + background: color-mix(in srgb, var(--accent) 88%, var(--foreground)); +} + +.button-ghost { + color: var(--foreground); + background: transparent; +} + +.button-destructive { + color: var(--destructive-foreground); + background: var(--destructive); +} + +.button-destructive:hover { + background: color-mix(in srgb, var(--destructive) 90%, var(--background)); +} + +.button-destructive:active { + background: color-mix(in srgb, var(--destructive) 80%, var(--background)); +} + +.button-link { + color: var(--primary); + background: transparent; + text-underline-offset: 4px; +} + +.button-link:hover { + text-decoration: underline; +} + +.button-link:active { + color: var(--primary-active); +} diff --git a/code/fms/fms-vue/src/composables/useTheme.js b/code/fms/fms-vue/src/composables/useTheme.js deleted file mode 100644 index d3f62589..00000000 --- a/code/fms/fms-vue/src/composables/useTheme.js +++ /dev/null @@ -1,96 +0,0 @@ -import { ref } from 'vue' - -const MODE_STORAGE_KEY = 'fms-theme' -const COLOR_STORAGE_KEY = 'fms-color-theme' - -export const colorThemes = Object.freeze([ - { value: 'neutral', label: '中性', color: 'oklch(0.205 0 0)' }, - { value: 'blue', label: '蓝色', color: 'oklch(0.546 0.245 262.881)' }, - { value: 'green', label: '绿色', color: 'oklch(0.527 0.154 150.069)' }, - { value: 'violet', label: '紫色', color: 'oklch(0.496 0.265 301.924)' }, - { value: 'orange', label: '橙色', color: 'oklch(0.57 0.2 49.5)' }, - { value: 'red', label: '红色', color: 'oklch(0.506 0.232 25.7)' }, - { value: 'pink', label: '粉色', color: 'oklch(0.523 0.214 349.4)' }, - { value: 'teal', label: '青色', color: 'oklch(0.563 0.144 180)' }, -]) - -const theme = ref('light') -const colorTheme = ref('neutral') -let initialized = false - -function isColorTheme(value) { - return colorThemes.some((item) => item.value === value) -} - -function applyTheme() { - if (typeof document === 'undefined') return - - document.documentElement.dataset.theme = theme.value - document.documentElement.dataset.colorTheme = colorTheme.value - document.documentElement.style.colorScheme = theme.value -} - -function readStorage(key) { - try { - return window.localStorage.getItem(key) - } catch { - return null - } -} - -function writeStorage(key, value) { - try { - window.localStorage.setItem(key, value) - } catch { - // The selected theme still applies when storage is unavailable. - } -} - -function initializeTheme() { - if (initialized || typeof window === 'undefined') return - - initialized = true - const storedTheme = readStorage(MODE_STORAGE_KEY) - const storedColorTheme = readStorage(COLOR_STORAGE_KEY) - - theme.value = storedTheme === 'dark' ? 'dark' : 'light' - colorTheme.value = isColorTheme(storedColorTheme) ? storedColorTheme : 'neutral' - applyTheme() -} - -function setTheme(value) { - theme.value = value === 'dark' ? 'dark' : 'light' - applyTheme() - - if (typeof window !== 'undefined') { - writeStorage(MODE_STORAGE_KEY, theme.value) - } -} - -function setColorTheme(value) { - colorTheme.value = isColorTheme(value) ? value : 'neutral' - applyTheme() - - if (typeof window !== 'undefined') { - writeStorage(COLOR_STORAGE_KEY, colorTheme.value) - } -} - -function toggleTheme() { - setTheme(theme.value === 'dark' ? 'light' : 'dark') -} - -initializeTheme() - -export function useTheme() { - initializeTheme() - - return { - theme, - colorTheme, - colorThemes, - setTheme, - setColorTheme, - toggleTheme, - } -} diff --git a/code/fms/fms-vue/src/main.js b/code/fms/fms-vue/src/main.js index 49edf9ed..e6536bc9 100644 --- a/code/fms/fms-vue/src/main.js +++ b/code/fms/fms-vue/src/main.js @@ -1,8 +1,11 @@ import { createApp } from 'vue' import '@fontsource-variable/noto-sans-sc/wght.css' -import './style.css' +import './styles/index.css' import App from './App.vue' import router from './router' import 'stk-table-vue/lib/style.css' +import { initTheme } from './theme/useTheme' + +initTheme() createApp(App).use(router).mount('#app') diff --git a/code/fms/fms-vue/src/style.css b/code/fms/fms-vue/src/style.css deleted file mode 100644 index f0730762..00000000 --- a/code/fms/fms-vue/src/style.css +++ /dev/null @@ -1,384 +0,0 @@ -:root { - --font-sans: 'Noto Sans SC Variable', 'PingFang SC', 'Microsoft YaHei', sans-serif; - - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --destructive-foreground: oklch(0.985 0 0); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --radius: 0.625rem; - - font-family: var(--font-sans); - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - line-height: 1.5; - color: var(--foreground); - background: var(--background); -} - -:root[data-theme='dark'] { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --destructive-foreground: oklch(0.985 0 0); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); -} - -:root[data-color-theme='blue'] { - --primary: oklch(0.546 0.245 262.881); - --primary-foreground: oklch(0.985 0 0); - --ring: oklch(0.546 0.245 262.881); -} - -:root[data-color-theme='green'] { - --primary: oklch(0.527 0.154 150.069); - --primary-foreground: oklch(0.985 0 0); - --ring: oklch(0.527 0.154 150.069); -} - -:root[data-color-theme='violet'] { - --primary: oklch(0.496 0.265 301.924); - --primary-foreground: oklch(0.985 0 0); - --ring: oklch(0.496 0.265 301.924); -} - -:root[data-color-theme='orange'] { - --primary: oklch(0.57 0.2 49.5); - --primary-foreground: oklch(0.985 0 0); - --ring: oklch(0.57 0.2 49.5); -} - -:root[data-color-theme='red'] { - --primary: oklch(0.506 0.232 25.7); - --primary-foreground: oklch(0.985 0 0); - --ring: oklch(0.506 0.232 25.7); -} - -:root[data-color-theme='pink'] { - --primary: oklch(0.523 0.214 349.4); - --primary-foreground: oklch(0.985 0 0); - --ring: oklch(0.523 0.214 349.4); -} - -:root[data-color-theme='teal'] { - --primary: oklch(0.563 0.144 180); - --primary-foreground: oklch(0.985 0 0); - --ring: oklch(0.563 0.144 180); -} - -:root[data-theme='dark'][data-color-theme='blue'] { - --primary: oklch(0.623 0.214 259.815); - --ring: oklch(0.623 0.214 259.815); -} - -:root[data-theme='dark'][data-color-theme='green'] { - --primary: oklch(0.596 0.145 163.225); - --ring: oklch(0.596 0.145 163.225); -} - -:root[data-theme='dark'][data-color-theme='violet'] { - --primary: oklch(0.558 0.238 301.358); - --ring: oklch(0.558 0.238 301.358); -} - -:root[data-theme='dark'][data-color-theme='orange'] { - --primary: oklch(0.643 0.215 41.5); - --ring: oklch(0.643 0.215 41.5); -} - -:root[data-theme='dark'][data-color-theme='red'] { - --primary: oklch(0.577 0.245 27.325); - --ring: oklch(0.577 0.245 27.325); -} - -:root[data-theme='dark'][data-color-theme='pink'] { - --primary: oklch(0.592 0.225 349.5); - --ring: oklch(0.592 0.225 349.5); -} - -:root[data-theme='dark'][data-color-theme='teal'] { - --primary: oklch(0.627 0.124 180); - --ring: oklch(0.627 0.124 180); -} - -body { - margin: 0; - font-family: inherit; - min-width: 320px; - color: var(--foreground); - background: var(--background); - transition: - color 160ms ease, - background-color 160ms ease; -} - -button, -input, -select, -textarea { - font: inherit; -} - -button { - cursor: pointer; -} - -button:disabled { - cursor: not-allowed; - opacity: 0.5; -} - -.button { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - white-space: nowrap; - border: 1px solid transparent; - border-radius: var(--radius); - font-weight: 500; - line-height: 1; - outline: none; - transition: - color 120ms ease, - background-color 120ms ease, - border-color 120ms ease, - box-shadow 120ms ease; -} - -.button:focus-visible { - box-shadow: - 0 0 0 2px var(--background), - 0 0 0 4px color-mix(in oklch, var(--ring) 50%, transparent); -} - -.button-default { - color: var(--primary-foreground); - background: var(--primary); -} - -.button-default:hover { - background: color-mix(in oklch, var(--primary) 90%, var(--background)); -} - -.button-secondary { - color: var(--secondary-foreground); - background: var(--secondary); -} - -.button-secondary:hover, -.button-ghost:hover { - background: var(--accent); - color: var(--accent-foreground); -} - -.button-outline { - color: var(--foreground); - border-color: var(--input); - background: var(--background); -} - -.button-outline:hover { - color: var(--accent-foreground); - background: var(--accent); -} - -.button-ghost { - color: var(--foreground); - background: transparent; -} - -.button-destructive { - color: var(--destructive-foreground); - background: var(--destructive); -} - -.button-destructive:hover { - background: color-mix(in oklch, var(--destructive) 90%, var(--background)); -} - -.button-link { - color: var(--primary); - background: transparent; - text-underline-offset: 4px; -} - -.button-link:hover { - text-decoration: underline; -} - -.button-size-sm { - height: 2.25rem; - padding: 0 0.75rem; - font-size: 0.875rem; -} - -.button-size-default { - height: 2.5rem; - padding: 0 1rem; - font-size: 0.875rem; -} - -.button-size-lg { - height: 2.75rem; - padding: 0 2rem; -} - -.button-size-icon { - width: 2.5rem; - height: 2.5rem; - padding: 0; -} - -.app-shell { - width: min(100% - 2rem, 72rem); - min-height: 100vh; - margin: 0 auto; - box-sizing: border-box; -} - -.app-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - padding: 2rem 0; - border-bottom: 1px solid var(--border); -} - -.theme-controls { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.color-theme-picker { - display: grid; - grid-template-columns: repeat(4, 1.75rem); - gap: 0.25rem; - padding: 0.25rem; - border: 1px solid var(--border); - border-radius: var(--radius); - background: var(--card); -} - -.color-swatch { - display: inline-flex; - width: 1.75rem; - height: 1.75rem; - align-items: center; - justify-content: center; - padding: 0; - border: 0; - border-radius: calc(var(--radius) - 0.25rem); - background: transparent; - outline: none; -} - -.color-swatch:hover { - background: var(--accent); -} - -.color-swatch:focus-visible, -.color-swatch-active { - box-shadow: - inset 0 0 0 2px var(--background), - inset 0 0 0 3px var(--ring); -} - -.color-swatch-dot { - width: 1rem; - height: 1rem; - border-radius: 50%; - background: var(--swatch-color); -} - -.eyebrow { - margin: 0 0 0.375rem; - color: var(--muted-foreground); - font-size: 0.75rem; - font-weight: 600; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -h1, -h2 { - margin: 0; -} - -h1 { - font-size: clamp(1.5rem, 4vw, 2rem); -} - -h2 { - font-size: 1.25rem; -} - -.showcase-section { - padding: 3rem 0; -} - -.section-heading { - max-width: 36rem; -} - -.section-heading > p:last-child { - margin: 0.75rem 0 0; - color: var(--muted-foreground); -} - -.button-row { - display: flex; - flex-wrap: wrap; - gap: 0.75rem; - margin-top: 1.5rem; -} - -@media (max-width: 640px) { - .app-header { - align-items: flex-start; - flex-direction: column; - padding: 1.25rem 0; - } - - .theme-controls { - width: 100%; - justify-content: space-between; - } - - .showcase-section { - padding: 2rem 0; - } -} diff --git a/code/fms/fms-vue/src/styles/index.css b/code/fms/fms-vue/src/styles/index.css new file mode 100644 index 00000000..fcb98118 --- /dev/null +++ b/code/fms/fms-vue/src/styles/index.css @@ -0,0 +1,86 @@ +:root { + --font-sans: 'Noto Sans SC Variable', 'PingFang SC', 'Microsoft YaHei', sans-serif; + + --background: #ffffff; + --foreground: #09090b; + --card: #ffffff; + --card-foreground: #09090b; + /* 主色系列(--primary / --primary-hover / --primary-active / --primary-foreground / --primary-border / --ring) + 由 theme/useTheme.js 在运行时根据主题色计算并写入 :root.style,此处不重复定义以避免双真相源。 */ + --secondary: #f4f4f5; + --secondary-foreground: #18181b; + --muted-foreground: #71717a; + --accent: #f4f4f5; + --accent-foreground: #18181b; + --destructive: #ef4444; + --destructive-foreground: #fafafa; + --border: #e4e4e7; + --input: #e4e4e7; + --radius: 0.625rem; + + font-family: var(--font-sans); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + line-height: 1.5; + color: var(--foreground); + background: var(--background); +} + +:root[data-theme='dark'] { + --background: #09090b; + --foreground: #fafafa; + --card: #18181b; + --card-foreground: #fafafa; + --secondary: #27272a; + --secondary-foreground: #fafafa; + --muted-foreground: #a1a1aa; + --accent: #27272a; + --accent-foreground: #fafafa; + --destructive: #7f1d1d; + --destructive-foreground: #fafafa; + --border: #ffffff1a; + --input: #ffffff26; +} + +body { + margin: 0; + font-family: inherit; + min-width: 320px; + color: var(--foreground); + background: var(--background); + transition: + color 160ms ease, + background-color 160ms ease; +} + +button, +input, +select, +textarea { + font: inherit; +} + +button { + cursor: pointer; +} + +button:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +h1, +h2 { + margin: 0; +} + +h1 { + font-size: clamp(1.5rem, 4vw, 2rem); +} + +h2 { + font-size: 1.25rem; +} diff --git a/code/fms/fms-vue/src/theme/color.js b/code/fms/fms-vue/src/theme/color.js new file mode 100644 index 00000000..59867965 --- /dev/null +++ b/code/fms/fms-vue/src/theme/color.js @@ -0,0 +1,196 @@ +// 约定:对外一律 hex('#rrggbb'),内部算法用 RGB 数组 [r, g, b]。 + +// ---------- 配置 ---------- +export const STORAGE_KEYS = { + mode: 'fms-theme', + color: 'fms-theme-color', +} + +export const DEFAULT_MODE = 'light' +export const DEFAULT_COLOR = '#18181b' + +export const PRESETS = [ + { name: 'Shadcn 中性', value: '#18181b' }, + { name: '蓝色', value: '#2563eb' }, + { name: '靛蓝', value: '#4f46e5' }, + { name: '紫色', value: '#7c3aed' }, + { name: '青色', value: '#0d9488' }, + { name: '绿色', value: '#16a34a' }, + { name: '橙色', value: '#ea580c' }, + { name: '红色', value: '#dc2626' }, + { name: '粉色', value: '#db2777' }, +] + +export const ALGORITHM = { + minContrast: 3, // 主色与背景最小对比度 + hoverDelta: 0.06, // hover 明度偏移 + activeDelta: 0.14, // active 明度偏移 + borderMixRatio: 0.55, // 边框与背景混合比例 +} + +// 背景参照色:用于主色对比度兜底(浅色=纯白、深色=zinc-900,与原始实现保持一致)。 +export const SURFACES = { + light: '#ffffff', + dark: '#18181b', +} + +// 主色上文字的前景候选(白 / 近黑),取对比度更大者。 +export const FOREGROUND_CANDIDATES = ['#ffffff', '#18181b'] + +// ---------- 颜色数学 ---------- +export function normalizeHex(value) { + const color = String(value ?? '').trim().toLowerCase() + + if (/^#[0-9a-f]{6}$/.test(color)) return color + if (/^#[0-9a-f]{3}$/.test(color)) { + const [red, green, blue] = color.slice(1) + return `#${red}${red}${green}${green}${blue}${blue}` + } + + return null +} + +export function hexToRgb(hex) { + return [ + Number.parseInt(hex.slice(1, 3), 16), + Number.parseInt(hex.slice(3, 5), 16), + Number.parseInt(hex.slice(5, 7), 16), + ] +} + +export function rgbToHex([red, green, blue]) { + return `#${[red, green, blue] + .map((channel) => Math.round(channel).toString(16).padStart(2, '0')) + .join('')}` +} + +export function rgbToHsl([red, green, blue]) { + const r = red / 255 + const g = green / 255 + const b = blue / 255 + const max = Math.max(r, g, b) + const min = Math.min(r, g, b) + const lightness = (max + min) / 2 + + if (max === min) return { h: 0, s: 0, l: lightness } + + const delta = max - min + const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min) + let hue + + if (max === r) hue = (g - b) / delta + (g < b ? 6 : 0) + else if (max === g) hue = (b - r) / delta + 2 + else hue = (r - g) / delta + 4 + + return { h: hue / 6, s: saturation, l: lightness } +} + +function hueToRgb(p, q, t) { + let hue = t + if (hue < 0) hue += 1 + if (hue > 1) hue -= 1 + if (hue < 1 / 6) return p + (q - p) * 6 * hue + if (hue < 1 / 2) return q + if (hue < 2 / 3) return p + (q - p) * (2 / 3 - hue) * 6 + return p +} + +export function hslToRgb({ h, s, l }) { + if (s === 0) { + const value = Math.round(l * 255) + return [value, value, value] + } + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s + const p = 2 * l - q + + return [ + Math.round(hueToRgb(p, q, h + 1 / 3) * 255), + Math.round(hueToRgb(p, q, h) * 255), + Math.round(hueToRgb(p, q, h - 1 / 3) * 255), + ] +} + +export function adjustLightness(rgb, delta) { + const hsl = rgbToHsl(rgb) + return hslToRgb({ ...hsl, l: Math.min(1, Math.max(0, hsl.l + delta)) }) +} + +export function mixRgb(firstRgb, secondRgb, secondRatio) { + const firstRatio = 1 - secondRatio + return firstRgb.map((channel, index) => + Math.round(channel * firstRatio + secondRgb[index] * secondRatio), + ) +} + +export function getRelativeLuminance(rgb) { + const [red, green, blue] = rgb.map((channel) => { + const value = channel / 255 + return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4 + }) + + return red * 0.2126 + green * 0.7152 + blue * 0.0722 +} + +export function getContrastRatio(firstRgb, secondRgb) { + const firstLuminance = getRelativeLuminance(firstRgb) + const secondLuminance = getRelativeLuminance(secondRgb) + const lighter = Math.max(firstLuminance, secondLuminance) + const darker = Math.min(firstLuminance, secondLuminance) + + return (lighter + 0.05) / (darker + 0.05) +} + +// ---------- 主色派生 ---------- +function getForegroundRgb(themeRgb) { + const [light, dark] = FOREGROUND_CANDIDATES.map(hexToRgb) + const lightContrast = getContrastRatio(themeRgb, light) + const darkContrast = getContrastRatio(themeRgb, dark) + + return lightContrast >= darkContrast ? light : dark +} + +function ensurePrimaryContrast(rgb, backgroundRgb) { + if (getContrastRatio(rgb, backgroundRgb) >= ALGORITHM.minContrast) return rgb + + const sourceLightness = rgbToHsl(rgb).l + + for (let step = 1; step <= 100; step += 1) { + const delta = step / 100 + const candidates = [adjustLightness(rgb, -delta), adjustLightness(rgb, delta)] + const candidate = candidates.find( + (item) => getContrastRatio(item, backgroundRgb) >= ALGORITHM.minContrast, + ) + + if (candidate) return candidate + } + + return sourceLightness >= 0.5 ? adjustLightness(rgb, -0.5) : adjustLightness(rgb, 0.5) +} + +export function createPrimaryTokens(color, mode) { + const sourceRgb = hexToRgb(color) + const backgroundRgb = hexToRgb(SURFACES[mode]) + const primaryRgb = ensurePrimaryContrast(sourceRgb, backgroundRgb) + const primaryLightness = rgbToHsl(primaryRgb).l + const direction = + mode === 'dark' ? (primaryLightness > 0.85 ? -1 : 1) : primaryLightness < 0.15 ? 1 : -1 + const hoverRgb = ensurePrimaryContrast( + adjustLightness(primaryRgb, direction * ALGORITHM.hoverDelta), + backgroundRgb, + ) + const activeRgb = ensurePrimaryContrast( + adjustLightness(primaryRgb, direction * ALGORITHM.activeDelta), + backgroundRgb, + ) + const primaryBorderRgb = mixRgb(primaryRgb, backgroundRgb, ALGORITHM.borderMixRatio) + + return { + primary: rgbToHex(primaryRgb), + 'primary-foreground': rgbToHex(getForegroundRgb(primaryRgb)), + 'primary-hover': rgbToHex(hoverRgb), + 'primary-active': rgbToHex(activeRgb), + 'primary-border': rgbToHex(primaryBorderRgb), + ring: rgbToHex(primaryRgb), + } +} diff --git a/code/fms/fms-vue/src/theme/useTheme.js b/code/fms/fms-vue/src/theme/useTheme.js new file mode 100644 index 00000000..d458ca9b --- /dev/null +++ b/code/fms/fms-vue/src/theme/useTheme.js @@ -0,0 +1,72 @@ +import { ref } from 'vue' +import { + createPrimaryTokens, + DEFAULT_COLOR, + DEFAULT_MODE, + normalizeHex, + STORAGE_KEYS, +} from './color' + +const theme = ref(DEFAULT_MODE) +const themeColor = ref(DEFAULT_COLOR) + +function applyTheme() { + const root = document.documentElement + const tokens = createPrimaryTokens(themeColor.value, theme.value) + + root.dataset.theme = theme.value + root.style.colorScheme = theme.value + + for (const [name, value] of Object.entries(tokens)) { + root.style.setProperty(`--${name}`, value) + } +} + +function readStorage(key) { + try { + return window.localStorage.getItem(key) + } catch { + return null + } +} + +function writeStorage(key, value) { + try { + window.localStorage.setItem(key, value) + } catch { + // storage 不可用时主题仍生效。 + } +} + +export function initTheme() { + const storedMode = readStorage(STORAGE_KEYS.mode) + const storedColor = normalizeHex(readStorage(STORAGE_KEYS.color)) + + theme.value = storedMode === 'dark' ? 'dark' : 'light' + themeColor.value = storedColor ?? DEFAULT_COLOR + applyTheme() +} + +function setTheme(value) { + theme.value = value === 'dark' ? 'dark' : 'light' + applyTheme() + writeStorage(STORAGE_KEYS.mode, theme.value) +} + +function setThemeColor(value) { + const color = normalizeHex(value) + if (!color) return false + + themeColor.value = color + applyTheme() + writeStorage(STORAGE_KEYS.color, color) + return true +} + +function toggleTheme() { + setTheme(theme.value === 'dark' ? 'light' : 'dark') +} + +export function useTheme() { + return { theme, themeColor, setTheme, setThemeColor, toggleTheme } +} diff --git a/code/fms/fms-vue/vite.config.js b/code/fms/fms-vue/vite.config.js index c40aa3c3..3e817721 100644 --- a/code/fms/fms-vue/vite.config.js +++ b/code/fms/fms-vue/vite.config.js @@ -1,6 +1,17 @@ +import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + server: { + host: true, + port: 5082, + strictPort: true, + }, plugins: [vue()], }) diff --git a/code/test/soybean-admin b/code/test/soybean-admin new file mode 160000 index 00000000..12854ad5 --- /dev/null +++ b/code/test/soybean-admin @@ -0,0 +1 @@ +Subproject commit 12854ad5f871477503666e758dc7d7edc4f0f277