Compare commits
4 Commits
0242f14aaa
...
ba5dee151e
| Author | SHA1 | Date |
|---|---|---|
|
|
ba5dee151e | |
|
|
72b2d5bdfe | |
|
|
c45e8b0d43 | |
|
|
3ce9735cda |
|
|
@ -4,6 +4,26 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v0.3.2(2026-08-25)
|
||||||
|
### 🎉 新版本发布!
|
||||||
|
|
||||||
|
### ✨ 新增功能
|
||||||
|
|
||||||
|
- **深色主题支持**:新增全局深色主题样式变量,适配明暗两种主题;配置 naive-ui 主题跟随系统或用户设置切换,并修复外观设置页面主题同步逻辑。
|
||||||
|
- **可视化更新弹窗**:新增 `UpdateModal` 更新弹窗组件,支持 Markdown 格式更新说明与下载进度展示;重构 updater 工具,拆分检查与更新流程,新增进度状态管理,更新流程交由弹窗组件统一处理。
|
||||||
|
|
||||||
|
### 🎨 优化
|
||||||
|
|
||||||
|
- 调整主布局样式,使用 CSS 变量适配主题切换与标题栏高度。
|
||||||
|
- 统一标题栏圆角变量使用(`radius-sm`),优化卡片间距等 UI 细节。
|
||||||
|
|
||||||
|
### 🚀 工程
|
||||||
|
|
||||||
|
- 新增 `tauri-plugin-prevent-default` 插件,区分开发/生产模式拦截默认事件。
|
||||||
|
- 优化发布流程:release 工作流自动从 CHANGELOG.md 提取当前 tag 对应版本更新日志作为 Release body。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v0.3.0(2026-08-24 16:52)
|
## v0.3.0(2026-08-24 16:52)
|
||||||
### 🎉 新版本发布!
|
### 🎉 新版本发布!
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 10px;
|
/* border-radius: 10px; */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "stealthreader",
|
"name": "stealthreader",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"@tauri-apps/plugin-updater": "^2.10.1",
|
"@tauri-apps/plugin-updater": "^2.10.1",
|
||||||
"axios": "^1.19.0",
|
"axios": "^1.19.0",
|
||||||
"dayjs": "^1.11.21",
|
"dayjs": "^1.11.21",
|
||||||
|
"markdown-it": "^15.0.0",
|
||||||
"naive-ui": "^2.45.0",
|
"naive-ui": "^2.45.0",
|
||||||
"pinia": "^4.0.3",
|
"pinia": "^4.0.3",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ importers:
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.21
|
specifier: ^1.11.21
|
||||||
version: 1.11.23
|
version: 1.11.23
|
||||||
|
markdown-it:
|
||||||
|
specifier: ^15.0.0
|
||||||
|
version: 15.0.0
|
||||||
naive-ui:
|
naive-ui:
|
||||||
specifier: ^2.45.0
|
specifier: ^2.45.0
|
||||||
version: 2.45.0(vue@3.5.41(typescript@5.6.3))
|
version: 2.45.0(vue@3.5.41(typescript@5.6.3))
|
||||||
|
|
@ -586,6 +589,9 @@ packages:
|
||||||
alien-signals@3.2.1:
|
alien-signals@3.2.1:
|
||||||
resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==}
|
resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==}
|
||||||
|
|
||||||
|
argparse@3.0.0:
|
||||||
|
resolution: {integrity: sha512-BOp5NMrHqKxmq/OLr+clzzrRxgOKSLkcjmkWuChp7Irqwn4s74WjOBPIgWfA/HMcBnVkZ5XEuf9uUqzlpfCQ6A==}
|
||||||
|
|
||||||
ast-kit@2.1.2:
|
ast-kit@2.1.2:
|
||||||
resolution: {integrity: sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g==}
|
resolution: {integrity: sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g==}
|
||||||
engines: {node: '>=20.18.0'}
|
engines: {node: '>=20.18.0'}
|
||||||
|
|
@ -679,6 +685,10 @@ packages:
|
||||||
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||||
engines: {node: '>=0.12'}
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
|
entities@8.0.0:
|
||||||
|
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
|
||||||
es-define-property@1.0.1:
|
es-define-property@1.0.1:
|
||||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -874,6 +884,9 @@ packages:
|
||||||
resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
|
resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
|
|
||||||
|
linkify-it@6.1.0:
|
||||||
|
resolution: {integrity: sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw==}
|
||||||
|
|
||||||
local-pkg@1.2.1:
|
local-pkg@1.2.1:
|
||||||
resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==}
|
resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
@ -894,10 +907,17 @@ packages:
|
||||||
magic-string@1.2.0:
|
magic-string@1.2.0:
|
||||||
resolution: {integrity: sha512-ptco+HFxTLgjafSLim2LojBSwfg5feBjd+SqyiwdGkzC38UPdZy3zgrHMI2CoTf5fJL38tbHMYWVzIH8BxGqJw==}
|
resolution: {integrity: sha512-ptco+HFxTLgjafSLim2LojBSwfg5feBjd+SqyiwdGkzC38UPdZy3zgrHMI2CoTf5fJL38tbHMYWVzIH8BxGqJw==}
|
||||||
|
|
||||||
|
markdown-it@15.0.0:
|
||||||
|
resolution: {integrity: sha512-Lf8ajvVNdRpzSNB4VegxNy7gjs8gU35l4b4+ET49LrQC5PKYwLZ72u60LeJ9gv3qiaesuYjJWCyVeQmv/QWKQw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
math-intrinsics@1.1.0:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
mdurl@2.1.0:
|
||||||
|
resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==}
|
||||||
|
|
||||||
mime-db@1.52.0:
|
mime-db@1.52.0:
|
||||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
@ -990,6 +1010,10 @@ packages:
|
||||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
punycode.js@2.3.1:
|
||||||
|
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
quansync@0.2.11:
|
quansync@0.2.11:
|
||||||
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||||
|
|
||||||
|
|
@ -1032,6 +1056,9 @@ packages:
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
uc.micro@3.0.0:
|
||||||
|
resolution: {integrity: sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw==}
|
||||||
|
|
||||||
ufo@1.6.4:
|
ufo@1.6.4:
|
||||||
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
|
resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
|
||||||
|
|
||||||
|
|
@ -1616,6 +1643,8 @@ snapshots:
|
||||||
|
|
||||||
alien-signals@3.2.1: {}
|
alien-signals@3.2.1: {}
|
||||||
|
|
||||||
|
argparse@3.0.0: {}
|
||||||
|
|
||||||
ast-kit@2.1.2:
|
ast-kit@2.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.29.8
|
'@babel/parser': 7.29.8
|
||||||
|
|
@ -1706,6 +1735,8 @@ snapshots:
|
||||||
|
|
||||||
entities@7.0.1: {}
|
entities@7.0.1: {}
|
||||||
|
|
||||||
|
entities@8.0.0: {}
|
||||||
|
|
||||||
es-define-property@1.0.1: {}
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
es-errors@1.3.0: {}
|
es-errors@1.3.0: {}
|
||||||
|
|
@ -1860,6 +1891,10 @@ snapshots:
|
||||||
lightningcss-win32-arm64-msvc: 1.33.0
|
lightningcss-win32-arm64-msvc: 1.33.0
|
||||||
lightningcss-win32-x64-msvc: 1.33.0
|
lightningcss-win32-x64-msvc: 1.33.0
|
||||||
|
|
||||||
|
linkify-it@6.1.0:
|
||||||
|
dependencies:
|
||||||
|
uc.micro: 3.0.0
|
||||||
|
|
||||||
local-pkg@1.2.1:
|
local-pkg@1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
mlly: 1.8.2
|
mlly: 1.8.2
|
||||||
|
|
@ -1882,8 +1917,19 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
markdown-it@15.0.0:
|
||||||
|
dependencies:
|
||||||
|
argparse: 3.0.0
|
||||||
|
entities: 8.0.0
|
||||||
|
linkify-it: 6.1.0
|
||||||
|
mdurl: 2.1.0
|
||||||
|
punycode.js: 2.3.1
|
||||||
|
uc.micro: 3.0.0
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
|
mdurl@2.1.0: {}
|
||||||
|
|
||||||
mime-db@1.52.0: {}
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
mime-types@2.1.35:
|
mime-types@2.1.35:
|
||||||
|
|
@ -1976,6 +2022,8 @@ snapshots:
|
||||||
|
|
||||||
proxy-from-env@2.1.0: {}
|
proxy-from-env@2.1.0: {}
|
||||||
|
|
||||||
|
punycode.js@2.3.1: {}
|
||||||
|
|
||||||
quansync@0.2.11: {}
|
quansync@0.2.11: {}
|
||||||
|
|
||||||
readdirp@5.1.1: {}
|
readdirp@5.1.1: {}
|
||||||
|
|
@ -2027,6 +2075,8 @@ snapshots:
|
||||||
|
|
||||||
typescript@5.6.3: {}
|
typescript@5.6.3: {}
|
||||||
|
|
||||||
|
uc.micro@3.0.0: {}
|
||||||
|
|
||||||
ufo@1.6.4: {}
|
ufo@1.6.4: {}
|
||||||
|
|
||||||
undici-types@8.3.0: {}
|
undici-types@8.3.0: {}
|
||||||
|
|
|
||||||
|
|
@ -2052,6 +2052,15 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|
@ -4076,6 +4085,7 @@ dependencies = [
|
||||||
"tauri-helper",
|
"tauri-helper",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"tauri-plugin-prevent-default",
|
||||||
"tauri-plugin-process",
|
"tauri-plugin-process",
|
||||||
"tauri-plugin-store",
|
"tauri-plugin-store",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
|
|
@ -4122,6 +4132,27 @@ version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
|
@ -4480,6 +4511,20 @@ dependencies = [
|
||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-prevent-default"
|
||||||
|
version = "5.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "674bac5c9831f4ca7ae42840768ac968b76353dcbe30bb0aad149c56f0be1389"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"itertools",
|
||||||
|
"serde",
|
||||||
|
"strum",
|
||||||
|
"tauri",
|
||||||
|
"thiserror 2.0.20",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-process"
|
name = "tauri-plugin-process"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
|
|
||||||
|
|
@ -41,3 +41,4 @@ regex = "1.13.1"
|
||||||
chardetng = "1.0.0"
|
chardetng = "1.0.0"
|
||||||
tauri-plugin-updater = "2.10.1"
|
tauri-plugin-updater = "2.10.1"
|
||||||
tauri-plugin-process = "2.3.1"
|
tauri-plugin-process = "2.3.1"
|
||||||
|
tauri-plugin-prevent-default = "5.0.2"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ use commands::book::*;
|
||||||
use services::db::init_pool;
|
use services::db::init_pool;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
|
||||||
|
use tauri_plugin_prevent_default::Flags;
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
|
@ -13,6 +15,7 @@ pub fn run() {
|
||||||
.plugin(tauri_plugin_dialog::init()) // 对话框插件
|
.plugin(tauri_plugin_dialog::init()) // 对话框插件
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build()) // 更新插件
|
.plugin(tauri_plugin_updater::Builder::new().build()) // 更新插件
|
||||||
.plugin(tauri_plugin_process::init()) // 进程插件(更新后重启)
|
.plugin(tauri_plugin_process::init()) // 进程插件(更新后重启)
|
||||||
|
.plugin(prevent_default())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let pool = tauri::async_runtime::block_on(async {
|
let pool = tauri::async_runtime::block_on(async {
|
||||||
init_pool(app.handle())
|
init_pool(app.handle())
|
||||||
|
|
@ -28,3 +31,15 @@ pub fn run() {
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 开发模式: 保留DevTools和Reload
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn prevent_default() -> tauri::plugin::TauriPlugin<tauri::Wry> {
|
||||||
|
tauri_plugin_prevent_default::Builder::new().with_flags(Flags::all().difference(Flags::DEV_TOOLS | Flags::RELOAD)).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生产模式: 阻止所有默认事件
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
fn prevent_default() -> tauri::plugin::TauriPlugin<tauri::Wry> {
|
||||||
|
tauri_plugin_prevent_default::init()
|
||||||
|
}
|
||||||
44
src/App.vue
44
src/App.vue
|
|
@ -1,10 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<n-config-provider :locale="zhCN" :data-locale="dateZhCN">
|
<n-config-provider :locale="zhCN" :data-locale="dateZhCN" :theme="isDark ? darkTheme : null">
|
||||||
<n-message-provider>
|
<n-message-provider>
|
||||||
<n-notification-provider>
|
<n-notification-provider>
|
||||||
<n-dialog-provider>
|
<n-dialog-provider>
|
||||||
<GlobalLoading />
|
<n-modal-provider>
|
||||||
<RouterView />
|
<GlobalLoading />
|
||||||
|
<UpdateModal />
|
||||||
|
<RouterView />
|
||||||
|
</n-modal-provider>
|
||||||
</n-dialog-provider>
|
</n-dialog-provider>
|
||||||
</n-notification-provider>
|
</n-notification-provider>
|
||||||
</n-message-provider>
|
</n-message-provider>
|
||||||
|
|
@ -12,15 +15,38 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { zhCN, dateZhCN } from "naive-ui"
|
import { zhCN, dateZhCN, darkTheme } from "naive-ui"
|
||||||
import GlobalLoading from "@/components/GlobalLoading.vue"
|
import GlobalLoading from "@/components/GlobalLoading.vue"
|
||||||
import { checkAndUpdate } from "@/utils/updater"
|
import UpdateModal from "@/components/UpdateModal.vue"
|
||||||
|
import { checkAndPrompt } from "@/utils/updater"
|
||||||
|
import { useSettingStore } from "@/stores/setting"
|
||||||
|
|
||||||
|
const settingStore = useSettingStore()
|
||||||
|
|
||||||
|
const media = window.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
const systemDark = ref(media.matches)
|
||||||
|
|
||||||
|
const themeMode = computed(() => settingStore.appearance.theme)
|
||||||
|
const isDark = computed(() => {
|
||||||
|
return themeMode.value === "dark" || (themeMode.value === "system" && systemDark.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
document.documentElement.setAttribute("data-theme", isDark.value ? "dark" : "light")
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSystemThemeChange = (e: MediaQueryListEvent) => {
|
||||||
|
systemDark.value = e.matches
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkAndUpdate()
|
checkAndPrompt()
|
||||||
|
media.addEventListener("change", onSystemThemeChange)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
media.removeEventListener("change", onSystemThemeChange)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,21 @@
|
||||||
|
|
||||||
--set-item-title-size: 14px;
|
--set-item-title-size: 14px;
|
||||||
--set-item-desc-size: 12px;
|
--set-item-desc-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色主题:通过根节点 data-theme="dark" 覆盖同名变量 */
|
||||||
|
:root[data-theme="dark"] {
|
||||||
|
--color-window-bg: #1b1c20;
|
||||||
|
--color-surface: #26272e;
|
||||||
|
--color-surface-hover: #30323b;
|
||||||
|
|
||||||
|
--color-text-primary: #f0f1f5;
|
||||||
|
--color-text-secondary: #9aa0ab;
|
||||||
|
--color-text-invert: #1d1d1f;
|
||||||
|
|
||||||
|
--color-border: rgba(255, 255, 255, 0.08);
|
||||||
|
--color-border-hover: rgba(102, 126, 234, 0.5);
|
||||||
|
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.35);
|
||||||
|
--shadow-card-hover: 0 10px 28px rgba(0, 0, 0, 0.5);
|
||||||
|
--shadow-focus: 0 4px 16px rgba(102, 126, 234, 0.3);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
<template>
|
||||||
|
<n-modal :show="showUpdateModal" preset="card" :title="modalTitle" :bordered="false" :closable="!busy"
|
||||||
|
:mask-closable="false" :close-on-esc="false" style="width: 430px" @close="cancelUpdate">
|
||||||
|
<template #header-extra>
|
||||||
|
<span v-if="updateInfo" class="update-current">当前 v{{ updateInfo.currentVersion }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="update-body">
|
||||||
|
<n-scrollbar class="update-scroll">
|
||||||
|
<div v-if="renderedBody" class="markdown-body" v-html="renderedBody" />
|
||||||
|
<n-empty v-else description="暂无更新说明" />
|
||||||
|
</n-scrollbar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="downloading || installing" class="update-progress">
|
||||||
|
<n-progress type="line" :percentage="progress" :processing="downloading" indicator-placement="inside"
|
||||||
|
:height="18" />
|
||||||
|
<div class="progress-hint">{{ progressHint }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="update-footer">
|
||||||
|
<n-button quaternary :disabled="busy" @click="skipVersion">跳过此版本</n-button>
|
||||||
|
<div class="update-footer-right">
|
||||||
|
<n-button v-if="!busy" @click="cancelUpdate">取消</n-button>
|
||||||
|
<n-button type="primary" :loading="downloading" @click="startUpdate">
|
||||||
|
{{ installing ? "安装中..." : downloading ? "下载中..." : "立即更新" }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue"
|
||||||
|
import MarkdownIt from "markdown-it"
|
||||||
|
import {
|
||||||
|
showUpdateModal,
|
||||||
|
updateInfo,
|
||||||
|
downloading,
|
||||||
|
installing,
|
||||||
|
progress,
|
||||||
|
downloadedBytes,
|
||||||
|
totalBytes,
|
||||||
|
formatBytes,
|
||||||
|
startUpdate,
|
||||||
|
skipVersion,
|
||||||
|
cancelUpdate,
|
||||||
|
} from "@/utils/updater"
|
||||||
|
|
||||||
|
const md = new MarkdownIt({ html: false, linkify: true })
|
||||||
|
|
||||||
|
const busy = computed(() => downloading.value || installing.value)
|
||||||
|
|
||||||
|
const modalTitle = computed(() =>
|
||||||
|
updateInfo.value ? `发现新版本 v${updateInfo.value.version}` : "发现新版本"
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderedBody = computed(() => md.render(updateInfo.value?.body || ""))
|
||||||
|
|
||||||
|
const progressHint = computed(() => {
|
||||||
|
if (installing.value) return "正在安装更新,即将重启应用..."
|
||||||
|
if (downloading.value) {
|
||||||
|
if (totalBytes.value > 0) {
|
||||||
|
return `正在下载更新 ${progress.value}% · ${formatBytes(downloadedBytes.value)} / ${formatBytes(totalBytes.value)}`
|
||||||
|
}
|
||||||
|
return `正在下载更新... ${formatBytes(downloadedBytes.value)}`
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.update-current {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-body {
|
||||||
|
.update-scroll {
|
||||||
|
height: 340px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body {
|
||||||
|
padding-right: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
:deep(h1),
|
||||||
|
:deep(h2),
|
||||||
|
:deep(h3),
|
||||||
|
:deep(h4),
|
||||||
|
:deep(h5),
|
||||||
|
:deep(h6) {
|
||||||
|
margin: 14px 0 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(h1) {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(h2) {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(h3) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(h4),
|
||||||
|
:deep(h5),
|
||||||
|
:deep(h6) {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(p) {
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(ul),
|
||||||
|
:deep(ol) {
|
||||||
|
margin: 6px 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(li) {
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(strong) {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(code) {
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--color-surface-hover);
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(pre) {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--color-surface-hover);
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(a) {
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(blockquote) {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-left: 3px solid var(--color-border-hover);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(hr) {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(table) {
|
||||||
|
width: 100%;
|
||||||
|
margin: 8px 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(th),
|
||||||
|
:deep(td) {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(th) {
|
||||||
|
background: var(--color-surface-hover);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-progress {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-hint {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-footer-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -23,9 +23,13 @@ const isMacos = navigator.userAgent.includes("Mac OS X")
|
||||||
.container {
|
.container {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: rgba(255, 255, 255, 1);
|
background: var(--color-window-bg);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
// border: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
:deep(.n-layout) {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|
@ -35,6 +39,6 @@ const isMacos = navigator.userAgent.includes("Mac OS X")
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
height: calc(100vh - 40px);
|
height: calc(100vh - var(--titlebar-height));
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ function close() {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 50%;
|
border-radius: var(--radius-sm);
|
||||||
border: 0.5px solid rgba(0, 0, 0, 0.15);
|
border: 0.5px solid rgba(0, 0, 0, 0.15);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ function close() {
|
||||||
.dot {
|
.dot {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
border-radius: 50%;
|
border-radius: var(--radius-sm);
|
||||||
background: var(--color-accent-gradient);
|
background: var(--color-accent-gradient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,11 @@ declare module 'vue' {
|
||||||
NLayoutHeader: typeof import('naive-ui')['NLayoutHeader']
|
NLayoutHeader: typeof import('naive-ui')['NLayoutHeader']
|
||||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
NModal: typeof import('naive-ui')['NModal']
|
NModal: typeof import('naive-ui')['NModal']
|
||||||
|
NModalProvider: typeof import('naive-ui')['NModalProvider']
|
||||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||||
NPagination: typeof import('naive-ui')['NPagination']
|
NPagination: typeof import('naive-ui')['NPagination']
|
||||||
NPopover: typeof import('naive-ui')['NPopover']
|
NPopover: typeof import('naive-ui')['NPopover']
|
||||||
|
NProgress: typeof import('naive-ui')['NProgress']
|
||||||
NRadioButton: typeof import('naive-ui')['NRadioButton']
|
NRadioButton: typeof import('naive-ui')['NRadioButton']
|
||||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
|
|
@ -47,5 +49,6 @@ declare module 'vue' {
|
||||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
UpdateModal: typeof import('./../components/UpdateModal.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
declare module "markdown-it" {
|
||||||
|
interface MarkdownItOptions {
|
||||||
|
html?: boolean
|
||||||
|
xhtmlOut?: boolean
|
||||||
|
breaks?: boolean
|
||||||
|
langPrefix?: string
|
||||||
|
linkify?: boolean
|
||||||
|
typographer?: boolean
|
||||||
|
quotes?: string | string[]
|
||||||
|
highlight?: (str: string, lang: string, attrs: string) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
class MarkdownIt {
|
||||||
|
constructor(options?: MarkdownItOptions)
|
||||||
|
set(options: MarkdownItOptions): this
|
||||||
|
render(src: string, env?: unknown): string
|
||||||
|
parse(src: string, env?: unknown): unknown[]
|
||||||
|
options: MarkdownItOptions
|
||||||
|
renderer: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MarkdownIt
|
||||||
|
}
|
||||||
|
|
@ -1,54 +1,137 @@
|
||||||
import { check, type Update } from "@tauri-apps/plugin-updater"
|
import { ref } from "vue"
|
||||||
import { ask } from "@tauri-apps/plugin-dialog"
|
import { check, type Update, type DownloadEvent } from "@tauri-apps/plugin-updater"
|
||||||
import { relaunch } from "@tauri-apps/plugin-process"
|
import { relaunch } from "@tauri-apps/plugin-process"
|
||||||
|
|
||||||
/**
|
export type CheckPromptResult = "none" | "error" | "prompting"
|
||||||
* 更新检查结果
|
|
||||||
* - updated:已确认更新并开始安装(随后应用重启)
|
|
||||||
* - none:当前已是最新版本
|
|
||||||
* - cancelled:存在更新但用户取消
|
|
||||||
* - error:检查或安装过程出错
|
|
||||||
*/
|
|
||||||
export type CheckUpdateResult = "updated" | "none" | "cancelled" | "error"
|
|
||||||
|
|
||||||
/**
|
// 当前待更新的 Update 对象(非响应式,避免代理破坏资源句柄)
|
||||||
* 仅检查是否有可用更新,返回 Update 对象或 null
|
let currentUpdate: Update | null = null
|
||||||
*/
|
|
||||||
export async function checkUpdate(): Promise<Update | null> {
|
export const checking = ref(false)
|
||||||
return await check()
|
export const downloading = ref(false)
|
||||||
|
export const installing = ref(false)
|
||||||
|
export const showUpdateModal = ref(false)
|
||||||
|
export const progress = ref(0)
|
||||||
|
export const downloadedBytes = ref(0)
|
||||||
|
export const totalBytes = ref(0)
|
||||||
|
|
||||||
|
export const updateInfo = ref<{
|
||||||
|
currentVersion: string
|
||||||
|
version: string
|
||||||
|
date?: string
|
||||||
|
body?: string
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
|
const SKIPPED_KEY = "skippedUpdateVersion"
|
||||||
|
|
||||||
|
function getSkippedVersion(): string | null {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem(SKIPPED_KEY)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSkippedVersion(version: string) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(SKIPPED_KEY, version)
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatBytes(bytes: number): string {
|
||||||
|
if (bytes <= 0) return "0 B"
|
||||||
|
const units = ["B", "KB", "MB", "GB"]
|
||||||
|
let v = bytes
|
||||||
|
let i = 0
|
||||||
|
while (v >= 1024 && i < units.length - 1) {
|
||||||
|
v /= 1024
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return `${v.toFixed(v >= 10 || i === 0 ? 0 : 1)} ${units[i]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 询问用户是否立即更新,确认后下载安装并重启应用
|
* 检查更新,若有可用更新则展示更新弹窗
|
||||||
* @param update 更新对象
|
* @returns none=无更新/已跳过;error=检查出错;prompting=已展示弹窗等待用户操作
|
||||||
* @returns true=已确认并开始安装;false=用户取消
|
|
||||||
*/
|
*/
|
||||||
export async function promptAndInstall(update: Update): Promise<boolean> {
|
export async function checkAndPrompt(): Promise<CheckPromptResult> {
|
||||||
const yes = await ask(
|
if (checking.value || downloading.value || installing.value) return "none"
|
||||||
`发现新版本 ${update.version},是否现在更新?\n\n${update.body || ""}`,
|
checking.value = true
|
||||||
{ title: "发现更新", kind: "info" }
|
|
||||||
)
|
|
||||||
if (!yes) return false
|
|
||||||
|
|
||||||
await update.downloadAndInstall()
|
|
||||||
await relaunch()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 一键检查并更新(完整流程)。
|
|
||||||
* 适合应用启动时静默调用:无更新、用户取消或出错均不打扰用户。
|
|
||||||
* @returns 更新结果状态
|
|
||||||
*/
|
|
||||||
export async function checkAndUpdate(): Promise<CheckUpdateResult> {
|
|
||||||
try {
|
try {
|
||||||
const update = await check()
|
const update = await check()
|
||||||
if (!update) return "none"
|
if (!update) return "none"
|
||||||
|
if (getSkippedVersion() === update.version) return "none"
|
||||||
|
|
||||||
const installed = await promptAndInstall(update)
|
currentUpdate = update
|
||||||
return installed ? "updated" : "cancelled"
|
updateInfo.value = {
|
||||||
|
currentVersion: update.currentVersion,
|
||||||
|
version: update.version,
|
||||||
|
date: update.date,
|
||||||
|
body: update.body,
|
||||||
|
}
|
||||||
|
progress.value = 0
|
||||||
|
downloadedBytes.value = 0
|
||||||
|
totalBytes.value = 0
|
||||||
|
showUpdateModal.value = true
|
||||||
|
return "prompting"
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("检查更新失败:", error)
|
console.error("检查更新失败:", error)
|
||||||
return "error"
|
return "error"
|
||||||
|
} finally {
|
||||||
|
checking.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始下载并安装更新(带进度回调),完成后重启应用
|
||||||
|
*/
|
||||||
|
export async function startUpdate(): Promise<void> {
|
||||||
|
if (!currentUpdate || downloading.value || installing.value) return
|
||||||
|
downloading.value = true
|
||||||
|
progress.value = 0
|
||||||
|
downloadedBytes.value = 0
|
||||||
|
totalBytes.value = 0
|
||||||
|
try {
|
||||||
|
await currentUpdate.download((event: DownloadEvent) => {
|
||||||
|
if (event.event === "Started") {
|
||||||
|
totalBytes.value = event.data.contentLength ?? 0
|
||||||
|
} else if (event.event === "Progress") {
|
||||||
|
downloadedBytes.value += event.data.chunkLength
|
||||||
|
if (totalBytes.value > 0) {
|
||||||
|
progress.value = Math.min(
|
||||||
|
100,
|
||||||
|
Math.round((downloadedBytes.value / totalBytes.value) * 100)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
downloading.value = false
|
||||||
|
installing.value = true
|
||||||
|
await currentUpdate.install()
|
||||||
|
await relaunch()
|
||||||
|
} catch (error) {
|
||||||
|
console.error("更新失败:", error)
|
||||||
|
downloading.value = false
|
||||||
|
installing.value = false
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跳过当前版本:记录已跳过版本并关闭弹窗 */
|
||||||
|
export function skipVersion() {
|
||||||
|
if (currentUpdate) {
|
||||||
|
setSkippedVersion(currentUpdate.version)
|
||||||
|
}
|
||||||
|
currentUpdate = null
|
||||||
|
updateInfo.value = null
|
||||||
|
showUpdateModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消更新:关闭弹窗 */
|
||||||
|
export function cancelUpdate() {
|
||||||
|
currentUpdate = null
|
||||||
|
updateInfo.value = null
|
||||||
|
showUpdateModal.value = false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -336,6 +336,7 @@ onMounted(() => {
|
||||||
box-shadow: var(--shadow-card);
|
box-shadow: var(--shadow-card);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
transition: color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ import {
|
||||||
ThunderboltOutlined,
|
ThunderboltOutlined,
|
||||||
} from "@vicons/antd"
|
} from "@vicons/antd"
|
||||||
import appIcon from "../../../app-icon.svg"
|
import appIcon from "../../../app-icon.svg"
|
||||||
import { checkAndUpdate } from "@/utils/updater"
|
import { checkAndPrompt } from "@/utils/updater"
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const year = new Date().getFullYear()
|
const year = new Date().getFullYear()
|
||||||
|
|
@ -92,13 +92,13 @@ const onCheckUpdate = async () => {
|
||||||
if (checkingUpdate.value) return
|
if (checkingUpdate.value) return
|
||||||
checkingUpdate.value = true
|
checkingUpdate.value = true
|
||||||
try {
|
try {
|
||||||
const result = await checkAndUpdate()
|
const result = await checkAndPrompt()
|
||||||
if (result === "none") {
|
if (result === "none") {
|
||||||
message.success("当前已是最新版本")
|
message.success("当前已是最新版本")
|
||||||
} else if (result === "error") {
|
} else if (result === "error") {
|
||||||
message.error("检查更新失败,请稍后重试")
|
message.error("检查更新失败,请稍后重试")
|
||||||
}
|
}
|
||||||
// updated:即将重启应用;cancelled:用户取消,无需提示
|
// prompting:已展示更新弹窗,由弹窗处理后续流程
|
||||||
} finally {
|
} finally {
|
||||||
checkingUpdate.value = false
|
checkingUpdate.value = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ const settingStore = useSettingStore()
|
||||||
const theme = ref(settingStore.appearance.theme)
|
const theme = ref(settingStore.appearance.theme)
|
||||||
const showShadow = ref(settingStore.appearance.showShadow)
|
const showShadow = ref(settingStore.appearance.showShadow)
|
||||||
|
|
||||||
|
watch(theme, (newVal) => {
|
||||||
|
settingStore.appearance.theme = newVal
|
||||||
|
})
|
||||||
|
|
||||||
watch(showShadow, (newVal) => {
|
watch(showShadow, (newVal) => {
|
||||||
settingStore.appearance.showShadow = newVal
|
settingStore.appearance.showShadow = newVal
|
||||||
getCurrentWindow().setShadow(newVal)
|
getCurrentWindow().setShadow(newVal)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue