Compare commits

..

No commits in common. "7b06516cbe2c2d04d54bf31ab29ace47a7cf1a55" and "3b966c5655e44e6457f171a448b196917fad0af2" have entirely different histories.

17 changed files with 90 additions and 958 deletions

View File

@ -38,16 +38,6 @@
- 窗口支持透明背景、置顶显示、圆角毛玻璃外观
- 设置通过 Pinia + 持久化插件跨会话保存
## 界面预览
| 书架管理 | 书籍详情 |
| --- | --- |
| ![书架管理](images/1.png) | ![书籍章节详情](images/3.png) |
| 幽灵阅读模式 | 阅读设置 |
| --- | --- |
| ![幽灵阅读模式](images/4.png) | ![阅读设置](images/2.png) |
## 技术栈
| 层 | 技术 |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

View File

@ -1,7 +1,7 @@
{
"name": "stealthreader",
"private": true,
"version": "0.3.0",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
@ -13,9 +13,7 @@
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.7.2",
"@tauri-apps/plugin-opener": "^2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-store": "~2.4.4",
"@tauri-apps/plugin-updater": "^2.10.1",
"axios": "^1.19.0",
"dayjs": "^1.11.21",
"naive-ui": "^2.45.0",

View File

@ -17,18 +17,12 @@ importers:
'@tauri-apps/plugin-opener':
specifier: ^2
version: 2.5.4
'@tauri-apps/plugin-process':
specifier: ^2.3.1
version: 2.3.1
'@tauri-apps/plugin-store':
specifier: ~2.4.4
version: 2.4.4
'@tauri-apps/plugin-updater':
specifier: ^2.10.1
version: 2.10.1
axios:
specifier: ^1.19.0
version: 1.19.0(debug@4.4.3)
version: 1.19.0
dayjs:
specifier: ^1.11.21
version: 1.11.23
@ -446,15 +440,9 @@ packages:
'@tauri-apps/plugin-opener@2.5.4':
resolution: {integrity: sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==}
'@tauri-apps/plugin-process@2.3.1':
resolution: {integrity: sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==}
'@tauri-apps/plugin-store@2.4.4':
resolution: {integrity: sha512-oxSMaj/QpVfJcBMYX5aOQV94fWvga0MwQMfD6TLlbK2dh+ShPWAzefd8HWXhvOKjPRJdGVAkW7ZGO76JzzjaDA==}
'@tauri-apps/plugin-updater@2.10.1':
resolution: {integrity: sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==}
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
@ -517,11 +505,6 @@ packages:
'@volar/typescript@2.4.28':
resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@vue-macros/common@3.1.4':
resolution: {integrity: sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==}
@ -1446,18 +1429,10 @@ snapshots:
dependencies:
'@tauri-apps/api': 2.11.1
'@tauri-apps/plugin-process@2.3.1':
dependencies:
'@tauri-apps/api': 2.11.1
'@tauri-apps/plugin-store@2.4.4':
dependencies:
'@tauri-apps/api': 2.11.1
'@tauri-apps/plugin-updater@2.10.1':
dependencies:
'@tauri-apps/api': 2.11.1
'@types/estree@1.0.9': {}
'@types/jsesc@2.5.1': {}
@ -1507,13 +1482,11 @@ snapshots:
'@volar/source-map@2.4.28': {}
'@volar/typescript@2.4.28(typescript@5.6.3)':
'@volar/typescript@2.4.28':
dependencies:
'@volar/language-core': 2.4.28
path-browserify: 1.0.1
vscode-uri: 3.1.0
optionalDependencies:
typescript: 5.6.3
'@vue-macros/common@3.1.4(vue@3.5.41(typescript@5.6.3))':
dependencies:
@ -1636,9 +1609,9 @@ snapshots:
asynckit@0.4.0: {}
axios@1.19.0(debug@4.4.3):
axios@1.19.0:
dependencies:
follow-redirects: 1.16.0(debug@4.4.3)
follow-redirects: 1.16.0
form-data: 4.0.6
https-proxy-agent: 5.0.1
proxy-from-env: 2.1.0
@ -1737,9 +1710,7 @@ snapshots:
optionalDependencies:
picomatch: 4.0.5
follow-redirects@1.16.0(debug@4.4.3):
optionalDependencies:
debug: 4.4.3
follow-redirects@1.16.0: {}
form-data@4.0.6:
dependencies:
@ -2178,7 +2149,7 @@ snapshots:
vue-tsc@3.3.10(typescript@5.6.3):
dependencies:
'@volar/typescript': 2.4.28(typescript@5.6.3)
'@volar/typescript': 2.4.28
'@vue/language-core': 3.3.10
typescript: 5.6.3

349
src-tauri/Cargo.lock generated
View File

@ -53,15 +53,6 @@ version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "async-broadcast"
version = "0.7.2"
@ -834,17 +825,6 @@ dependencies = [
"serde_core",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "derive_more"
version = "2.1.1"
@ -1173,16 +1153,6 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.11"
@ -1813,21 +1783,6 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
@ -2150,36 +2105,6 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "jni"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
"cfg-if",
"combine",
"jni-macros",
"jni-sys 0.4.1",
"log",
"simd_cesu8",
"thiserror 2.0.20",
"walkdir",
"windows-link 0.2.1",
]
[[package]]
name = "jni-macros"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"simd_cesu8",
"syn 2.0.119",
]
[[package]]
name = "jni-sys"
version = "0.3.1"
@ -2390,12 +2315,6 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minisign-verify"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@ -2661,18 +2580,6 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-osa-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
dependencies = [
"bitflags 2.13.1",
"objc2",
"objc2-app-kit",
"objc2-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
@ -2806,20 +2713,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "osakit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
dependencies = [
"objc2",
"objc2-foundation",
"objc2-osa-kit",
"serde",
"serde_json",
"thiserror 2.0.20",
]
[[package]]
name = "pango"
version = "0.18.3"
@ -3263,20 +3156,15 @@ dependencies = [
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
@ -3312,20 +3200,6 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustc-hash"
version = "2.1.3"
@ -3354,79 +3228,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pki-types"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
dependencies = [
"core-foundation",
"core-foundation-sys",
"jni 0.22.4",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.23"
@ -3774,22 +3575,6 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "simd_cesu8"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
dependencies = [
"rustc_version",
"simdutf8",
]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "siphasher"
version = "1.0.3"
@ -4076,9 +3861,7 @@ dependencies = [
"tauri-helper",
"tauri-plugin-dialog",
"tauri-plugin-opener",
"tauri-plugin-process",
"tauri-plugin-store",
"tauri-plugin-updater",
]
[[package]]
@ -4122,12 +3905,6 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "swift-rs"
version = "1.0.7"
@ -4222,7 +3999,7 @@ dependencies = [
"gdkwayland-sys",
"gdkx11-sys",
"gtk",
"jni 0.21.1",
"jni",
"libc",
"log",
"ndk",
@ -4255,17 +4032,6 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "tar"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
@ -4289,7 +4055,7 @@ dependencies = [
"gtk",
"heck 0.5.0",
"http",
"jni 0.21.1",
"jni",
"libc",
"log",
"mime",
@ -4480,16 +4246,6 @@ dependencies = [
"zbus",
]
[[package]]
name = "tauri-plugin-process"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a"
dependencies = [
"tauri",
"tauri-plugin",
]
[[package]]
name = "tauri-plugin-store"
version = "2.4.4"
@ -4506,39 +4262,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
dependencies = [
"base64 0.22.1",
"dirs",
"flate2",
"futures-util",
"http",
"infer",
"log",
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest",
"rustls",
"semver",
"serde",
"serde_json",
"tar",
"tauri",
"tauri-plugin",
"tempfile",
"thiserror 2.0.20",
"time",
"tokio",
"url",
"windows-sys 0.60.2",
"zip",
]
[[package]]
name = "tauri-runtime"
version = "2.11.3"
@ -4549,7 +4272,7 @@ dependencies = [
"dpi",
"gtk",
"http",
"jni 0.21.1",
"jni",
"objc2",
"objc2-ui-kit",
"objc2-web-kit",
@ -4572,7 +4295,7 @@ checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f"
dependencies = [
"gtk",
"http",
"jni 0.21.1",
"jni",
"log",
"objc2",
"objc2-app-kit",
@ -4809,16 +4532,6 @@ dependencies = [
"syn 3.0.3",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.19"
@ -5165,12 +4878,6 @@ version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.8"
@ -5420,15 +5127,6 @@ dependencies = [
"system-deps",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webview2-com"
version = "0.38.2"
@ -5665,15 +5363,6 @@ dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@ -5969,7 +5658,7 @@ dependencies = [
"gtk",
"http",
"javascriptcore-rs",
"jni 0.21.1",
"jni",
"libc",
"ndk",
"objc2",
@ -6016,16 +5705,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]]
name = "yoke"
version = "0.8.3"
@ -6140,12 +5819,6 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
[[package]]
name = "zerotrie"
version = "0.2.5"
@ -6179,18 +5852,6 @@ dependencies = [
"syn 3.0.3",
]
[[package]]
name = "zip"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
dependencies = [
"arbitrary",
"crc32fast",
"indexmap 2.14.0",
"memchr",
]
[[package]]
name = "zmij"
version = "1.0.23"

View File

@ -39,5 +39,3 @@ tauri-plugin-dialog = "2.7.2"
encoding_rs = "0.8.35"
regex = "1.13.1"
chardetng = "1.0.0"
tauri-plugin-updater = "2.10.1"
tauri-plugin-process = "2.3.1"

View File

@ -13,10 +13,6 @@
"core:window:allow-set-size",
"opener:default",
"dialog:default",
"dialog:allow-open",
"updater:default",
"updater:allow-check",
"process:default",
"process:allow-restart"
"dialog:allow-open"
]
}

View File

@ -1,23 +1,19 @@
mod commands;
mod models;
mod services;
mod models;
use tauri::Manager;
use commands::book::*;
use services::db::init_pool;
use tauri::Manager;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init()) // 对话框插件
.plugin(tauri_plugin_updater::Builder::new().build()) // 更新插件
.plugin(tauri_plugin_process::init()) // 进程插件(更新后重启)
.plugin(tauri_plugin_dialog::init())
.setup(|app| {
let pool = tauri::async_runtime::block_on(async {
init_pool(app.handle())
.await
.expect("sqlite数据库连接池初始化失败!")
init_pool(app.handle()).await.expect("sqlite数据库连接池初始化失败!")
});
app.manage(pool);
Ok(())

View File

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "StealthReader",
"version": "../package.json",
"productName": "stealthreader",
"version": "0.2.0",
"identifier": "StealthReader",
"build": {
"beforeDevCommand": "pnpm dev",
@ -32,16 +32,13 @@
},
"bundle": {
"active": true,
"createUpdaterArtifacts": true,
"targets": "all",
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"]
},
"plugins": {
"updater": {
"active": true,
"dialog": true,
"endpoints": ["https://github.com/SparksFly91/StealthReader/releases/latest/download/latest.json"],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDYyQjM0MUIxMDNGRkMxOUQKUldTZHdmOERzVUd6WWx5TzJlczBjU2dWSXdzSjhwVjRtOFJZUDJ2ajB1MHdTS1p3NmhuZlIwNloK"
}
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

View File

@ -14,11 +14,6 @@
<script setup lang="ts">
import { zhCN, dateZhCN } from "naive-ui"
import GlobalLoading from "@/components/GlobalLoading.vue"
import { checkAndUpdate } from "@/utils/updater"
onMounted(() => {
checkAndUpdate()
})
</script>
<style scoped>

View File

@ -1,54 +0,0 @@
import { check, type Update } from "@tauri-apps/plugin-updater"
import { ask } from "@tauri-apps/plugin-dialog"
import { relaunch } from "@tauri-apps/plugin-process"
/**
*
* - updated
* - none
* - cancelled
* - error
*/
export type CheckUpdateResult = "updated" | "none" | "cancelled" | "error"
/**
* Update null
*/
export async function checkUpdate(): Promise<Update | null> {
return await check()
}
/**
*
* @param update
* @returns true=false=
*/
export async function promptAndInstall(update: Update): Promise<boolean> {
const yes = await ask(
`发现新版本 ${update.version},是否现在更新?\n\n${update.body || ""}`,
{ title: "发现更新", kind: "info" }
)
if (!yes) return false
await update.downloadAndInstall()
await relaunch()
return true
}
/**
*
*
* @returns
*/
export async function checkAndUpdate(): Promise<CheckUpdateResult> {
try {
const update = await check()
if (!update) return "none"
const installed = await promptAndInstall(update)
return installed ? "updated" : "cancelled"
} catch (error) {
console.error("检查更新失败:", error)
return "error"
}
}

View File

@ -20,24 +20,10 @@
<n-descriptions-item label="总章节">{{ book?.total_chapters ?? 0 }}</n-descriptions-item>
<n-descriptions-item label="总字数">{{ formatChars(book?.total_chars) }}</n-descriptions-item>
<n-descriptions-item label="导入时间">{{ formatTime(book?.create_time) }}</n-descriptions-item>
</n-descriptions>
<div class="progress-row">
<n-button v-if="continueInfo" class="continue-btn" @click="continueRead">
<template #default>
<div class="continue-content">
<div class="continue-action">点击继续阅读</div>
<div class="continue-main">
<span class="continue-prefix">阅读到</span>
<span class="continue-title" :title="continueInfo.title">{{ continueTitle }}</span>
<span class="continue-percent">{{ continuePercent }}%</span>
</div>
<div class="continue-time">上次阅读 {{ formatTime(book?.last_read_time) }}</div>
</div>
</template>
</n-button>
<n-descriptions-item label="上次阅读">{{ formatTime(book?.last_read_time) }}</n-descriptions-item>
<n-descriptions-item label="阅读进度">{{ readProgress }}</n-descriptions-item>
<n-descriptions-item label="简介" :span="2">
<n-popover
class="intro-popover-wrap"
trigger="hover"
placement="bottom-start"
:show-arrow="true"
@ -57,7 +43,8 @@
<div class="intro-popover">{{ book?.introduction || "暂无简介" }}</div>
</n-scrollbar>
</n-popover>
</div>
</n-descriptions-item>
</n-descriptions>
</div>
</div>
</section>
@ -150,7 +137,6 @@ const page = ref(1)
const limit = ref(50)
const loadingChapters = ref(false)
const keyword = ref("")
const lastReadChapter = ref<Chapters | null>(null)
const goBack = () => router.back()
@ -158,30 +144,11 @@ const readChapter = (ch: Chapters) => {
router.push({ name: "Reader", query: { id: ch.id } })
}
const continueRead = () => {
const ch = lastReadChapter.value
if (ch) {
router.push({ name: "Reader", query: { id: ch.id } })
}
}
const continueInfo = computed(() => {
const readProgress = computed(() => {
const b = book.value
if (!b || b.last_read_chapter_id <= 0) return null
return lastReadChapter.value
})
const continueTitle = computed(() => {
const t = lastReadChapter.value?.title
if (!t) return ""
return t.length > 5 ? `${t.slice(0, 5)}` : t
})
const continuePercent = computed(() => {
const b = book.value
const ch = lastReadChapter.value
if (!b || b.total_chapters <= 0 || !ch) return 0
return Math.round((ch.number / b.total_chapters) * 100)
if (!b || b.total_chapters <= 0) return "—"
const percent = Math.round((b.last_read_chapter_id / b.total_chapters) * 100)
return `${b.last_read_chapter_id} 章 · ${percent}%`
})
const formatChars = (chars?: number) => {
@ -201,25 +168,11 @@ const loadBook = async () => {
const res = await withLoading(() => BookApi.detail(bookId.value), "加载中...")
if (res.code === 0) {
book.value = res.data
if (res.data.last_read_chapter_id > 0) {
await loadLastReadChapter(res.data.last_read_chapter_id)
}
} else {
message.error(res.msg)
}
}
const loadLastReadChapter = async (chapterId: number) => {
try {
const res = await BookApi.chapterDetail(chapterId)
if (res.code === 0) {
lastReadChapter.value = res.data
}
} catch {
lastReadChapter.value = null
}
}
const loadChapters = async () => {
if (!bookId.value) return
loadingChapters.value = true
@ -324,99 +277,17 @@ onMounted(() => {
text-overflow: ellipsis;
}
.intro-popover-wrap {
flex: 1;
min-width: 0;
}
.intro-preview {
box-sizing: border-box;
height: var(--progress-height);
min-width: 0;
line-height: 1.6;
font-size: 13px;
color: var(--color-text-secondary);
word-break: break-all;
white-space: pre-wrap;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
cursor: help;
padding: 10px 0;
border-radius: var(--radius-sm);
}
.progress-row {
--progress-height: 84px;
display: flex;
align-items: flex-start;
gap: 16px;
}
.continue-btn {
box-sizing: border-box;
flex-shrink: 0;
width: 220px;
height: var(--progress-height);
border-radius: var(--radius-card);
padding: 10px 16px;
text-align: left;
transition: box-shadow 0.2s ease, background-color 0.2s ease;
&:hover {
box-shadow: var(--shadow-card-hover);
}
:deep(.n-button__content) {
width: 100%;
height: 100%;
}
}
.continue-content {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: 4px;
width: 100%;
height: 100%;
}
.continue-action {
font-size: 14px;
font-weight: 600;
line-height: 1.4;
color: var(--color-text-primary);
}
.continue-main {
display: flex;
align-items: baseline;
gap: 6px;
font-size: 13px;
line-height: 1.4;
}
.continue-prefix {
color: var(--color-text-secondary);
}
.continue-title {
max-width: 5em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--color-text-primary);
}
.continue-percent {
font-weight: 700;
color: var(--color-accent);
}
.continue-time {
font-size: 12px;
line-height: 1.4;
color: var(--color-text-secondary);
}
:global(.intro-popover-scrollbar .n-scrollbar-container) {

View File

@ -1,9 +1,7 @@
<template>
<div class="glass-panel" :style="readerStyle" data-tauri-drag-region @mousedown="onWindowMouseDown">
<div class="reading-wrap">
<div class="chapter-name" :class="{ 'chapter-name--hidden': currentPage !== 0 }">
{{ chapter?.title || "加载中..." }}
</div>
<div v-if="currentPage == 0" class="chapter-name">{{ chapter?.title || "加载中..." }}</div>
<div ref="pageEl" class="page-body" @click="onPageClick">
<div class="page-text">{{ currentText }}</div>
@ -254,10 +252,6 @@ onUnmounted(() => {
margin-bottom: 20px;
}
.chapter-name--hidden {
visibility: hidden;
}
.page-body {
flex: 1;
min-height: 0;

View File

@ -1,156 +1,18 @@
<template>
<div class="setting-page">
<h2 class="page-title">关于</h2>
<p class="page-desc">关于幽灵阅读器</p>
<p class="page-desc">关于本应用的信息</p>
<div class="about-hero">
<div class="about-logo">
<img :src="appIcon" alt="幽灵阅读器" />
</div>
<div class="about-info">
<div class="about-name">幽灵阅读器</div>
<div class="about-subname">Stealth Reader</div>
<div class="about-meta">
<div class="about-version">版本 {{ version || "0.0.0" }}</div>
<n-button
class="check-update-btn"
size="tiny"
secondary
round
:loading="checkingUpdate"
@click="onCheckUpdate"
>
<template #icon>
<n-icon :component="SyncOutlined" />
</template>
{{ checkingUpdate ? "检查中..." : "检查更新" }}
</n-button>
</div>
</div>
</div>
<div class="about-intro">
一款安静本地优先的桌面小说阅读器轻量纯本地无账号无广告专注好好读一本书
</div>
<div class="about-section">
<div class="section-title">核心特性</div>
<div class="feature-list">
<div v-for="f in features" :key="f.title" class="feature-item">
<span class="feature-icon" :style="{ backgroundColor: f.bg }">
<n-icon :component="f.icon" :size="16" />
</span>
<div class="feature-body">
<div class="feature-title">{{ f.title }}</div>
<div class="feature-desc">{{ f.desc }}</div>
</div>
</div>
</div>
</div>
<div class="about-section">
<div class="section-title">技术栈</div>
<div class="tech-list">
<div v-for="t in tech" :key="t.name" class="tech-item">
<span class="tech-name">{{ t.name }}</span>
<span class="tech-desc">{{ t.desc }}</span>
</div>
</div>
</div>
<div class="about-footer">
<span>© {{ year }} Stealth Reader</span>
<span>Apache-2.0 License</span>
<div class="about-card">
<div class="app-logo"></div>
<div class="app-name">幽灵阅读器</div>
<div class="app-version">版本 0.1.0</div>
<div class="app-desc">一款安静沉浸的本地阅读器</div>
</div>
</div>
</template>
<script setup lang="ts">
import { getVersion } from "@tauri-apps/api/app"
import {
BookOutlined,
FileTextOutlined,
FontColorsOutlined,
LockOutlined,
ScanOutlined,
SyncOutlined,
ThunderboltOutlined,
} from "@vicons/antd"
import appIcon from "../../../app-icon.svg"
import { checkAndUpdate } from "@/utils/updater"
const message = useMessage()
const year = new Date().getFullYear()
const version = ref("")
const checkingUpdate = ref(false)
getVersion().then((v) => {
version.value = v
})
const onCheckUpdate = async () => {
if (checkingUpdate.value) return
checkingUpdate.value = true
try {
const result = await checkAndUpdate()
if (result === "none") {
message.success("当前已是最新版本")
} else if (result === "error") {
message.error("检查更新失败,请稍后重试")
}
// updatedcancelled
} finally {
checkingUpdate.value = false
}
}
const features = [
{
title: "本地优先",
desc: "纯本地存储,数据不离开你的设备",
icon: LockOutlined,
bg: "color-mix(in srgb, #667eea 14%, transparent)",
},
{
title: "智能分章",
desc: "自动识别「第X章」「Chapter N」等章节格式",
icon: ScanOutlined,
bg: "color-mix(in srgb, #10b981 14%, transparent)",
},
{
title: "自动编码检测",
desc: "GBK / UTF-8 等中文编码自动识别",
icon: FileTextOutlined,
bg: "color-mix(in srgb, #f59e0b 14%, transparent)",
},
{
title: "沉浸阅读",
desc: "可拖拽窗口、透明背景、翻页阅读",
icon: BookOutlined,
bg: "color-mix(in srgb, #ec4899 14%, transparent)",
},
{
title: "外观自定义",
desc: "字体大小 / 颜色 / 背景随心调整",
icon: FontColorsOutlined,
bg: "color-mix(in srgb, #8b5cf6 14%, transparent)",
},
{
title: "轻量快速",
desc: "Tauri 2 构建,内存占用远低于 Electron",
icon: ThunderboltOutlined,
bg: "color-mix(in srgb, #06b6d4 14%, transparent)",
},
]
const tech = [
{ name: "桌面框架", desc: "Tauri 2 + Rust" },
{ name: "前端框架", desc: "Vue 3 + TypeScript" },
{ name: "UI 组件", desc: "Naive UI" },
{ name: "数据存储", desc: "SQLite (sqlx)" },
{ name: "编码检测", desc: "chardetng" },
]
</script>
<script setup lang="ts"></script>
<style lang="scss" scoped>
.setting-page {
@ -171,185 +33,48 @@ const tech = [
color: var(--color-text-secondary);
}
.about-hero {
display: flex;
.about-card {
display: grid;
grid-template-columns: 52px minmax(0, 1fr);
column-gap: 14px;
align-items: center;
gap: 16px;
padding: 20px 16px;
min-height: 108px;
padding: 18px 16px;
border-top: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);
}
.about-logo {
width: 64px;
height: 64px;
flex-shrink: 0;
border-radius: 16px;
overflow: hidden;
box-shadow: var(--shadow-card);
img {
width: 100%;
height: 100%;
display: block;
}
}
.about-info {
min-width: 0;
}
.about-name {
font-size: 18px;
font-weight: 700;
color: var(--color-text-primary);
}
.about-subname {
font-size: 13px;
color: var(--color-text-secondary);
margin-top: 1px;
}
.about-version {
display: inline-block;
margin-top: 6px;
padding: 1px 8px;
border-radius: 999px;
background-color: color-mix(in srgb, var(--color-accent) 12%, transparent);
color: var(--color-accent);
font-size: 12px;
font-weight: 600;
}
.about-meta {
display: flex;
align-items: center;
gap: 10px;
}
.check-update-btn {
margin-top: 6px;
font-weight: 500;
:deep(.n-button__icon) {
margin-right: 4px;
}
}
.about-intro {
margin: 18px 0;
padding: 14px 16px;
border-radius: var(--radius-sm);
background-color: var(--color-window-bg);
font-size: 13px;
line-height: 1.7;
color: var(--color-text-secondary);
}
.about-section {
& + & {
margin-top: 20px;
}
}
.section-title {
font-size: 14px;
font-weight: 600;
color: var(--color-text-primary);
margin-bottom: 10px;
}
.feature-list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.feature-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 12px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
&:hover {
border-color: var(--color-border-hover);
box-shadow: var(--shadow-card);
}
}
.feature-icon {
width: 32px;
height: 32px;
flex-shrink: 0;
.app-logo {
width: 52px;
height: 52px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
color: var(--color-text-primary);
background: var(--color-accent-gradient);
color: var(--color-text-invert);
font-size: 22px;
font-weight: 700;
grid-row: span 2;
}
.feature-body {
min-width: 0;
}
.feature-title {
font-size: 13px;
.app-name {
font-size: 16px;
font-weight: 600;
color: var(--color-text-primary);
}
.feature-desc {
margin-top: 2px;
font-size: 12px;
line-height: 1.5;
color: var(--color-text-secondary);
}
.tech-list {
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
}
.tech-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 14px;
min-height: 40px;
& + & {
border-top: 1px solid var(--color-border);
}
}
.tech-name {
font-size: 13px;
font-weight: 600;
color: var(--color-text-primary);
}
.tech-desc {
.app-version {
font-size: 13px;
color: var(--color-text-secondary);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
margin: 2px 0 0;
}
.about-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 24px;
padding-top: 14px;
border-top: 1px solid var(--color-border);
font-size: 12px;
.app-desc {
grid-column: 1 / -1;
font-size: 13px;
color: var(--color-text-secondary);
margin: 12px 0 0;
}
@media (max-width: 640px) {
@ -357,14 +82,8 @@ const tech = [
padding: 18px 16px 24px;
}
.feature-list {
grid-template-columns: 1fr;
}
.about-footer {
flex-direction: column;
align-items: flex-start;
gap: 4px;
.app-desc {
margin-left: 0;
}
}
</style>