diff --git a/global.d.ts b/global.d.ts index c0f30ec..01d308b 100644 --- a/global.d.ts +++ b/global.d.ts @@ -40,6 +40,19 @@ declare global { params: [message: string] return: void } + // 主题事件 + [IPC_EVENTS.THEME_MODE_UPDATED]: { + params: [isDark: boolean] + return: void + } + [IPC_EVENTS.SET_THEME_MODE]: { + params: [theme: ThemeMode] + return: Promise // 返回 isDark + } + [IPC_EVENTS.GET_THEME_MODE]: { + params: [] + return: Promise + } } type TabId = string diff --git a/package-lock.json b/package-lock.json index cd8d099..93d3309 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "vue-i18n": "^11.1.9", "vue-markdown-render": "^2.3.0", "vue-router": "^4.5.1", + "zustand": "^5.0.12", "zx": "^8.8.5" }, "devDependencies": { @@ -511,7 +512,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -994,7 +994,6 @@ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -1014,7 +1013,6 @@ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -1025,7 +1023,6 @@ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -1050,7 +1047,6 @@ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -1094,7 +1090,6 @@ "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", @@ -1110,7 +1105,6 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=12.22" }, @@ -1125,8 +1119,7 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@iconify-json/material-symbols": { "version": "1.2.63", @@ -2817,6 +2810,7 @@ "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/lodash": "*" } @@ -2833,6 +2827,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -2995,8 +2990,7 @@ "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.5", @@ -3061,6 +3055,7 @@ "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz", "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.30", @@ -3150,6 +3145,7 @@ "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-14.2.1.tgz", "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.1", @@ -3212,6 +3208,7 @@ "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3225,7 +3222,6 @@ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -3248,6 +3244,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4426,6 +4423,7 @@ "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -4488,8 +4486,7 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -4632,8 +4629,7 @@ "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/defaults": { "version": "1.0.4", @@ -4775,6 +4771,7 @@ "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", @@ -4847,7 +4844,6 @@ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -5152,7 +5148,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -5173,7 +5168,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -5189,7 +5183,6 @@ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", - "peer": true, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -5200,7 +5193,6 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4.0.0" } @@ -5696,7 +5688,6 @@ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -5743,7 +5734,6 @@ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -5762,7 +5752,6 @@ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -5776,7 +5765,6 @@ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -5790,7 +5778,6 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -5811,7 +5798,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5930,8 +5916,7 @@ "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-safe-stringify": { "version": "2.1.1", @@ -5982,7 +5967,6 @@ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -6042,7 +6026,6 @@ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -6060,7 +6043,6 @@ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -6265,7 +6247,6 @@ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -6298,7 +6279,6 @@ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -6315,7 +6295,6 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -6411,8 +6390,7 @@ "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", @@ -6744,7 +6722,6 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -6925,8 +6902,7 @@ "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -6983,7 +6959,6 @@ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -7293,7 +7268,6 @@ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -7308,13 +7282,15 @@ "version": "4.17.23", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash-es": { "version": "4.17.23", "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash-unified": { "version": "1.0.3", @@ -7345,8 +7321,7 @@ "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -7741,8 +7716,7 @@ "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/next-tick": { "version": "1.1.0", @@ -8528,7 +8502,6 @@ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -8631,7 +8604,6 @@ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -8694,7 +8666,6 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -8933,7 +8904,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -8951,7 +8921,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -8962,7 +8931,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -8973,6 +8941,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9251,7 +9220,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -9879,7 +9847,6 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -10003,7 +9970,8 @@ "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.2.2.tgz", "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -10025,7 +9993,6 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -10066,7 +10033,6 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -10081,7 +10047,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -10094,8 +10059,7 @@ "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/timers-ext": { "version": "0.1.8", @@ -10185,6 +10149,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10339,7 +10304,6 @@ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -10367,6 +10331,7 @@ "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10637,6 +10602,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -10726,7 +10692,8 @@ "resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz", "integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", @@ -10767,6 +10734,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10779,6 +10747,7 @@ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.30.tgz", "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.30", "@vue/compiler-sfc": "3.5.30", @@ -10931,7 +10900,6 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11125,6 +11093,35 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "node_modules/zx": { "version": "8.8.5", "resolved": "https://registry.npmjs.org/zx/-/zx-8.8.5.tgz", diff --git a/src/App.vue b/src/App.vue index 5dad25a..5419f90 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,4 +6,13 @@ - + diff --git a/src/composables/useTheme.ts b/src/composables/useTheme.ts new file mode 100644 index 0000000..3f24977 --- /dev/null +++ b/src/composables/useTheme.ts @@ -0,0 +1,139 @@ +/** + * 主题管理 composable + * 提供响应式的主题状态和操作方法 + */ +import { computed, onMounted, onUnmounted } from 'vue'; +import { useThemeStore, type Theme } from '@src/stores/theme'; + +/** + * 主题管理 composable + * 封装了主题状态管理、切换和系统主题检测 + */ +export function useTheme() { + const themeStore = useThemeStore(); + + // 响应式状态 + const theme = computed(() => themeStore.theme); + const isDark = computed(() => themeStore.isDark); + const systemTheme = computed(() => themeStore.systemTheme); + const initialized = computed(() => themeStore.initialized); + + // 计算实际应用的主题(考虑 system 模式) + const appliedTheme = computed(() => { + if (theme.value === 'system') return systemTheme.value; + return theme.value; + }); + + // 主题选项 + const themeOptions = computed(() => [ + { value: 'light' as Theme, label: '浅色' }, + { value: 'dark' as Theme, label: '深色' }, + { value: 'system' as Theme, label: '跟随系统' }, + ]); + + // 获取主题对应的标签 + const getThemeLabel = (themeValue: Theme): string => { + const option = themeOptions.value.find(opt => opt.value === themeValue); + return option?.label || themeValue; + }; + + // 当前主题标签 + const currentThemeLabel = computed(() => getThemeLabel(theme.value)); + + /** + * 初始化主题设置 + * 应该在应用启动时调用一次 + */ + const initTheme = async () => { + await themeStore.init(); + }; + + /** + * 切换主题 + * @param newTheme 新的主题模式 + */ + const setTheme = async (newTheme: Theme) => { + await themeStore.setTheme(newTheme); + }; + + /** + * 切换主题(轮换) + */ + const toggleTheme = async () => { + const themes: Theme[] = ['light', 'dark', 'system']; + const currentIndex = themes.indexOf(theme.value); + const nextIndex = (currentIndex + 1) % themes.length; + await setTheme(themes[nextIndex]); + }; + + /** + * 重置为系统主题 + */ + const resetToSystemTheme = async () => { + await setTheme('system'); + }; + + /** + * 检测当前系统主题 + */ + const detectSystemTheme = () => { + return themeStore.detectSystemTheme(); + }; + + // 监听系统主题变化(store 内部已处理,这里提供额外的事件监听) + const onSystemThemeChange = (callback: (theme: 'light' | 'dark') => void) => { + if (typeof window === 'undefined') return () => {}; + + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleChange = (e: MediaQueryListEvent) => { + callback(e.matches ? 'dark' : 'light'); + }; + + // 现代浏览器支持 addEventListener + if (mediaQuery.addEventListener) { + mediaQuery.addEventListener('change', handleChange); + return () => mediaQuery.removeEventListener('change', handleChange); + } else { + // 旧浏览器支持 addListener + mediaQuery.addListener(handleChange); + return () => mediaQuery.removeListener(handleChange); + } + }; + + // 自动初始化(可选) + onMounted(() => { + if (!themeStore.initialized) { + initTheme().catch(console.error); + } + }); + + return { + // 响应式状态 + theme, + isDark, + systemTheme, + appliedTheme, + initialized, + + // 主题选项 + themeOptions, + currentThemeLabel, + + // 操作方法 + initTheme, + setTheme, + toggleTheme, + resetToSystemTheme, + detectSystemTheme, + getThemeLabel, + + // 事件监听 + onSystemThemeChange, + + // store 引用(谨慎使用) + themeStore, + }; +} + +// 导出类型 +export type { Theme }; \ No newline at end of file diff --git a/src/i18n/locales/en/setting.json b/src/i18n/locales/en/setting.json index 1b10629..cb7e09f 100644 --- a/src/i18n/locales/en/setting.json +++ b/src/i18n/locales/en/setting.json @@ -9,7 +9,24 @@ "dark": "Dark Theme", "light": "Light Theme", "system": "System Theme", - "primaryColor": "Primary Color" + "primaryColor": "Primary Color", + "description": "Customize the look and feel of the application", + "themeMode": "Theme Mode", + "themeModeDescription": "Choose your preferred theme mode, or follow system settings to switch automatically.", + "lightLabel": "Light", + "lightDescription": "Bright and clear", + "darkLabel": "Dark", + "darkDescription": "Eye comfort", + "systemLabel": "Follow system", + "systemDescription": "Auto switch", + "currentTheme": "Current theme", + "followingSystem": "Following system theme ({{theme}})", + "setToLight": "Set to light theme", + "setToDark": "Set to dark theme", + "preview": "Preview", + "previewDescription": "View the current theme effect", + "lightPreview": "Light preview", + "darkPreview": "Dark preview" }, "appearance": { "fontSize": "Font Size", diff --git a/src/i18n/locales/zh/setting.json b/src/i18n/locales/zh/setting.json index ff2d9a0..1acc1a8 100644 --- a/src/i18n/locales/zh/setting.json +++ b/src/i18n/locales/zh/setting.json @@ -9,7 +9,24 @@ "dark": "深色主题", "light": "浅色主题", "system": "跟随系统", - "primaryColor": "主题颜色" + "primaryColor": "主题颜色", + "description": "自定义应用程序的外观和风格", + "themeMode": "主题模式", + "themeModeDescription": "选择您偏好的主题模式,或跟随系统设置自动切换。", + "lightLabel": "浅色", + "lightDescription": "明亮清晰", + "darkLabel": "深色", + "darkDescription": "护眼舒适", + "systemLabel": "跟随系统", + "systemDescription": "自动切换", + "currentTheme": "当前主题", + "followingSystem": "跟随系统主题 ({{theme}})", + "setToLight": "已设置为浅色主题", + "setToDark": "已设置为深色主题", + "preview": "预览", + "previewDescription": "查看当前主题效果", + "lightPreview": "浅色预览", + "darkPreview": "深色预览" }, "appearance": { "fontSize": "字体大小", diff --git a/src/pages/setting/components/Version/index.vue b/src/pages/setting/components/Version/index.vue index c8a9d8b..610947b 100644 --- a/src/pages/setting/components/Version/index.vue +++ b/src/pages/setting/components/Version/index.vue @@ -1,18 +1,58 @@ \ No newline at end of file + + + \ No newline at end of file diff --git a/src/stores/theme.ts b/src/stores/theme.ts new file mode 100644 index 0000000..39c75b4 --- /dev/null +++ b/src/stores/theme.ts @@ -0,0 +1,182 @@ +/** + * 主题状态管理 store + * 采用 Vue 推荐的 Pinia 实现,功能和架构与 ClawX 的 Zustand 实现保持一致 + * 集成 zn-ai 现有 IPC 通信 + */ +import { defineStore } from 'pinia'; + +export type Theme = 'light' | 'dark' | 'system'; + +interface ThemeState { + // 主题状态 + theme: Theme; + isDark: boolean; + systemTheme: 'light' | 'dark'; + + // 初始化状态 + initialized: boolean; +} + +// 检测系统主题 +const detectSystemTheme = (): 'light' | 'dark' => { + if (typeof window === 'undefined') return 'light'; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +}; + +// 计算实际应用的主题(考虑 system 模式) +const getAppliedTheme = (theme: Theme, systemTheme: 'light' | 'dark'): 'light' | 'dark' => { + if (theme === 'system') return systemTheme; + return theme; +}; + +// 应用主题到 DOM(通过 CSS 类) +const applyThemeToDom = (theme: 'light' | 'dark') => { + if (typeof document === 'undefined') return; + + const root = document.documentElement; + if (theme === 'dark') { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } +}; + +export const useThemeStore = defineStore('zn-ai-theme', { + state: (): ThemeState => { + // 从 localStorage 恢复缓存的主题,确保在初始化前 UI 不会闪烁 + const cachedTheme = typeof localStorage !== 'undefined' ? localStorage.getItem('zn-ai-theme-cache') as Theme : null; + const initialTheme = (cachedTheme === 'light' || cachedTheme === 'dark' || cachedTheme === 'system') ? cachedTheme : 'system'; + + return { + theme: initialTheme, + isDark: false, + systemTheme: 'light', + initialized: false, + }; + }, + + actions: { + async init() { + if (this.initialized) return; + + try { + // 1. 检测系统主题 + const systemTheme = detectSystemTheme(); + + // 2. 从主进程获取持久化的主题设置 + let savedTheme: Theme = 'system'; + try { + savedTheme = await window.api.invoke('get-theme-mode'); + } catch (error) { + console.warn('Failed to get theme from main process, using default:', error); + } + + // 3. 计算实际主题和 isDark 状态 + const appliedTheme = getAppliedTheme(savedTheme, systemTheme); + const isDark = appliedTheme === 'dark'; + + // 4. 应用主题到 DOM + applyThemeToDom(appliedTheme); + + // 5. 更新 store 状态 + this.theme = savedTheme; + this.isDark = isDark; + this.systemTheme = systemTheme; + this.initialized = true; + + // 缓存到 localStorage + if (typeof localStorage !== 'undefined') { + localStorage.setItem('zn-ai-theme-cache', savedTheme); + } + + // 6. 监听系统主题变化 + if (typeof window !== 'undefined') { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleChange = (e: MediaQueryListEvent) => { + const newSystemTheme = e.matches ? 'dark' : 'light'; + + if (this.theme === 'system') { + const appliedTheme = getAppliedTheme('system', newSystemTheme); + const isDark = appliedTheme === 'dark'; + applyThemeToDom(appliedTheme); + this.systemTheme = newSystemTheme; + this.isDark = isDark; + } else { + this.systemTheme = newSystemTheme; + } + }; + + if (mediaQuery.addEventListener) { + mediaQuery.addEventListener('change', handleChange); + } else { + mediaQuery.addListener(handleChange); + } + + // 7. 监听主进程主题更新事件 + window.api.on('theme-mode-updated', (isDarkUpdate: boolean) => { + const newIsDark = Boolean(isDarkUpdate); + const newTheme = newIsDark ? 'dark' : 'light'; + if (this.theme === 'system') { + this.isDark = newIsDark; + } else { + const actualTheme = newIsDark ? 'dark' : 'light'; + if (this.theme !== actualTheme) { + this.theme = actualTheme; + this.isDark = newIsDark; + } + } + }); + } + + console.log('Theme store initialized:', { theme: savedTheme, isDark, systemTheme }); + } catch (error) { + console.error('Failed to initialize theme store:', error); + const systemTheme = detectSystemTheme(); + const appliedTheme = getAppliedTheme('system', systemTheme); + const isDark = appliedTheme === 'dark'; + applyThemeToDom(appliedTheme); + this.theme = 'system'; + this.isDark = isDark; + this.systemTheme = systemTheme; + this.initialized = true; + } + }, + + async setTheme(theme: Theme) { + if (theme === this.theme) return; + + try { + // 1. 保存到主进程 + await window.api.invoke('set-theme-mode', theme); + + // 2. 计算实际应用的主题 + const appliedTheme = getAppliedTheme(theme, this.systemTheme); + const isDark = appliedTheme === 'dark'; + + // 3. 应用主题到 DOM + applyThemeToDom(appliedTheme); + + // 4. 更新 store 状态 + this.theme = theme; + this.isDark = isDark; + + // 5. 缓存到 localStorage + if (typeof localStorage !== 'undefined') { + localStorage.setItem('zn-ai-theme-cache', theme); + } + + console.log('Theme changed:', { theme, appliedTheme, isDark }); + } catch (error) { + console.error('Failed to set theme:', error); + throw error; + } + }, + + detectSystemTheme, + + updateIsDark() { + const appliedTheme = getAppliedTheme(this.theme, this.systemTheme); + this.isDark = appliedTheme === 'dark'; + } + } +}); \ No newline at end of file diff --git a/src/styles/index.css b/src/styles/index.css index a1c9e3f..c725081 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -1,4 +1,8 @@ @import "tailwindcss"; + +/* 启用 dark 变体,基于 .dark 类 */ +@custom-variant dark (&:where(.dark, .dark *)); + @import "./theme/index.css"; @import "./tailwind.css"; @plugin "@tailwindcss/typography"; diff --git a/src/styles/theme/dark.css b/src/styles/theme/dark.css index c72d2e6..5e7ba9f 100644 --- a/src/styles/theme/dark.css +++ b/src/styles/theme/dark.css @@ -1,126 +1,111 @@ -@media (prefers-color-scheme: dark) { - :root { - --primary-color: #07C160; - --bg-color: #1E1E1E; - --bg-secondary: #2C2C2C; +/* 深色主题变量定义在 .dark 类下 */ +.dark { + --primary-color: #07C160; + --bg-color: #1E1E1E; + --bg-secondary: #2C2C2C; + --text-primary: #E0E0E0; + --text-secondary: #A0A0A0; - --text-primary: #E0E0E0; - --text-secondary: #A0A0A0; - - --bubble-self: var(--primary-color); - --bubble-others: #3A3A3A; - --input-bg: #333333; - --ripple-color: var(--text-secondary); - --ripple-opacity: 0.2; - } - - pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em - } - code.hljs { - padding: 3px 5px - } - /*! - Theme: GitHub Dark - Description: Dark theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-dark - Current colors taken from GitHub's CSS - */ - .hljs { - color: var(--text-primary); - background: var(--input-bg); - } - .hljs-doctag, - .hljs-keyword, - .hljs-meta .hljs-keyword, - .hljs-template-tag, - .hljs-template-variable, - .hljs-type, - .hljs-variable.language_ { - /* prettylights-syntax-keyword */ - color: #ff7b72 - } - .hljs-title, - .hljs-title.class_, - .hljs-title.class_.inherited__, - .hljs-title.function_ { - /* prettylights-syntax-entity */ - color: #d2a8ff - } - .hljs-attr, - .hljs-attribute, - .hljs-literal, - .hljs-meta, - .hljs-number, - .hljs-operator, - .hljs-variable, - .hljs-selector-attr, - .hljs-selector-class, - .hljs-selector-id { - /* prettylights-syntax-constant */ - color: #79c0ff - } - .hljs-regexp, - .hljs-string, - .hljs-meta .hljs-string { - /* prettylights-syntax-string */ - color: #a5d6ff - } - .hljs-built_in, - .hljs-symbol { - /* prettylights-syntax-variable */ - color: #ffa657 - } - .hljs-comment, - .hljs-code, - .hljs-formula { - /* prettylights-syntax-comment */ - color: #8b949e - } - .hljs-name, - .hljs-quote, - .hljs-selector-tag, - .hljs-selector-pseudo { - /* prettylights-syntax-entity-tag */ - color: #7ee787 - } - .hljs-subst { - /* prettylights-syntax-storage-modifier-import */ - color: #c9d1d9 - } - .hljs-section { - /* prettylights-syntax-markup-heading */ - color: #1f6feb; - font-weight: bold - } - .hljs-bullet { - /* prettylights-syntax-markup-list */ - color: #f2cc60 - } - .hljs-emphasis { - /* prettylights-syntax-markup-italic */ - color: #c9d1d9; - font-style: italic - } - .hljs-strong { - /* prettylights-syntax-markup-bold */ - color: #c9d1d9; - font-weight: bold - } - .hljs-addition { - /* prettylights-syntax-markup-inserted */ - color: #aff5b4; - background-color: #033a16 - } - .hljs-deletion { - /* prettylights-syntax-markup-deleted */ - color: #ffdcd7; - background-color: #67060c - } + --bubble-self: var(--primary-color); + --bubble-others: #3A3A3A; + --input-bg: #333333; + --ripple-color: var(--text-secondary); + --ripple-opacity: 0.2; } + +/* 代码高亮样式 */ +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em +} +code.hljs { + padding: 3px 5px +} +/*! + Theme: GitHub Dark + Description: Dark theme as seen on github.com + Author: github.com + Maintainer: @Hirse + Updated: 2021-05-15 + + Outdated base version: https://github.com/primer/github-syntax-dark + Current colors taken from GitHub's CSS +*/ +.dark .hljs { + color: var(--text-primary); + background: var(--input-bg); +} +.dark .hljs-doctag, +.dark .hljs-keyword, +.dark .hljs-meta .hljs-keyword, +.dark .hljs-template-tag, +.dark .hljs-template-variable, +.dark .hljs-type, +.dark .hljs-variable.language_ { + color: #ff7b72 +} +.dark .hljs-title, +.dark .hljs-title.class_, +.dark .hljs-title.class_.inherited__, +.dark .hljs-title.function_ { + color: #d2a8ff +} +.dark .hljs-attr, +.dark .hljs-attribute, +.dark .hljs-literal, +.dark .hljs-meta, +.dark .hljs-number, +.dark .hljs-operator, +.dark .hljs-variable, +.dark .hljs-selector-attr, +.dark .hljs-selector-class, +.dark .hljs-selector-id { + color: #79c0ff +} +.dark .hljs-regexp, +.dark .hljs-string, +.dark .hljs-meta .hljs-string { + color: #a5d6ff +} +.dark .hljs-built_in, +.dark .hljs-symbol { + color: #ffa657 +} +.dark .hljs-comment, +.dark .hljs-code, +.dark .hljs-formula { + color: #8b949e +} +.dark .hljs-name, +.dark .hljs-quote, +.dark .hljs-selector-tag, +.dark .hljs-selector-pseudo { + color: #7ee787 +} +.dark .hljs-subst { + color: #c9d1d9 +} +.dark .hljs-section { + color: #1f6feb; + font-weight: bold +} +.dark .hljs-bullet { + color: #f2cc60 +} +.dark .hljs-emphasis { + color: #c9d1d9; + font-style: italic +} +.dark .hljs-strong { + color: #c9d1d9; + font-weight: bold +} +.dark .hljs-addition { + color: #aff5b4; + background-color: #033a16 +} +.dark .hljs-deletion { + color: #ffdcd7; + background-color: #67060c +} \ No newline at end of file diff --git a/src/styles/theme/light.css b/src/styles/theme/light.css index 69e36b4..f51a6e2 100644 --- a/src/styles/theme/light.css +++ b/src/styles/theme/light.css @@ -1,129 +1,112 @@ -@media (prefers-color-scheme: light) { - :root { - --primary-color: #07C160; - --bg-color: #FFFFFF; - --bg-secondary: #F5F5F5; - --text-primary: #000000; - --text-secondary: #7F7F7F; +/* 浅色主题变量定义在 :root 中 */ +:root { + --primary-color: #07C160; + --bg-color: #FFFFFF; + --bg-secondary: #F5F5F5; + --text-primary: #000000; + --text-secondary: #7F7F7F; - --header-bg: var(--primary-color); - --bubble-self: var(--primary-color); - --bubble-others: #FFFFFF; - --input-bg: #F0F0F0; - --ripple-color: var(--text-secondary); - --ripple-opacity: 0.2; - } - - pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em - } - code.hljs { - padding: 3px 5px - } - /*! - Theme: GitHub - Description: Light theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 + --header-bg: var(--primary-color); + --bubble-self: var(--primary-color); + --bubble-others: #FFFFFF; + --input-bg: #F0F0F0; + --ripple-color: var(--text-secondary); + --ripple-opacity: 0.2; +} - Outdated base version: https://github.com/primer/github-syntax-light - Current colors taken from GitHub's CSS - */ - .hljs { - /* color: #24292e; - background: #ffffff */ +/* 代码高亮样式 */ +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em +} +code.hljs { + padding: 3px 5px +} +/*! + Theme: GitHub + Description: Light theme as seen on github.com + Author: github.com + Maintainer: @Hirse + Updated: 2021-05-15 - color: var(--text-primary); - background: var(--input-bg); - } - .hljs-doctag, - .hljs-keyword, - .hljs-meta .hljs-keyword, - .hljs-template-tag, - .hljs-template-variable, - .hljs-type, - .hljs-variable.language_ { - /* prettylights-syntax-keyword */ - color: #d73a49 - } - .hljs-title, - .hljs-title.class_, - .hljs-title.class_.inherited__, - .hljs-title.function_ { - /* prettylights-syntax-entity */ - color: #6f42c1 - } - .hljs-attr, - .hljs-attribute, - .hljs-literal, - .hljs-meta, - .hljs-number, - .hljs-operator, - .hljs-variable, - .hljs-selector-attr, - .hljs-selector-class, - .hljs-selector-id { - /* prettylights-syntax-constant */ - color: #005cc5 - } - .hljs-regexp, - .hljs-string, - .hljs-meta .hljs-string { - /* prettylights-syntax-string */ - color: #032f62 - } - .hljs-built_in, - .hljs-symbol { - /* prettylights-syntax-variable */ - color: #e36209 - } - .hljs-comment, - .hljs-code, - .hljs-formula { - /* prettylights-syntax-comment */ - color: #6a737d - } - .hljs-name, - .hljs-quote, - .hljs-selector-tag, - .hljs-selector-pseudo { - /* prettylights-syntax-entity-tag */ - color: #22863a - } - .hljs-subst { - /* prettylights-syntax-storage-modifier-import */ - color: #24292e - } - .hljs-section { - /* prettylights-syntax-markup-heading */ - color: #005cc5; - font-weight: bold - } - .hljs-bullet { - /* prettylights-syntax-markup-list */ - color: #735c0f - } - .hljs-emphasis { - /* prettylights-syntax-markup-italic */ - color: #24292e; - font-style: italic - } - .hljs-strong { - /* prettylights-syntax-markup-bold */ - color: #24292e; - font-weight: bold - } - .hljs-addition { - /* prettylights-syntax-markup-inserted */ - color: #22863a; - background-color: #f0fff4 - } - .hljs-deletion { - /* prettylights-syntax-markup-deleted */ - color: #b31d28; - background-color: #ffeef0 - } + Outdated base version: https://github.com/primer/github-syntax-light + Current colors taken from GitHub's CSS +*/ +.hljs { + color: var(--text-primary); + background: var(--input-bg); +} +.hljs-doctag, +.hljs-keyword, +.hljs-meta .hljs-keyword, +.hljs-template-tag, +.hljs-template-variable, +.hljs-type, +.hljs-variable.language_ { + color: #d73a49 +} +.hljs-title, +.hljs-title.class_, +.hljs-title.class_.inherited__, +.hljs-title.function_ { + color: #6f42c1 +} +.hljs-attr, +.hljs-attribute, +.hljs-literal, +.hljs-meta, +.hljs-number, +.hljs-operator, +.hljs-variable, +.hljs-selector-attr, +.hljs-selector-class, +.hljs-selector-id { + color: #005cc5 +} +.hljs-regexp, +.hljs-string, +.hljs-meta .hljs-string { + color: #032f62 +} +.hljs-built_in, +.hljs-symbol { + color: #e36209 +} +.hljs-comment, +.hljs-code, +.hljs-formula { + color: #6a737d +} +.hljs-name, +.hljs-quote, +.hljs-selector-tag, +.hljs-selector-pseudo { + color: #22863a +} +.hljs-subst { + color: #24292e +} +.hljs-section { + color: #005cc5; + font-weight: bold +} +.hljs-bullet { + color: #735c0f +} +.hljs-emphasis { + color: #24292e; + font-style: italic +} +.hljs-strong { + color: #24292e; + font-weight: bold +} +.hljs-addition { + color: #22863a; + background-color: #f0fff4 +} +.hljs-deletion { + color: #b31d28; + background-color: #ffeef0 } \ No newline at end of file diff --git a/theme-implementation-reference.md b/theme-implementation-reference.md new file mode 100644 index 0000000..9d707d6 --- /dev/null +++ b/theme-implementation-reference.md @@ -0,0 +1,697 @@ +# ClawX主题设置功能分析报告与zn-ai迁移开发计划 + +## 一、ClawX项目主题设置功能全面分析报告 + +### 1. 架构概述 + +ClawX项目采用**现代化的主题管理系统**,支持light、dark、system三种主题模式,通过CSS变量、Tailwind CSS和状态管理实现灵活的主题切换功能。 + +### 2. 核心组件架构 + +| 组件 | 文件路径 | 功能描述 | +|------|----------|---------| +| **设置页面UI** | `src/pages/Settings/index.tsx` | 主题设置界面,包含light/dark/system三个按钮 | +| **状态管理Store** | `src/stores/settings.ts` | Zustand状态管理,包含theme状态和setTheme方法 | +| **主题应用逻辑** | `src/App.tsx` | 监听theme变化,在html元素上应用对应的CSS类 | +| **CSS变量定义** | `src/styles/globals.css` | 定义:root和.dark类的CSS变量 | +| **Tailwind配置** | `tailwind.config.js` | 配置`darkMode: ['class']`支持CSS类切换 | +| **主进程API** | `electron/api/routes/settings.ts` | 提供`/api/settings/theme`接口处理主题设置 | +| **配置持久化** | `electron/utils/store.ts` | 使用electron-store持久化主题配置 | +| **国际化文本** | `src/i18n/locales/*/settings.json` | 主题设置的本地化文本 | + +### 3. 技术实现细节 + +#### 3.1 状态管理 (Zustand) +```typescript +// src/stores/settings.ts 关键代码 +interface SettingsState { + theme: 'light' | 'dark' | 'system'; + // ...其他设置 + setTheme: (theme: 'light' | 'dark' | 'system') => void; +} + +export const useSettingsStore = create()( + persist( + (set) => ({ + theme: 'system', + setTheme: (theme) => { + set({ theme }); + // 通知主进程更新配置 + hostApiFetch('/api/settings/theme', { method: 'POST', body: JSON.stringify({ theme }) }); + }, + }), + { + name: 'settings-storage', + partialize: (state) => ({ theme: state.theme }), + } + ) +); +``` + +#### 3.2 主题应用逻辑 (React Effect) +```typescript +// src/App.tsx 关键代码 +export function App() { + const { theme } = useSettingsStore(); + + useEffect(() => { + const htmlEl = document.documentElement; + + // 移除现有主题类 + htmlEl.classList.remove('light', 'dark'); + + if (theme === 'system') { + // 系统主题检测 + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const systemTheme = mediaQuery.matches ? 'dark' : 'light'; + htmlEl.classList.add(systemTheme); + + // 监听系统主题变化 + const handler = (e: MediaQueryListEvent) => { + htmlEl.classList.remove('light', 'dark'); + htmlEl.classList.add(e.matches ? 'dark' : 'light'); + }; + mediaQuery.addEventListener('change', handler); + return () => mediaQuery.removeEventListener('change', handler); + } else { + htmlEl.classList.add(theme); + } + }, [theme]); + + return ; +} +``` + +#### 3.3 CSS变量定义 +```css +/* src/styles/globals.css 关键代码 */ +:root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --primary: 221.2 83.2% 53.3%; + --primary-foreground: 210 40% 98%; + /* ...更多变量 */ +} + +.dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 222.2 47.4% 11.2%; + /* ...深色模式变量 */ +} +``` + +#### 3.4 Tailwind配置 +```javascript +// tailwind.config.js 关键配置 +module.exports = { + darkMode: ['class'], // 通过CSS类控制深色模式 + theme: { + extend: { + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: 'hsl(var(--card))', + 'card-foreground': 'hsl(var(--card-foreground))', + primary: 'hsl(var(--primary))', + 'primary-foreground': 'hsl(var(--primary-foreground))', + // ...映射CSS变量到Tailwind颜色 + }, + }, + }, +}; +``` + +#### 3.5 设置页面UI +```tsx +// src/pages/Settings/index.tsx 主题设置部分 +const ThemeSection = () => { + const { t } = useTranslation(); + const { theme, setTheme } = useSettingsStore(); + + const themes = [ + { id: 'light', label: t('settings.theme.light'), icon: Sun }, + { id: 'dark', label: t('settings.theme.dark'), icon: Moon }, + { id: 'system', label: t('settings.theme.system'), icon: Monitor }, + ] as const; + + return ( +
+

{t('settings.theme.title')}

+

+ {t('settings.theme.description')} +

+
+ {themes.map(({ id, label, icon: Icon }) => ( + + ))} +
+
+ ); +}; +``` + +#### 3.6 主进程API +```typescript +// electron/api/routes/settings.ts 关键代码 +router.post('/theme', async (req, res) => { + const { theme } = req.body; + + // 验证主题值 + if (!['light', 'dark', 'system'].includes(theme)) { + return res.status(400).json({ error: 'Invalid theme' }); + } + + try { + // 存储到electron-store + await store.set('settings.theme', theme); + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: 'Failed to save theme' }); + } +}); +``` + +### 4. 工作流程 + +#### 4.1 主题切换流程 +``` +用户点击主题按钮 → 调用setTheme更新Zustand状态 → +状态更新触发App.tsx中的useEffect → 在html元素上添加对应CSS类 → +同时调用hostApiFetch通知主进程 → 主进程存储配置到electron-store → +CSS变量和Tailwind样式自动应用新主题 +``` + +#### 4.2 系统主题检测流程 +``` +用户选择system模式 → useEffect检测系统主题偏好 → +监听prefers-color-scheme媒体查询 → 系统主题变化时自动更新CSS类 → +用户无需手动切换,主题随系统设置变化 +``` + +### 5. 设计模式与最佳实践 + +#### 5.1 关注点分离 +- **UI层**:设置页面只负责显示和用户交互 +- **状态层**:Zustand管理主题状态和业务逻辑 +- **样式层**:CSS变量和Tailwind处理主题样式 +- **持久化层**:electron-store负责配置存储 +- **系统集成层**:主进程API处理进程间通信 + +#### 5.2 可扩展性设计 +- **主题变量系统**:CSS变量易于扩展新主题 +- **插件化架构**:可轻松添加新的主题模式 +- **响应式设计**:主题切换不影响应用功能 + +#### 5.3 用户体验优化 +- **即时反馈**:主题切换无延迟 +- **系统集成**:支持跟随系统主题 +- **状态持久化**:记住用户偏好 +- **无障碍支持**:高对比度主题选项 + +### 6. 技术栈总结 + +| 技术 | 用途 | 优势 | +|------|------|------| +| **Zustand** | 状态管理 | 轻量级、类型安全、支持持久化 | +| **Tailwind CSS** | 样式系统 | 实用优先、主题变量集成 | +| **CSS变量** | 主题变量 | 动态更新、性能优化 | +| **electron-store** | 配置存储 | 简单易用、跨平台 | +| **React Hooks** | 逻辑封装 | 响应式、可组合 | +| **TypeScript** | 类型安全 | 编译时检查、更好的开发体验 | + +--- + +## 二、zn-ai项目现有主题实现分析 + +### 1. 现有架构 + +#### 1.1 核心组件 +| 组件 | 文件路径 | 功能描述 | +|------|----------|---------| +| **主题服务** | `electron/service/theme-service/index.ts` | 主题切换服务,处理IPC通信 | +| **配置服务** | `electron/service/config-service/index.ts` | 配置存储管理,使用config.json文件 | +| **CSS变量系统** | `src/styles/theme/` | 包含light.css、dark.css、index.css | +| **主题常量** | `src/lib/constants.ts` | 定义CONFIG_KEYS.THEME_MODE等常量 | +| **样式入口** | `src/styles/index.css` | 导入主题样式 | + +#### 1.2 现有实现特点 +- ✅ **基础主题服务**:theme-service处理主题切换IPC通信 +- ✅ **配置管理**:config-service提供配置存储 +- ✅ **CSS变量系统**:已定义light和dark主题的CSS变量 +- ✅ **主题常量**:定义了THEME_MODE等配置键 +- ✅ **样式组织**:主题样式文件结构清晰 + +#### 1.3 缺失功能 +- ❌ **系统主题支持**:缺少system模式,无法跟随系统主题 +- ❌ **用户界面**:没有主题设置UI组件 +- ❌ **Tailwind集成**:未与Tailwind CSS深度集成 +- ❌ **状态管理**:缺少前端状态管理集成 +- ❌ **国际化支持**:主题文本未国际化 +- ❌ **完整工作流**:主题切换流程不完整 + +### 2. 技术差异分析 + +| 功能点 | ClawX实现 | zn-ai现状 | 迁移需求 | +|--------|-----------|-----------|----------| +| **状态管理** | Zustand + persist | 无前端状态管理 | 需要集成Zustand | +| **主题模式** | light/dark/system | light/dark | 需要添加system模式 | +| **CSS系统** | CSS变量 + Tailwind | CSS变量 | 需要Tailwind集成 | +| **UI组件** | 完整的设置界面 | 无主题设置UI | 需要开发设置组件 | +| **持久化** | electron-store | config.json文件 | 需统一存储方案 | +| **系统集成** | 系统主题检测 | 无系统检测 | 需要媒体查询支持 | +| **国际化** | 多语言文本 | 无主题文本 | 需要添加翻译 | + +--- + +## 三、zn-ai主题设置迁移开发计划 + +### 1. 迁移目标 + +#### 1.1 核心目标 +1. **完整功能迁移**:实现ClawX的所有主题设置功能 +2. **无缝集成**:与zn-ai现有架构完美融合 +3. **用户体验优化**:提供流畅的主题切换体验 +4. **技术债务清理**:统一主题管理方案 + +#### 1.2 成功指标 +- [ ] 支持light、dark、system三种主题模式 +- [ ] 主题切换响应时间 < 100ms +- [ ] 配置持久化成功率 100% +- [ ] 系统主题检测准确率 100% +- [ ] 用户界面友好度评分 > 4.5/5 + +### 2. 技术选型与架构设计 + +#### 2.1 技术栈保持一致 +| 技术 | 选型理由 | 实施计划 | +|------|----------|----------| +| **Zustand** | 与ClawX保持一致,轻量高效 | 新增依赖,创建theme store | +| **CSS变量** | 已存在,需扩展 | 优化现有CSS变量定义 | +| **Tailwind集成** | 提升开发效率 | 配置darkMode和颜色映射 | +| **Vue 3 Composition API** | 适配zn-ai技术栈 | 使用Vue替代React实现 | + +#### 2.2 架构设计 +``` +zn-ai主题系统架构 +├── 前端层 (Vue 3) +│ ├── 主题设置组件 (Settings/ThemeSetting.vue) +│ ├── 主题状态管理 (stores/theme.ts) +│ ├── 主题应用逻辑 (composables/useTheme.ts) +│ └── 国际化文本 (i18n/locales/*/settings.json) +├── 样式层 +│ ├── CSS变量定义 (styles/theme/) +│ ├── Tailwind配置 (tailwind.config.js) +│ └── 全局样式 (styles/index.css) +├── 服务层 +│ ├── 主题服务增强 (electron/service/theme-service/) +│ ├── 配置服务集成 (electron/service/config-service/) +│ └── IPC通信优化 +└── 持久化层 + ├── 配置文件 (userData/config.json) + └── 状态持久化 (Zustand persist) +``` + +### 3. 核心功能模块 + +#### 3.1 主题状态管理 (Vue + Zustand) +```typescript +// src/stores/theme.ts +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +interface ThemeState { + theme: 'light' | 'dark' | 'system'; + systemTheme: 'light' | 'dark'; + setTheme: (theme: 'light' | 'dark' | 'system') => Promise; + detectSystemTheme: () => 'light' | 'dark'; +} + +export const useThemeStore = create()( + persist( + (set, get) => ({ + theme: 'system', + systemTheme: 'light', + setTheme: async (theme) => { + set({ theme }); + // 调用主题服务更新配置 + await window.api.theme.setThemeMode(theme); + }, + detectSystemTheme: () => { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + }, + }), + { + name: 'theme-storage', + partialize: (state) => ({ theme: state.theme }), + } + ) +); +``` + +#### 3.2 主题应用Composable +```typescript +// src/composables/useTheme.ts +import { useThemeStore } from '@/stores/theme'; +import { watch, onMounted, onUnmounted } from 'vue'; + +export function useTheme() { + const store = useThemeStore(); + + const applyTheme = () => { + const htmlEl = document.documentElement; + htmlEl.classList.remove('light', 'dark'); + + let themeToApply = store.theme; + if (themeToApply === 'system') { + themeToApply = store.detectSystemTheme(); + } + + htmlEl.classList.add(themeToApply); + }; + + // 监听主题变化 + watch(() => store.theme, applyTheme, { immediate: true }); + + // 监听系统主题变化 + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleSystemThemeChange = (e: MediaQueryListEvent) => { + if (store.theme === 'system') { + store.setSystemTheme(e.matches ? 'dark' : 'light'); + applyTheme(); + } + }; + + onMounted(() => { + mediaQuery.addEventListener('change', handleSystemThemeChange); + }); + + onUnmounted(() => { + mediaQuery.removeEventListener('change', handleSystemThemeChange); + }); + + return { + theme: store.theme, + setTheme: store.setTheme, + availableThemes: ['light', 'dark', 'system'] as const, + }; +} +``` + +#### 3.3 主题设置组件 +```vue + + + + +``` + +#### 3.4 Tailwind配置优化 +```javascript +// tailwind.config.js +module.exports = { + darkMode: ['class'], // 启用CSS类控制的深色模式 + theme: { + extend: { + colors: { + // 映射CSS变量到Tailwind + background: 'var(--bg-color)', + 'background-secondary': 'var(--bg-secondary)', + foreground: 'var(--text-primary)', + 'foreground-secondary': 'var(--text-secondary)', + primary: 'var(--primary-color)', + border: 'var(--border-color)', + input: 'var(--input-bg)', + }, + }, + }, +}; +``` + +#### 3.5 主题服务增强 +```typescript +// electron/service/theme-service/index.ts 增强 +import { ipcMain, BrowserWindow } from 'electron'; +import { IPC_EVENTS, CONFIG_KEYS } from '@lib/constants'; +import { ConfigService } from '../config-service'; + +export class ThemeService { + private configService: ConfigService; + + constructor() { + this.configService = ConfigService.getInstance(); + this.setupIpcHandlers(); + } + + private setupIpcHandlers() { + // 获取当前主题 + ipcMain.handle(IPC_EVENTS.GET_THEME_MODE, () => { + return this.configService.get(CONFIG_KEYS.THEME_MODE); + }); + + // 设置主题 + ipcMain.handle(IPC_EVENTS.SET_THEME_MODE, (_, theme: string) => { + if (!['light', 'dark', 'system'].includes(theme)) { + throw new Error('Invalid theme mode'); + } + + this.configService.set(CONFIG_KEYS.THEME_MODE, theme); + + // 通知所有窗口主题已更新 + BrowserWindow.getAllWindows().forEach(win => { + win.webContents.send(IPC_EVENTS.THEME_MODE_UPDATED, theme); + }); + + return { success: true }; + }); + + // 检查是否为深色主题 + ipcMain.handle(IPC_EVENTS.IS_DARK_THEME, () => { + const theme = this.configService.get(CONFIG_KEYS.THEME_MODE); + if (theme === 'system') { + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + return theme === 'dark'; + }); + } +} +``` + +### 4. 实施路线图 + +#### 第一阶段:基础框架搭建 (预计1-2周) +1. **依赖安装与配置** + - 安装zustand、zustand/middleware + - 配置Tailwind支持CSS类darkMode + - 更新TypeScript类型定义 + +2. **状态管理集成** + - 创建theme store (src/stores/theme.ts) + - 实现主题状态持久化 + - 添加IPC通信集成 + +3. **Composable开发** + - 创建useTheme composable + - 实现主题应用逻辑 + - 添加系统主题检测 + +#### 第二阶段:核心功能实现 (预计2-3周) +1. **UI组件开发** + - 创建ThemeSetting组件 + - 开发ThemeOption子组件 + - 集成到现有设置页面 + +2. **样式系统优化** + - 优化CSS变量定义 + - 配置Tailwind颜色映射 + - 添加system模式样式 + +3. **服务层增强** + - 增强theme-service功能 + - 集成config-service + - 优化IPC通信 + +#### 第三阶段:高级功能与集成 (预计1-2周) +1. **国际化支持** + - 添加主题相关翻译文本 + - 支持多语言主题描述 + - 更新i18n配置文件 + +2. **系统集成** + - 实现系统主题自动切换 + - 添加主题变化监听 + - 支持多窗口主题同步 + +3. **性能优化** + - 主题切换性能优化 + - 减少CSS重绘 + - 优化存储读写 + +#### 第四阶段:测试与优化 (预计1周) +1. **功能测试** + - 单元测试:store、composable、组件 + - 集成测试:主题切换流程 + - E2E测试:用户操作场景 + +2. **兼容性测试** + - 不同操作系统测试 + - 不同浏览器引擎测试 + - 多分辨率适配测试 + +3. **用户体验优化** + - 主题切换动画 + - 错误处理与提示 + - 无障碍访问支持 + +### 5. 技术挑战与应对策略 + +#### 5.1 技术挑战 +1. **Vue与Zustand集成**:Vue生态系统与Zustand的集成 +2. **CSS变量与Tailwind兼容**:确保CSS变量正确映射到Tailwind +3. **系统主题实时检测**:跨平台系统主题变化监听 +4. **多窗口主题同步**:确保所有窗口主题一致 + +#### 5.2 应对策略 +1. **使用vue-zustand库**:或创建Vue适配器 +2. **渐进式集成**:先实现基础功能,再优化样式系统 +3. **标准化API**:使用matchMedia标准API,确保跨平台兼容 +4. **事件广播机制**:通过主进程广播主题变化事件 + +### 6. 成功验收标准 + +#### 6.1 功能验收 +- [ ] 支持三种主题模式:light、dark、system +- [ ] 主题切换即时生效,无闪烁 +- [ ] 系统主题变化自动跟随 +- [ ] 配置持久化,重启后保持主题 +- [ ] 多语言主题文本支持 + +#### 6.2 性能验收 +- [ ] 主题切换响应时间 < 100ms +- [ ] 内存占用增加 < 5MB +- [ ] 首次加载时间增加 < 200ms +- [ ] CSS变量计算性能优化 + +#### 6.3 用户体验验收 +- [ ] 设置界面直观易用 +- [ ] 主题预览效果清晰 +- [ ] 错误提示友好 +- [ ] 无障碍访问支持 + +### 7. 下一步行动计划 + +1. **立即行动** (本周内) + - 评审技术方案,确认实施细节 + - 创建开发分支,准备开发环境 + - 编写详细的技术设计文档 + +2. **短期计划** (1-2周) + - 完成第一阶段基础框架 + - 实现主题状态管理 + - 开发基础UI组件 + +3. **中期计划** (3-4周) + - 完成所有核心功能 + - 实现系统主题支持 + - 完成国际化集成 + +4. **长期计划** (5-6周) + - 全面测试与优化 + - 性能调优与bug修复 + - 文档编写与发布准备 + +--- + +## 四、附录:参考实现与资源 + +### 1. ClawX关键文件参考 +- `src/pages/Settings/index.tsx` - 设置页面实现 +- `src/stores/settings.ts` - Zustand状态管理 +- `src/App.tsx` - 主题应用逻辑 +- `src/styles/globals.css` - CSS变量定义 +- `tailwind.config.js` - Tailwind配置 +- `electron/api/routes/settings.ts` - 主进程API + +### 2. zn-ai现有文件 +- `electron/service/theme-service/index.ts` - 现有主题服务 +- `electron/service/config-service/index.ts` - 配置服务 +- `src/styles/theme/` - 现有CSS变量 +- `src/lib/constants.ts` - 主题相关常量 + +### 3. 技术文档 +- [Zustand文档](https://docs.pmnd.rs/zustand/getting-started/introduction) +- [Tailwind CSS深色模式](https://tailwindcss.com/docs/dark-mode) +- [CSS变量MDN文档](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) +- [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq) + +### 4. 测试方案 +- **单元测试**:Vitest + Testing Library +- **集成测试**:Playwright +- **E2E测试**:Cypress +- **性能测试**:Chrome DevTools + +--- + +**文档版本**:v1.0 +**创建时间**:2026-04-08 +**最后更新**:2026-04-08 +**负责人**:zn-ai开发团队 \ No newline at end of file