split UI
This commit is contained in:
348
src-tauri/Cargo.lock
generated
348
src-tauri/Cargo.lock
generated
@@ -280,6 +280,24 @@ dependencies = [
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "breakpad-symbols"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b002797414ffc34425bdf5b21a9e50d102013292625749eeba0a59923176ab05"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cachemap2",
|
||||
"circular",
|
||||
"debugid",
|
||||
"futures-util",
|
||||
"minidump-common",
|
||||
"nom",
|
||||
"range-map",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "8.0.2"
|
||||
@@ -328,6 +346,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachemap2"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bba2f68a9fefca870fed897de7c655f9d5c1eaf1cd9517db96c9a3861f648b"
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.18.5"
|
||||
@@ -458,6 +482,12 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "circular"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@@ -585,6 +615,31 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio 0.8.11",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -667,6 +722,15 @@ dependencies = [
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.5"
|
||||
@@ -833,6 +897,15 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.0"
|
||||
@@ -1976,6 +2049,15 @@ version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
@@ -1991,6 +2073,82 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minidump"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c671544a05d0e8daa3018c8fb6687c11935c4ae8f122de8f2386c2896b4e9b8"
|
||||
dependencies = [
|
||||
"debugid",
|
||||
"encoding_rs",
|
||||
"memmap2",
|
||||
"minidump-common",
|
||||
"num-traits",
|
||||
"range-map",
|
||||
"scroll",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minidump-common"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dbc11dfb55b3b7b5684fb16d98e0fc9d1e93a64d6b00bf383eabfc4541aaac2"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"debugid",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"range-map",
|
||||
"scroll",
|
||||
"smart-default",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minidump-processor"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76b49bde7c0ae9a7142c540c27c7fc29db2288fd9614f11a9ce57badeb74af43"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"breakpad-symbols",
|
||||
"debugid",
|
||||
"futures-util",
|
||||
"memmap2",
|
||||
"minidump",
|
||||
"minidump-common",
|
||||
"minidump-unwind",
|
||||
"scroll",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"yaxpeax-x86",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minidump-unwind"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63aef4cd2e018881680b152296ae28e674242823faa1767b417b6669a1896cdc"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"breakpad-symbols",
|
||||
"minidump",
|
||||
"minidump-common",
|
||||
"scroll",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -2001,6 +2159,18 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.0"
|
||||
@@ -2088,6 +2258,16 @@ version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
@@ -2103,6 +2283,17 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -2904,6 +3095,15 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-map"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -3134,6 +3334,26 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "scroll"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da"
|
||||
dependencies = [
|
||||
"scroll_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scroll_derive"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.24.0"
|
||||
@@ -3349,6 +3569,27 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio 0.8.11",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.7"
|
||||
@@ -3388,6 +3629,17 @@ version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "smart-default"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
@@ -3575,6 +3827,8 @@ name = "system-doctor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"minidump",
|
||||
"minidump-processor",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
@@ -4009,7 +4263,7 @@ checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.1.0",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
@@ -4189,6 +4443,7 @@ version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
@@ -4876,6 +5131,15 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -4918,6 +5182,21 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -4975,6 +5254,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -4993,6 +5278,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5011,6 +5302,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5041,6 +5338,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5059,6 +5362,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5077,6 +5386,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -5095,6 +5410,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5228,6 +5549,31 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaxpeax-arch"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f005c964432a1f9ee04598e094a3eb5f7568f6b33e89a2762d7bef6fbe8b255"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaxpeax-x86"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9107477944697db42c41326f82d4c65b769b32512cdad1e086f36f0e0f25ff45"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"yaxpeax-arch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
|
||||
@@ -26,6 +26,8 @@ sysinfo = "0.30"
|
||||
wmi = "0.13"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
minidump = "0.19"
|
||||
minidump-processor = "0.19"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
|
||||
@@ -8,13 +8,15 @@ use serde::Serialize;
|
||||
use sysinfo::{System, Disks};
|
||||
use wmi::{COMLibrary, WMIConnection};
|
||||
use std::fs;
|
||||
// 引入 tauri::Emitter 用于发送事件 (Tauri v2)
|
||||
use std::path::Path;
|
||||
use tauri::Emitter;
|
||||
use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone};
|
||||
use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone, DateTime};
|
||||
// [修改] 只引入 minidump 基础库,移除 processor 依赖以避免 API 冲突
|
||||
use minidump::{Minidump, MinidumpException, MinidumpSystemInfo};
|
||||
|
||||
// --- 1. 数据结构 (保持不变,用于序列化部分数据) ---
|
||||
// --- 1. 数据结构 (保持不变) ---
|
||||
|
||||
#[derive(Serialize, Clone)] // 增加 Clone trait 方便使用
|
||||
#[derive(Serialize, Clone)]
|
||||
struct HardwareSummary {
|
||||
cpu_name: String,
|
||||
sys_vendor: String,
|
||||
@@ -67,51 +69,39 @@ struct BatteryInfo {
|
||||
explanation: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
struct BsodFileItem {
|
||||
filename: String,
|
||||
path: String,
|
||||
size_kb: u64,
|
||||
created_time: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
struct BsodAnalysisReport {
|
||||
crash_reason: String,
|
||||
crash_address: String,
|
||||
bug_check_code: String,
|
||||
crashing_thread: Option<String>,
|
||||
human_analysis: String,
|
||||
recommendation: String,
|
||||
}
|
||||
|
||||
// --- WMI 反序列化结构 ---
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_DiskDrive {
|
||||
Model: Option<String>,
|
||||
Status: Option<String>,
|
||||
}
|
||||
|
||||
struct Win32_DiskDrive { Model: Option<String>, Status: Option<String> }
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_NTLogEvent {
|
||||
TimeGenerated: String,
|
||||
EventCode: u32,
|
||||
SourceName: String,
|
||||
Message: Option<String>,
|
||||
}
|
||||
|
||||
struct Win32_NTLogEvent { TimeGenerated: String, EventCode: u32, SourceName: String, Message: Option<String> }
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_PnPEntity {
|
||||
Name: Option<String>,
|
||||
ConfigManagerErrorCode: Option<u32>,
|
||||
}
|
||||
|
||||
struct Win32_PnPEntity { Name: Option<String>, ConfigManagerErrorCode: Option<u32> }
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_Battery {
|
||||
DesignCapacity: Option<u32>,
|
||||
FullChargeCapacity: Option<u32>,
|
||||
BatteryStatus: Option<u16>,
|
||||
}
|
||||
|
||||
struct Win32_Battery { DesignCapacity: Option<u32>, FullChargeCapacity: Option<u32>, BatteryStatus: Option<u16> }
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_BIOS {
|
||||
SMBIOSBIOSVersion: Option<String>,
|
||||
}
|
||||
|
||||
struct Win32_BIOS { SMBIOSBIOSVersion: Option<String> }
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_BaseBoard {
|
||||
Manufacturer: Option<String>,
|
||||
Product: Option<String>,
|
||||
}
|
||||
|
||||
struct Win32_BaseBoard { Manufacturer: Option<String>, Product: Option<String> }
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Win32_ComputerSystem {
|
||||
Manufacturer: Option<String>,
|
||||
Model: Option<String>,
|
||||
}
|
||||
struct Win32_ComputerSystem { Manufacturer: Option<String>, Model: Option<String> }
|
||||
|
||||
// --- 辅助函数 ---
|
||||
fn get_wmi_query_time(days_ago: i64) -> String {
|
||||
@@ -120,9 +110,7 @@ fn get_wmi_query_time(days_ago: i64) -> String {
|
||||
}
|
||||
|
||||
fn format_wmi_time(wmi_str: &str) -> String {
|
||||
if wmi_str.len() < 25 {
|
||||
return wmi_str.to_string();
|
||||
}
|
||||
if wmi_str.len() < 25 { return wmi_str.to_string(); }
|
||||
let year = wmi_str[0..4].parse::<i32>().unwrap_or(1970);
|
||||
let month = wmi_str[4..6].parse::<u32>().unwrap_or(1);
|
||||
let day = wmi_str[6..8].parse::<u32>().unwrap_or(1);
|
||||
@@ -141,14 +129,146 @@ fn format_wmi_time(wmi_str: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 核心逻辑:分步流式传输 ---
|
||||
// --- 新增功能:翻译蓝屏代码为人话 (手动映射常见代码) ---
|
||||
fn translate_bugcheck_u32(code: u32) -> (String, String) {
|
||||
// 这些是 Windows 最常见的 BSOD 代码
|
||||
match code {
|
||||
0x000000D1 => (
|
||||
"DRIVER_IRQL_NOT_LESS_OR_EQUAL (0xD1)".to_string(),
|
||||
"驱动程序使用了不正确的内存地址。通常是驱动程序冲突或损坏。请检查最近安装的硬件驱动(显卡、网卡等),尝试回滚或更新驱动。"
|
||||
.to_string(),
|
||||
),
|
||||
0x0000007E | 0x1000007E => (
|
||||
"SYSTEM_THREAD_EXCEPTION_NOT_HANDLED (0x7E)".to_string(),
|
||||
"系统线程抛出了未捕获的异常。可能是显卡驱动不兼容,或者BIOS设置问题。建议重装显卡驱动,或恢复BIOS默认设置。"
|
||||
.to_string(),
|
||||
),
|
||||
0x0000001A => (
|
||||
"MEMORY_MANAGEMENT (0x1A)".to_string(),
|
||||
"严重的内存管理错误。高度怀疑内存条物理故障。建议立即运行 Windows 内存诊断工具,或者尝试拔插内存条。"
|
||||
.to_string(),
|
||||
),
|
||||
0x000000EF => (
|
||||
"CRITICAL_PROCESS_DIED (0xEF)".to_string(),
|
||||
"Windows 核心进程意外终止。通常是系统文件损坏或硬盘故障。建议运行 'sfc /scannow' 修复系统,并检查硬盘健康度。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000124 => (
|
||||
"WHEA_UNCORRECTABLE_ERROR (0x124)".to_string(),
|
||||
"硬件发生无法纠正的物理错误。这是纯硬件故障。通常是 CPU 电压不足、超频失败、过热,或者主板/PCIe设备(如 NVMe 硬盘)故障。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000116 => (
|
||||
"VIDEO_TDR_FAILURE (0x116)".to_string(),
|
||||
"显卡响应超时。显卡驱动崩溃或显卡过热。如果你在玩游戏,可能是显卡超频不稳定或散热硅脂干了。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000050 => (
|
||||
"PAGE_FAULT_IN_NONPAGED_AREA (0x50)".to_string(),
|
||||
"试图访问无效的内存地址。可能是内存条故障,或者是防病毒软件/驱动程序冲突。建议检查内存。"
|
||||
.to_string(),
|
||||
),
|
||||
0x0000000A => (
|
||||
"IRQL_NOT_LESS_OR_EQUAL (0x0A)".to_string(),
|
||||
"驱动程序使用了不正确的内存地址。通常由有缺陷的驱动程序或硬件兼容性问题引起。"
|
||||
.to_string(),
|
||||
),
|
||||
0x0000003B => (
|
||||
"SYSTEM_SERVICE_EXCEPTION (0x3B)".to_string(),
|
||||
"系统服务执行异常。通常与图形驱动程序或过时的系统文件有关。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000133 => (
|
||||
"DPC_WATCHDOG_VIOLATION (0x133)".to_string(),
|
||||
"DPC 看门狗超时。通常是 SSD 固件过旧或无线网卡驱动冲突导致系统卡死时间过长。"
|
||||
.to_string(),
|
||||
),
|
||||
_ => (
|
||||
format!("未知错误代码: 0x{:X}", code),
|
||||
"请尝试在搜索引擎中搜索此错误代码。通用建议:更新驱动、检查内存、扫描病毒。".to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// --- 命令:列出 Minidump 文件 ---
|
||||
#[tauri::command]
|
||||
async fn list_minidumps() -> Result<Vec<BsodFileItem>, String> {
|
||||
let path = Path::new("C:\\Windows\\Minidump");
|
||||
let mut files = Vec::new();
|
||||
|
||||
if path.exists() {
|
||||
if let Ok(entries) = fs::read_dir(path) {
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
// [修复] 使用 .as_ref() 防止 metadata 所有权被移动
|
||||
let metadata = entry.metadata().ok();
|
||||
let created = metadata.as_ref()
|
||||
.and_then(|m| m.modified().ok()) // 通常用修改时间
|
||||
.map(|t| {
|
||||
let dt: DateTime<Local> = t.into();
|
||||
dt.format("%Y-%m-%d %H:%M:%S").to_string()
|
||||
})
|
||||
.unwrap_or("Unknown".to_string());
|
||||
|
||||
// [修复] 使用 .as_ref() 再次访问 metadata
|
||||
let size = metadata.as_ref().map(|m| m.len() / 1024).unwrap_or(0);
|
||||
|
||||
files.push(BsodFileItem {
|
||||
filename: entry.file_name().to_string_lossy().to_string(),
|
||||
path: entry.path().to_string_lossy().to_string(),
|
||||
size_kb: size,
|
||||
created_time: created,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 按时间倒序排列
|
||||
files.sort_by(|a, b| b.created_time.cmp(&a.created_time));
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
// --- 命令:分析指定的 Minidump 文件 ---
|
||||
#[tauri::command]
|
||||
async fn analyze_minidump(filepath: String) -> Result<BsodAnalysisReport, String> {
|
||||
let path = Path::new(&filepath);
|
||||
|
||||
// 1. 读取文件 (使用基础 minidump 库)
|
||||
let dump = Minidump::read_path(path).map_err(|e| format!("无法读取文件: {}", e))?;
|
||||
|
||||
// 2. 直接获取异常流 (Exception Stream)
|
||||
let exception_stream = dump.get_stream::<MinidumpException>()
|
||||
.map_err(|_| "无法找到异常信息流 (No Exception Stream)".to_string())?;
|
||||
|
||||
// [修复] 使用 .raw 访问内部原始结构
|
||||
let exception_code = exception_stream.raw.exception_record.exception_code;
|
||||
let exception_address = exception_stream.raw.exception_record.exception_address;
|
||||
|
||||
// 3. 尝试获取系统信息 (OS Version)
|
||||
let sys_info_str = match dump.get_stream::<MinidumpSystemInfo>() {
|
||||
// [修复] 使用 .raw 访问 build_number
|
||||
Ok(info) => format!("Windows Build {}", info.raw.build_number),
|
||||
Err(_) => "Unknown OS".to_string(),
|
||||
};
|
||||
|
||||
// 4. 翻译
|
||||
let (reason_str, recommend) = translate_bugcheck_u32(exception_code);
|
||||
|
||||
Ok(BsodAnalysisReport {
|
||||
crash_reason: reason_str,
|
||||
crash_address: format!("0x{:X}", exception_address),
|
||||
bug_check_code: format!("0x{:X} ({})", exception_code, sys_info_str),
|
||||
crashing_thread: None, // 基础解析不包含线程栈回溯
|
||||
human_analysis: "根据错误代码自动匹配的分析结果。".to_string(),
|
||||
recommendation: recommend,
|
||||
})
|
||||
}
|
||||
|
||||
// --- 现有命令:run_diagnosis (保持不变) ---
|
||||
#[tauri::command]
|
||||
async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
// 使用 spawn 在后台线程运行,避免阻塞 Tauri 主线程
|
||||
// 注意:这里不等待 join,而是让它在后台跑,通过 window.emit 发送进度
|
||||
std::thread::spawn(move || {
|
||||
// 1. 硬件概览 (最快)
|
||||
// 1. 硬件概览
|
||||
{
|
||||
let mut sys = System::new();
|
||||
sys.refresh_memory();
|
||||
@@ -180,7 +300,6 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
// C盘
|
||||
let mut c_total = 0u64;
|
||||
let mut c_used = 0u64;
|
||||
let disks = Disks::new_with_refreshed_list();
|
||||
@@ -209,15 +328,12 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
c_drive_total_gb: c_total,
|
||||
c_drive_used_gb: c_used,
|
||||
};
|
||||
|
||||
// 发送硬件事件
|
||||
let _ = window.emit("report-hardware", hardware);
|
||||
}
|
||||
|
||||
// WMI 连接复用 (如果需要) 或者重新建立
|
||||
let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok();
|
||||
|
||||
// 2. 存储设备 (较快)
|
||||
// 2. 存储设备
|
||||
{
|
||||
let mut storage = Vec::new();
|
||||
if let Some(con) = &wmi_con {
|
||||
@@ -242,7 +358,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
let _ = window.emit("report-storage", storage);
|
||||
}
|
||||
|
||||
// 3. 驱动 (快)
|
||||
// 3. 驱动
|
||||
{
|
||||
let mut driver_issues = Vec::new();
|
||||
if let Some(con) = &wmi_con {
|
||||
@@ -267,7 +383,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
let _ = window.emit("report-drivers", driver_issues);
|
||||
}
|
||||
|
||||
// 4. Minidump (快)
|
||||
// 4. Minidump
|
||||
{
|
||||
let mut minidump = MinidumpInfo { found: false, count: 0, explanation: "无蓝屏记录".to_string() };
|
||||
if let Ok(entries) = fs::read_dir("C:\\Windows\\Minidump") {
|
||||
@@ -283,7 +399,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
let _ = window.emit("report-minidumps", minidump);
|
||||
}
|
||||
|
||||
// 5. 电池 (快)
|
||||
// 5. 电池
|
||||
{
|
||||
let mut battery_info = None;
|
||||
if let Some(con) = &wmi_con {
|
||||
@@ -309,12 +425,10 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 即使是 None 也要发,以便前端知道检查完成了
|
||||
// 这里为了简化,直接发 Option
|
||||
let _ = window.emit("report-battery", battery_info);
|
||||
}
|
||||
|
||||
// 6. 日志 (最慢,放在最后)
|
||||
// 6. 日志
|
||||
{
|
||||
let mut events = Vec::new();
|
||||
if let Some(con) = &wmi_con {
|
||||
@@ -366,7 +480,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![run_diagnosis])
|
||||
.invoke_handler(tauri::generate_handler![run_diagnosis, list_minidumps, analyze_minidump])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
682
src/App.vue
682
src/App.vue
@@ -1,235 +1,254 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<!-- 顶部标题栏 -->
|
||||
<header class="header-bar">
|
||||
<div class="app-layout">
|
||||
<!-- 左侧侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<span class="icon">🩺</span>
|
||||
<h1>静态排查仪表盘</h1>
|
||||
<h1>体检中心</h1>
|
||||
</div>
|
||||
<span class="version-tag">Powered by Rust & Tauri</span>
|
||||
</header>
|
||||
<nav class="nav-menu">
|
||||
<button
|
||||
class="nav-item"
|
||||
:class="{ active: currentTab === 'overview' }"
|
||||
@click="currentTab = 'overview'"
|
||||
>
|
||||
<span class="nav-icon">📊</span> 静态概览
|
||||
</button>
|
||||
<button
|
||||
class="nav-item"
|
||||
:class="{ active: currentTab === 'bsod' }"
|
||||
@click="currentTab = 'bsod'"
|
||||
>
|
||||
<span class="nav-icon">☠️</span> 蓝屏分析
|
||||
</button>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">Pro v1.2</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 工具栏:左侧主操作,右侧辅助操作 -->
|
||||
<div class="toolbar">
|
||||
<div class="left-actions">
|
||||
<!-- 右侧主内容区 -->
|
||||
<main class="main-content">
|
||||
|
||||
<!-- TAB 1: 概览 -->
|
||||
<div v-if="currentTab === 'overview'" class="tab-view overview-view">
|
||||
<div class="view-header">
|
||||
<h2>系统健康概览</h2>
|
||||
<div class="actions-row">
|
||||
<button @click="startScan" :disabled="loading" class="primary-btn" :class="{ 'scanning': loading }">
|
||||
<span v-if="loading" class="icon-spin">🔄</span>
|
||||
<span v-if="loading">正在排查...</span>
|
||||
<template v-else>
|
||||
<span class="btn-icon">🔍</span> 开始全面体检
|
||||
</template>
|
||||
<span v-else>🔍 全面体检</span>
|
||||
</button>
|
||||
<div class="sub-actions">
|
||||
<button class="icon-btn" @click="triggerImport" :disabled="loading">📂 导入</button>
|
||||
<button class="icon-btn" @click="exportReport" :disabled="!isReportValid || loading">💾 导出</button>
|
||||
</div>
|
||||
|
||||
<div class="right-actions">
|
||||
<button class="icon-btn" @click="triggerImport" :disabled="loading" title="导入报告">
|
||||
📂 导入
|
||||
</button>
|
||||
<button class="icon-btn" @click="exportReport" :disabled="!isReportValid || loading" title="导出报告">
|
||||
💾 导出
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的文件输入框 -->
|
||||
<!-- 状态提示 -->
|
||||
<div v-if="errorMsg" class="message-box error">⚠️ {{ errorMsg }}</div>
|
||||
<div v-if="scanFinished" class="message-box success">✅ 扫描完成</div>
|
||||
|
||||
<input type="file" ref="fileInput" @change="handleFileImport" accept=".json" style="display: none" />
|
||||
|
||||
<!-- 错误提示 (保留在原本位置或顶部) -->
|
||||
<div v-if="errorMsg" class="inline-error">
|
||||
⚠️ {{ errorMsg }}
|
||||
</div>
|
||||
|
||||
<!-- 右上角 Toast 通知 (通用化) -->
|
||||
<transition name="toast-slide">
|
||||
<div v-if="toast.show" class="toast-notification" :class="toast.type">
|
||||
<div class="toast-content">
|
||||
<span class="check-icon">{{ toast.type === 'error' ? '❌' : '✅' }}</span>
|
||||
<div class="toast-text">
|
||||
<h4>{{ toast.title }}</h4>
|
||||
<p>{{ toast.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast-progress"></div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 结果显示区域 -->
|
||||
<!-- 卡片展示区 -->
|
||||
<div v-if="report.hardware" class="dashboard fade-in">
|
||||
|
||||
<!-- 1. 硬件概览 -->
|
||||
<div v-if="report.hardware" class="card summary slide-up">
|
||||
<div class="card-header">
|
||||
<h2>🖥️ 硬件概览与资源占用</h2>
|
||||
</div>
|
||||
<!-- 硬件卡片 -->
|
||||
<div class="card summary slide-up">
|
||||
<div class="card-header"><h3>🖥️ 硬件与资源</h3></div>
|
||||
<div class="grid-container-summary">
|
||||
<div class="basic-info">
|
||||
<div class="info-item">
|
||||
<span class="label">CPU 型号</span>
|
||||
<span class="value text-ellipsis" :title="report.hardware.cpu_name">{{ report.hardware.cpu_name }}</span>
|
||||
<div class="info-item"><span class="label">CPU</span><span class="value text-ellipsis" :title="report.hardware.cpu_name">{{ report.hardware.cpu_name }}</span></div>
|
||||
<div class="info-item"><span class="label">System</span><span class="value text-ellipsis">{{ report.hardware.sys_vendor }} {{ report.hardware.sys_product }}</span></div>
|
||||
<div class="info-item"><span class="label">Mobo</span><span class="value text-ellipsis">{{ report.hardware.mobo_vendor }} {{ report.hardware.mobo_product }}</span></div>
|
||||
<div class="info-item"><span class="label">BIOS</span><span class="value text-ellipsis">{{ report.hardware.bios_version }}</span></div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">整机型号</span>
|
||||
<span class="value text-ellipsis">{{ report.hardware.sys_vendor }} {{ report.hardware.sys_product }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">主板型号</span>
|
||||
<span class="value text-ellipsis">{{ report.hardware.mobo_vendor }} {{ report.hardware.mobo_product }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">BIOS 版本</span>
|
||||
<span class="value">{{ report.hardware.bios_version }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">操作系统</span>
|
||||
<span class="value">{{ report.hardware.os_version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="usage-bars">
|
||||
<div class="usage-item">
|
||||
<div class="progress-label">
|
||||
<span>C盘空间 (已用 {{ report.hardware.c_drive_used_gb }}GB / 总计 {{ report.hardware.c_drive_total_gb }}GB)</span>
|
||||
<span class="text-ellipsis">C盘 ({{ report.hardware.c_drive_used_gb }}/{{ report.hardware.c_drive_total_gb }}GB)</span>
|
||||
<strong>{{ calculatePercent(report.hardware.c_drive_used_gb, report.hardware.c_drive_total_gb) }}%</strong>
|
||||
</div>
|
||||
<div class="progress-bar-large">
|
||||
<div class="fill" :style="getProgressStyle(report.hardware.c_drive_used_gb, report.hardware.c_drive_total_gb)"></div>
|
||||
</div>
|
||||
<div class="progress-bar-large"><div class="fill" :style="getProgressStyle(report.hardware.c_drive_used_gb, report.hardware.c_drive_total_gb)"></div></div>
|
||||
</div>
|
||||
<div class="usage-item">
|
||||
<div class="progress-label">
|
||||
<span>内存占用 (已用 {{ report.hardware.memory_used_gb }}GB / 总计 {{ report.hardware.memory_total_gb }}GB)</span>
|
||||
<span class="text-ellipsis">内存 ({{ report.hardware.memory_used_gb }}/{{ report.hardware.memory_total_gb }}GB)</span>
|
||||
<strong>{{ calculatePercent(report.hardware.memory_used_gb, report.hardware.memory_total_gb) }}%</strong>
|
||||
</div>
|
||||
<div class="progress-bar-large">
|
||||
<div class="fill" :style="getProgressStyle(report.hardware.memory_used_gb, report.hardware.memory_total_gb)"></div>
|
||||
</div>
|
||||
<div class="progress-bar-large"><div class="fill" :style="getProgressStyle(report.hardware.memory_used_gb, report.hardware.memory_total_gb)"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 驱动状态 -->
|
||||
<!-- 驱动状态卡片 -->
|
||||
<div v-if="report.drivers" class="card slide-up" :class="{ danger: report.drivers.length > 0, success: report.drivers.length === 0 }">
|
||||
<div class="card-header">
|
||||
<h2>🔌 驱动与设备状态</h2>
|
||||
<span class="badge" :class="report.drivers.length > 0 ? 'badge-red' : 'badge-green'">
|
||||
{{ report.drivers.length > 0 ? '发现异常' : '正常' }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="report.drivers.length === 0" class="good-news">✅ 设备管理器中未发现带有黄色感叹号或错误的设备。</div>
|
||||
<div v-else class="list-container">
|
||||
<div class="card-header"><h3>🔌 驱动状态</h3><span class="badge" :class="report.drivers.length > 0 ? 'badge-red' : 'badge-green'">{{ report.drivers.length > 0 ? '异常' : '正常' }}</span></div>
|
||||
<div v-if="report.drivers.length > 0" class="list-container">
|
||||
<div v-for="(drv, idx) in report.drivers" :key="idx" class="list-item error-item">
|
||||
<div class="item-header">
|
||||
<strong>{{ drv.device_name }}</strong>
|
||||
<strong class="text-ellipsis" :title="drv.device_name">{{ drv.device_name }}</strong>
|
||||
<span class="code-tag">Code {{ drv.error_code }}</span>
|
||||
</div>
|
||||
<p class="description">{{ drv.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="good-news">设备管理器无异常。</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 电池健康 -->
|
||||
<!-- 电池卡片 -->
|
||||
<div v-if="report.battery" class="card slide-up" :class="{ danger: report.battery.health_percentage < 60 }">
|
||||
<div class="card-header">
|
||||
<h2>🔋 电池健康度 (寿命)</h2>
|
||||
<span class="badge" :class="report.battery.is_ac_connected ? 'badge-blue' : 'badge-gray'">
|
||||
{{ report.battery.is_ac_connected ? '⚡ 已连接电源' : '🔋 使用电池中' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-header"><h3>🔋 电池健康</h3><span class="badge badge-blue">{{ report.battery.health_percentage }}%</span></div>
|
||||
<div class="content-box">
|
||||
<div class="progress-wrapper">
|
||||
<div class="progress-label">
|
||||
<span>当前健康度 (相对于设计容量)</span>
|
||||
<strong>{{ report.battery.health_percentage }}%</strong>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="fill" :style="{ width: report.battery.health_percentage + '%', background: getBatteryColor(report.battery.health_percentage) }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="explanation">{{ report.battery.explanation }}</p>
|
||||
<div class="progress-bar"><div class="fill" :style="{ width: report.battery.health_percentage + '%', background: getBatteryColor(report.battery.health_percentage) }"></div></div>
|
||||
<p class="description mt-2">{{ report.battery.explanation }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 硬盘健康 -->
|
||||
<!-- 硬盘卡片 -->
|
||||
<div v-if="report.storage" class="card slide-up" :class="{ danger: hasStorageDanger }">
|
||||
<div class="card-header">
|
||||
<h2>💾 硬盘健康度 (S.M.A.R.T)</h2>
|
||||
</div>
|
||||
<div class="card-header"><h3>💾 硬盘 S.M.A.R.T</h3></div>
|
||||
<div class="list-container">
|
||||
<div v-for="(disk, index) in report.storage" :key="index" class="list-item">
|
||||
<div class="item-header">
|
||||
<strong>{{ disk.model }}</strong>
|
||||
<span class="status-text" :class="disk.is_danger ? 'text-red' : 'text-green'">
|
||||
{{ disk.health_status }}
|
||||
</span>
|
||||
<strong class="text-ellipsis" :title="disk.model">{{ disk.model }}</strong>
|
||||
<span class="status-text" :class="disk.is_danger ? 'text-red' : 'text-green'">{{ disk.health_status }}</span>
|
||||
</div>
|
||||
<p class="description">{{ disk.human_explanation }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. 蓝屏分析 -->
|
||||
<div v-if="report.minidumps" class="card slide-up" :class="{ danger: report.minidumps.found, success: !report.minidumps.found }">
|
||||
<div class="card-header">
|
||||
<h2>☠️ 蓝屏死机记录 (BSOD)</h2>
|
||||
</div>
|
||||
<div class="content-box">
|
||||
<p class="main-text">{{ report.minidumps.explanation }}</p>
|
||||
<p v-if="report.minidumps.found" class="tip">💡 建议使用 BlueScreenView 或 WinDbg 工具打开 C:\Windows\Minidump 文件夹下的 .dmp 文件进行深入分析。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 6. 关键日志 -->
|
||||
<!-- 日志卡片 -->
|
||||
<div v-if="report.events" class="card slide-up" :class="{ danger: report.events.length > 0 }">
|
||||
<div class="card-header">
|
||||
<h2>⚡ 关键供电与硬件日志 (Event Log)</h2>
|
||||
</div>
|
||||
<div v-if="report.events.length === 0" class="good-news">✅ 近期日志中未发现 Kernel-Power(41) 或 WHEA(18/19) 等致命错误。</div>
|
||||
<div v-else class="list-container">
|
||||
<div class="card-header"><h3>⚡ 关键日志</h3></div>
|
||||
<div v-if="report.events.length > 0" class="list-container">
|
||||
<div v-for="(evt, idx) in report.events" :key="idx" class="list-item warning-item">
|
||||
<div class="item-header">
|
||||
<span class="event-id">ID: {{ evt.event_id }}</span>
|
||||
<span class="event-source">{{ evt.source }}</span>
|
||||
<span class="event-time">{{ evt.time_generated }}</span>
|
||||
<span class="event-source" :title="evt.source">{{ evt.source }}</span>
|
||||
<span class="event-time">{{ formatTime(evt.time_generated) }}</span>
|
||||
</div>
|
||||
<p class="description highlight">{{ evt.analysis_hint }}</p>
|
||||
<p v-if="evt.message" class="description raw-message">{{ evt.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="good-news">无致命错误日志。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 2: 蓝屏分析 -->
|
||||
<div v-if="currentTab === 'bsod'" class="tab-view bsod-view">
|
||||
<div class="view-header">
|
||||
<h2>蓝屏死机分析 (Minidump)</h2>
|
||||
<button class="secondary-btn" @click="loadMinidumps" :disabled="bsodLoading">🔄 刷新列表</button>
|
||||
</div>
|
||||
|
||||
<div class="bsod-layout">
|
||||
<div class="bsod-list-panel">
|
||||
<div v-if="bsodList.length === 0 && !bsodLoading" class="empty-state">
|
||||
未发现 Minidump 文件。<br>系统可能未启用转储或已被清理。
|
||||
</div>
|
||||
<div v-else class="file-list">
|
||||
<div
|
||||
v-for="file in bsodList"
|
||||
:key="file.path"
|
||||
class="file-item"
|
||||
:class="{ active: selectedBsod?.path === file.path }"
|
||||
@click="analyzeBsod(file)"
|
||||
>
|
||||
<div class="file-icon">📄</div>
|
||||
<div class="file-info">
|
||||
<div class="file-name">{{ file.filename }}</div>
|
||||
<div class="file-meta">{{ file.created_time }} · {{ file.size_kb }}KB</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bsod-detail-panel">
|
||||
<div v-if="bsodAnalyzing" class="analyzing-state">
|
||||
<div class="spinner"></div>
|
||||
<p>正在解析 Dump 文件,可能需要几秒钟...</p>
|
||||
</div>
|
||||
<div v-else-if="bsodResult" class="analysis-result fade-in">
|
||||
<div class="result-header">
|
||||
<h3>{{ bsodResult.crash_reason }}</h3>
|
||||
<span class="code-pill">{{ bsodResult.bug_check_code }}</span>
|
||||
</div>
|
||||
|
||||
<div class="human-analysis card-section">
|
||||
<h4>🤖 智能分析</h4>
|
||||
<p class="human-text">{{ bsodResult.human_analysis }}</p>
|
||||
<div class="recommendation">
|
||||
<strong>建议操作:</strong> {{ bsodResult.recommendation }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tech-details card-section">
|
||||
<h4>🔬 技术细节</h4>
|
||||
<div class="detail-grid">
|
||||
<div class="d-item"><span class="d-label">崩溃地址</span>{{ bsodResult.crash_address }}</div>
|
||||
<div class="d-item"><span class="d-label">相关线程</span>{{ bsodResult.crashing_thread || 'N/A' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-detail">
|
||||
👈 请从左侧选择一个蓝屏文件开始分析
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Toast 通知 -->
|
||||
<transition name="toast-slide">
|
||||
<div v-if="toast.show" class="toast-notification" :class="toast.type">
|
||||
<div class="toast-content">
|
||||
<span class="check-icon">{{ toast.type === 'error' ? '❌' : '✅' }}</span>
|
||||
<div class="toast-text"><h4>{{ toast.title }}</h4><p>{{ toast.message }}</p></div>
|
||||
</div>
|
||||
<div class="toast-progress"></div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onUnmounted, reactive } from 'vue';
|
||||
import { ref, computed, onUnmounted, reactive, onMounted, watch } from 'vue';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
|
||||
const emptyReport = () => ({
|
||||
hardware: null, storage: null, events: null, minidumps: null, drivers: null, battery: null
|
||||
});
|
||||
// --- 状态管理 ---
|
||||
const currentTab = ref('overview'); // 'overview' | 'bsod'
|
||||
|
||||
// 概览相关
|
||||
const emptyReport = () => ({ hardware: null, storage: null, events: null, minidumps: null, drivers: null, battery: null });
|
||||
const report = ref(emptyReport());
|
||||
const loading = ref(false);
|
||||
const errorMsg = ref('');
|
||||
const scanFinished = ref(false);
|
||||
const fileInput = ref(null);
|
||||
let unlistenFns = [];
|
||||
let toastTimer = null;
|
||||
|
||||
// 通用 Toast 状态
|
||||
const toast = reactive({
|
||||
show: false,
|
||||
title: '',
|
||||
message: '',
|
||||
type: 'success' // 'success' | 'error'
|
||||
});
|
||||
// BSOD 相关
|
||||
const bsodList = ref([]);
|
||||
const selectedBsod = ref(null);
|
||||
const bsodResult = ref(null);
|
||||
const bsodLoading = ref(false);
|
||||
const bsodAnalyzing = ref(false);
|
||||
|
||||
const toast = reactive({ show: false, title: '', message: '', type: 'success' });
|
||||
let toastTimer = null;
|
||||
|
||||
const hasStorageDanger = computed(() => report.value?.storage?.some(d => d.is_danger) || false);
|
||||
const isReportValid = computed(() => report.value && (report.value.hardware || report.value.storage));
|
||||
|
||||
// --- 辅助函数 ---
|
||||
// --- 辅助函数 (保留) ---
|
||||
function calculatePercent(used, total) { return (!total || total === 0) ? 0 : Math.round((used / total) * 100); }
|
||||
function getProgressStyle(used, total) {
|
||||
const percent = calculatePercent(used, total);
|
||||
@@ -239,272 +258,191 @@ function getProgressStyle(used, total) {
|
||||
function getBatteryColor(p) { return p < 50 ? '#ff4757' : (p < 80 ? '#ffa502' : '#2ed573'); }
|
||||
function formatTime(t) { return !t ? "Unknown Time" : t.replace('T', ' ').substring(0, 19); }
|
||||
|
||||
// --- 导出/导入 ---
|
||||
async function exportReport() {
|
||||
if (!isReportValid.value) return;
|
||||
const fileName = `SystemDoctor_Report_${new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-")}.json`;
|
||||
const content = JSON.stringify(report.value, null, 2);
|
||||
|
||||
// 尝试使用现代文件系统 API (显示保存对话框)
|
||||
if (window.showSaveFilePicker) {
|
||||
try {
|
||||
const handle = await window.showSaveFilePicker({
|
||||
suggestedName: fileName,
|
||||
types: [{
|
||||
description: 'JSON Report',
|
||||
accept: { 'application/json': ['.json'] },
|
||||
}],
|
||||
});
|
||||
const writable = await handle.createWritable();
|
||||
await writable.write(content);
|
||||
await writable.close();
|
||||
triggerToast('导出成功', '文件已保存到指定位置', 'success');
|
||||
return;
|
||||
} catch (err) {
|
||||
// 用户取消不报错,其他错误降级处理
|
||||
if (err.name !== 'AbortError') {
|
||||
console.warn('Native save dialog failed, falling back to download link', err);
|
||||
} else {
|
||||
return; // 用户取消
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 降级方案:创建链接直接下载 (默认下载文件夹)
|
||||
try {
|
||||
const blob = new Blob([content], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url);
|
||||
triggerToast('导出成功', '文件已保存到默认下载目录', 'success');
|
||||
} catch (err) {
|
||||
errorMsg.value = "导出失败: " + err.message;
|
||||
triggerToast('导出失败', err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function exportReport() { /* ... */ if (!isReportValid.value) return; try { const blob = new Blob([JSON.stringify(report.value, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `SystemDoctor_Report_${new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-")}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); triggerToast('导出成功', '文件已保存'); } catch (err) { triggerToast('导出失败', err.message, 'error'); } }
|
||||
function triggerImport() { fileInput.value.click(); }
|
||||
function handleFileImport(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const json = JSON.parse(e.target.result);
|
||||
if (json && (json.hardware || json.storage)) {
|
||||
report.value = json;
|
||||
errorMsg.value = '';
|
||||
triggerToast('导入成功', '已加载历史报告数据', 'success');
|
||||
} else {
|
||||
errorMsg.value = "无效的报告文件。";
|
||||
triggerToast('导入失败', '文件格式不正确', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
errorMsg.value = "解析失败: " + err.message;
|
||||
triggerToast('导入失败', err.message, 'error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
event.target.value = '';
|
||||
}
|
||||
function handleFileImport(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const json = JSON.parse(e.target.result); if (json && (json.hardware || json.storage)) { report.value = json; scanFinished.value = false; triggerToast('导入成功', '已加载历史报告'); } else { triggerToast('导入失败', '文件格式错误', 'error'); } } catch (err) { triggerToast('解析失败', err.message, 'error'); } }; reader.readAsText(file); event.target.value = ''; }
|
||||
|
||||
// --- 扫描逻辑 ---
|
||||
async function startScan() {
|
||||
report.value = emptyReport();
|
||||
errorMsg.value = '';
|
||||
loading.value = true;
|
||||
|
||||
report.value = emptyReport(); errorMsg.value = ''; scanFinished.value = false; loading.value = true;
|
||||
if (unlistenFns.length > 0) { for (const fn of unlistenFns) fn(); unlistenFns = []; }
|
||||
|
||||
try {
|
||||
const l1 = await listen('report-hardware', (e) => report.value.hardware = e.payload);
|
||||
const l2 = await listen('report-storage', (e) => report.value.storage = e.payload);
|
||||
const l3 = await listen('report-drivers', (e) => report.value.drivers = e.payload);
|
||||
const l4 = await listen('report-minidumps', (e) => report.value.minidumps = e.payload);
|
||||
const l5 = await listen('report-battery', (e) => report.value.battery = e.payload);
|
||||
const l6 = await listen('report-events', (e) => report.value.events = e.payload);
|
||||
const l7 = await listen('diagnosis-finished', () => {
|
||||
loading.value = false;
|
||||
triggerToast('扫描已完成', '所有硬件与日志检查完毕', 'success');
|
||||
});
|
||||
unlistenFns.push(l1, l2, l3, l4, l5, l6, l7);
|
||||
unlistenFns.push(
|
||||
await listen('report-hardware', (e) => report.value.hardware = e.payload),
|
||||
await listen('report-storage', (e) => report.value.storage = e.payload),
|
||||
await listen('report-drivers', (e) => report.value.drivers = e.payload),
|
||||
await listen('report-minidumps', (e) => report.value.minidumps = e.payload),
|
||||
await listen('report-battery', (e) => report.value.battery = e.payload),
|
||||
await listen('report-events', (e) => report.value.events = e.payload),
|
||||
await listen('diagnosis-finished', () => { loading.value = false; scanFinished.value = true; triggerToast('扫描完成', '体检已结束', 'success'); })
|
||||
);
|
||||
await invoke('run_diagnosis');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
errorMsg.value = "启动扫描失败: " + e;
|
||||
loading.value = false;
|
||||
triggerToast('扫描失败', '无法启动后台诊断程序', 'error');
|
||||
}
|
||||
} catch (e) { loading.value = false; errorMsg.value = e; }
|
||||
}
|
||||
|
||||
// --- Toast 逻辑 ---
|
||||
function triggerToast(title, message, type = 'success') {
|
||||
toast.title = title;
|
||||
toast.message = message;
|
||||
toast.type = type;
|
||||
toast.show = true;
|
||||
async function loadMinidumps() { bsodLoading.value = true; try { bsodList.value = await invoke('list_minidumps'); } catch (e) { triggerToast('加载失败', e, 'error'); } finally { bsodLoading.value = false; } }
|
||||
async function analyzeBsod(file) { if (bsodAnalyzing.value) return; selectedBsod.value = file; bsodResult.value = null; bsodAnalyzing.value = true; try { bsodResult.value = await invoke('analyze_minidump', { filepath: file.path }); } catch (e) { triggerToast('分析失败', e, 'error'); } finally { bsodAnalyzing.value = false; } }
|
||||
|
||||
if (toastTimer) clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => {
|
||||
toast.show = false;
|
||||
}, 3000);
|
||||
}
|
||||
watch(currentTab, (newVal) => { if (newVal === 'bsod' && bsodList.value.length === 0) loadMinidumps(); });
|
||||
|
||||
onUnmounted(() => {
|
||||
for (const fn of unlistenFns) fn();
|
||||
if (toastTimer) clearTimeout(toastTimer);
|
||||
});
|
||||
function triggerToast(title, message, type = 'success') { toast.title = title; toast.message = message; toast.type = type; toast.show = true; if (toastTimer) clearTimeout(toastTimer); toastTimer = setTimeout(() => { toast.show = false; }, 3000); }
|
||||
onUnmounted(() => { for (const fn of unlistenFns) fn(); if (toastTimer) clearTimeout(toastTimer); });
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0; padding: 0; background-color: #f4f6f9; /* 更柔和的背景 */
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
/* 容器调整 */
|
||||
.container {
|
||||
max-width: 1200px; margin: 0 auto; padding: 20px 30px; box-sizing: border-box;
|
||||
font-family: 'Segoe UI', system-ui, sans-serif; color: #2c3e50;
|
||||
}
|
||||
/* ... 布局样式保持不变 ... */
|
||||
.app-layout { display: flex; height: 100vh; width: 100vw; overflow: hidden; background-color: #f4f6f9; }
|
||||
.sidebar { width: 240px; background: #2c3e50; color: white; display: flex; flex-direction: column; padding: 20px 0; box-shadow: 2px 0 10px rgba(0,0,0,0.1); z-index: 10; }
|
||||
.brand { padding: 0 20px 30px; display: flex; align-items: center; gap: 12px; }
|
||||
.brand .icon { font-size: 1.8rem; } .brand h1 { font-size: 1.2rem; margin: 0; font-weight: 700; color: white; }
|
||||
.nav-menu { flex: 1; display: flex; flex-direction: column; gap: 5px; padding: 0 10px; }
|
||||
.nav-item { background: transparent; border: none; color: #bdc3c7; padding: 12px 15px; font-size: 1rem; cursor: pointer; text-align: left; border-radius: 8px; transition: all 0.2s; display: flex; align-items: center; gap: 12px; }
|
||||
.nav-item:hover { background: rgba(255,255,255,0.1); color: white; }
|
||||
.nav-item.active { background: #2ecc71; color: white; font-weight: 600; box-shadow: 0 4px 10px rgba(46, 204, 113, 0.3); }
|
||||
.nav-icon { font-size: 1.2rem; }
|
||||
.sidebar-footer { padding: 0 20px; text-align: center; color: #7f8c8d; font-size: 0.8rem; }
|
||||
.main-content { flex: 1; overflow-y: auto; padding: 30px 40px; position: relative; }
|
||||
|
||||
/* --- 顶部标题栏 --- */
|
||||
.header-bar {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-bottom: 25px; padding-bottom: 15px; border-bottom: 1px solid #e1e4e8;
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 10px; }
|
||||
.brand .icon { font-size: 1.8rem; }
|
||||
h1 { font-size: 1.5rem; margin: 0; color: #2c3e50; font-weight: 700; letter-spacing: -0.5px; }
|
||||
.version-tag { font-size: 0.85rem; color: #95a5a6; font-weight: 500; }
|
||||
.view-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; }
|
||||
.view-header h2 { margin: 0; font-size: 1.8rem; color: #2c3e50; }
|
||||
.actions-row { display: flex; align-items: center; gap: 15px; }
|
||||
.sub-actions { display: flex; gap: 10px; }
|
||||
|
||||
/* --- 工具栏布局 --- */
|
||||
.toolbar {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-bottom: 30px; background: white; padding: 10px 15px;
|
||||
border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.left-actions { display: flex; align-items: center; }
|
||||
.right-actions { display: flex; gap: 10px; }
|
||||
|
||||
/* 主按钮 (绿色强调) */
|
||||
.primary-btn {
|
||||
background: #2ecc71; color: white; border: none;
|
||||
padding: 10px 24px; font-size: 1rem; border-radius: 8px;
|
||||
cursor: pointer; font-weight: 600; display: flex; align-items: center; gap: 8px;
|
||||
transition: background 0.2s, transform 0.1s;
|
||||
}
|
||||
.primary-btn:hover:not(:disabled) { background: #27ae60; transform: translateY(-1px); }
|
||||
.primary-btn:active:not(:disabled) { transform: translateY(1px); }
|
||||
.primary-btn:disabled { background: #bdc3c7; cursor: not-allowed; }
|
||||
.icon-spin { animation: spin 1s linear infinite; }
|
||||
|
||||
/* 辅助按钮 (白色简约) */
|
||||
.icon-btn {
|
||||
background: transparent; border: 1px solid transparent; color: #555;
|
||||
padding: 8px 16px; font-size: 0.95rem; border-radius: 8px;
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.icon-btn:hover:not(:disabled) { background: #f0f2f5; color: #2c3e50; }
|
||||
.icon-btn:disabled { color: #ccc; cursor: not-allowed; }
|
||||
|
||||
/* --- Toast 通知 (增强版) --- */
|
||||
.toast-notification {
|
||||
position: fixed; top: 25px; right: 25px; z-index: 9999;
|
||||
background: white; border-left: 5px solid #2ecc71;
|
||||
padding: 15px 20px; border-radius: 8px;
|
||||
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
|
||||
min-width: 280px; overflow: hidden;
|
||||
}
|
||||
.toast-notification.error { border-left-color: #ff4757; }
|
||||
|
||||
.toast-content { display: flex; align-items: flex-start; gap: 12px; }
|
||||
.check-icon { font-size: 1.2rem; }
|
||||
.toast-text h4 { margin: 0 0 4px 0; font-size: 1rem; color: #2c3e50; }
|
||||
.toast-text p { margin: 0; font-size: 0.85rem; color: #7f8c8d; }
|
||||
|
||||
/* 简单的倒计时进度条动画 */
|
||||
.toast-progress {
|
||||
position: absolute; bottom: 0; left: 0; height: 3px; background: #2ecc71;
|
||||
width: 100%; animation: progress 3s linear forwards;
|
||||
}
|
||||
.toast-notification.error .toast-progress { background: #ff4757; }
|
||||
|
||||
@keyframes progress { from { width: 100%; } to { width: 0%; } }
|
||||
|
||||
/* Toast 进出动画 */
|
||||
.toast-slide-enter-active, .toast-slide-leave-active { transition: all 0.3s ease; }
|
||||
.toast-slide-enter-from, .toast-slide-leave-to { transform: translateX(50px); opacity: 0; }
|
||||
|
||||
.inline-error {
|
||||
background: #ffeaea; color: #c0392b; padding: 10px; border-radius: 6px; margin-bottom: 20px; font-size: 0.9rem; font-weight: 600;
|
||||
}
|
||||
|
||||
/* --- 动画与通用 --- */
|
||||
@keyframes spin { 100% { transform: rotate(360deg); } }
|
||||
.dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; }
|
||||
.slide-up { animation: slideUp 0.4s ease-out forwards; }
|
||||
@keyframes slideUp { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
|
||||
.card { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.03); border: 1px solid #eaecf0; border-top: 3px solid #2ecc71; }
|
||||
.card-header { display: flex; justify-content: space-between; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #f0f2f5; }
|
||||
.card-header h3 { margin: 0; font-size: 1.1rem; font-weight: 600; }
|
||||
|
||||
/* 卡片样式 (微调更紧凑) */
|
||||
.card {
|
||||
background: white; border-radius: 10px; padding: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.03); border: 1px solid #eaecf0;
|
||||
border-top: 3px solid #2ecc71;
|
||||
/* --- [修复] 关键 CSS 修改 --- */
|
||||
|
||||
/* 1. item-header 增加 gap */
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
gap: 12px; /* [新增] 强制间距,防止内容对撞 */
|
||||
}
|
||||
.card:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.06); transform: translateY(-2px); transition: all 0.2s; }
|
||||
.card.danger { border-top-color: #ff4757; }
|
||||
.card.summary { border-top-color: #3498db; grid-column: 1 / -1; }
|
||||
|
||||
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #f0f2f5; }
|
||||
h2 { font-size: 1.1rem; margin: 0; font-weight: 600; }
|
||||
/* 2. 进度条标签 增加 gap */
|
||||
.progress-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.85rem;
|
||||
color: #636e72;
|
||||
gap: 10px; /* [新增] 强制间距 */
|
||||
}
|
||||
|
||||
/* 3. 长文本处理:设备名、日志来源等 */
|
||||
.text-ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0; /* Flexbox item 必须有这个才能正确收缩 */
|
||||
}
|
||||
|
||||
/* 让 item-header 中的 strong 也能截断 */
|
||||
.item-header strong {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
flex: 1; /* 占据剩余空间 */
|
||||
}
|
||||
|
||||
/* 状态标签固定宽度,防止被挤压 */
|
||||
.status-text, .code-tag, .badge {
|
||||
flex-shrink: 0; /* 禁止缩小 */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 4. 日志行布局优化 */
|
||||
.event-id {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
background: #e2e8f0;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 来源:占据中间空间,过长省略 */
|
||||
.event-source {
|
||||
font-size: 0.75rem;
|
||||
color: #7f8c8d;
|
||||
flex: 1; /* 关键:占据中间所有剩余空间 */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0 5px; /* 小间距 */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 时间:靠右,不换行 */
|
||||
.event-time {
|
||||
font-size: 0.75rem;
|
||||
color: #95a5a6;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* --- 剩余样式 (保持不变) --- */
|
||||
.grid-container-summary { display: grid; grid-template-columns: 1fr 1.5fr; gap: 25px; }
|
||||
@media (max-width: 768px) { .grid-container-summary { grid-template-columns: 1fr; } }
|
||||
|
||||
@media (max-width: 900px) { .grid-container-summary { grid-template-columns: 1fr; } } /* 响应式阈值调大一点 */
|
||||
.basic-info, .usage-bars { display: flex; flex-direction: column; gap: 12px; }
|
||||
.usage-bars { padding-top: 5px; }
|
||||
.usage-item { display: flex; flex-direction: column; gap: 6px; }
|
||||
|
||||
.info-item { display: flex; flex-direction: column; }
|
||||
.label { font-size: 0.75rem; color: #95a5a6; margin-bottom: 2px; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
|
||||
.label { font-size: 0.75rem; color: #95a5a6; margin-bottom: 2px; text-transform: uppercase; font-weight: 600; }
|
||||
.value { font-size: 1rem; font-weight: 500; color: #2c3e50; }
|
||||
.text-ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
|
||||
.progress-label { display: flex; justify-content: space-between; font-size: 0.85rem; color: #636e72; }
|
||||
.progress-bar-large { width: 100%; height: 16px; background: #f1f3f5; border-radius: 8px; overflow: hidden; }
|
||||
.fill { height: 100%; transition: width 0.6s ease; }
|
||||
|
||||
.list-container { display: flex; flex-direction: column; gap: 10px; }
|
||||
.list-item { background: #f8f9fa; padding: 10px 14px; border-radius: 6px; border: 1px solid #edf2f7; }
|
||||
.list-item.error-item { background: #fff5f5; border-color: #fed7d7; }
|
||||
.list-item.warning-item { background: #fffaf0; border-color: #fce588; }
|
||||
|
||||
.item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
|
||||
.description { margin: 0; color: #636e72; font-size: 0.9rem; line-height: 1.4; }
|
||||
.description.highlight { color: #d35400; font-weight: 500; }
|
||||
.description.raw-message { margin-top: 6px; font-size: 0.8rem; color: #7f8c8d; font-family: Consolas, monospace; background: #fff; padding: 4px 8px; border-radius: 4px; border: 1px solid #eee; }
|
||||
|
||||
.status-text { font-weight: 600; font-size: 0.9rem; }
|
||||
.description.raw-message { margin-top: 6px; font-size: 0.8rem; color: #7f8c8d; font-family: Consolas, monospace; background: #fff; padding: 4px 8px; border-radius: 4px; border: 1px solid #eee; word-break: break-all; }
|
||||
.text-green { color: #27ae60; } .text-red { color: #c0392b; }
|
||||
|
||||
.badge { font-size: 0.7rem; padding: 2px 8px; border-radius: 10px; color: white; font-weight: 600; }
|
||||
.badge-green { background-color: #2ed573; } .badge-red { background-color: #ff4757; } .badge-blue { background-color: #3498db; } .badge-gray { background-color: #95a5a6; }
|
||||
.badge-red { background-color: #ff4757; } .badge-green { background-color: #2ed573; } .badge-blue { background-color: #3498db; }
|
||||
.code-tag { background: #ff4757; color: white; padding: 1px 5px; border-radius: 3px; font-size: 0.75rem; font-weight: bold; }
|
||||
|
||||
.good-news { color: #27ae60; font-weight: 600; background: #eafaf1; padding: 12px; border-radius: 6px; border: 1px solid #d4efdf; text-align: center; font-size: 0.9rem; }
|
||||
.tip { margin-top: 12px; font-size: 0.85rem; color: #d35400; background: #fff3e0; padding: 10px; border-radius: 6px; border: 1px solid #ffe0b2; }
|
||||
|
||||
.progress-wrapper { margin-bottom: 12px; }
|
||||
.progress-bar { width: 100%; height: 8px; background: #f1f3f5; border-radius: 4px; overflow: hidden; }
|
||||
|
||||
.event-id { font-weight: bold; color: #2c3e50; background: #e2e8f0; padding: 1px 5px; border-radius: 3px; margin-right: 8px; font-size: 0.75rem; }
|
||||
.event-time { margin-left: auto; font-size: 0.75rem; color: #95a5a6; }
|
||||
.content-box { padding: 5px 0; }
|
||||
.main-text { font-weight: 500; color: #2c3e50; margin-bottom: 8px; }
|
||||
.content-box { padding: 5px 0; } .main-text { font-weight: 500; color: #2c3e50; margin-bottom: 8px; }
|
||||
.card.danger { border-top-color: #ff4757; }
|
||||
.primary-btn { background: #2ecc71; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: 600; display: flex; align-items: center; gap: 8px; transition: 0.2s; }
|
||||
.primary-btn:hover:not(:disabled) { background: #27ae60; } .primary-btn:disabled { background: #bdc3c7; cursor: not-allowed; }
|
||||
.secondary-btn { background: white; border: 1px solid #dcdfe6; color: #606266; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 0.9rem; }
|
||||
.secondary-btn:hover:not(:disabled) { border-color: #2ecc71; color: #2ecc71; }
|
||||
.icon-btn { background: transparent; border: none; cursor: pointer; padding: 8px; color: #7f8c8d; font-weight: 600; }
|
||||
.icon-btn:hover:not(:disabled) { color: #2ecc71; background: #eafaf1; border-radius: 6px; }
|
||||
.toast-notification { position: fixed; top: 20px; right: 20px; background: white; border-left: 5px solid #2ecc71; padding: 15px 20px; border-radius: 8px; box-shadow: 0 5px 20px rgba(0,0,0,0.15); z-index: 100; min-width: 250px; }
|
||||
.toast-notification.error { border-left-color: #ff4757; }
|
||||
.toast-content { display: flex; gap: 12px; } .toast-text h4 { margin: 0 0 4px; font-size: 1rem; } .toast-text p { margin: 0; color: #7f8c8d; font-size: 0.85rem; }
|
||||
.toast-slide-enter-active, .toast-slide-leave-active { transition: all 0.3s; } .toast-slide-enter-from, .toast-slide-leave-to { transform: translateX(100%); opacity: 0; }
|
||||
@keyframes spin { 100% { transform: rotate(360deg); } }
|
||||
.fade-in { animation: fadeIn 0.4s ease-out; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
.slide-up { animation: slideUp 0.4s ease-out forwards; } @keyframes slideUp { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
|
||||
.bsod-layout { display: grid; grid-template-columns: 300px 1fr; gap: 20px; height: calc(100vh - 120px); }
|
||||
.bsod-list-panel { background: white; border-radius: 10px; border: 1px solid #eaecf0; overflow-y: auto; }
|
||||
.bsod-detail-panel { background: white; border-radius: 10px; border: 1px solid #eaecf0; padding: 25px; overflow-y: auto; position: relative; }
|
||||
.file-item { padding: 15px; border-bottom: 1px solid #f0f2f5; cursor: pointer; display: flex; gap: 12px; transition: background 0.2s; }
|
||||
.file-item:hover { background: #f8f9fa; } .file-item.active { background: #eafaf1; border-left: 4px solid #2ecc71; }
|
||||
.file-icon { font-size: 1.5rem; } .file-name { font-weight: 600; font-size: 0.9rem; color: #2c3e50; margin-bottom: 4px; } .file-meta { font-size: 0.8rem; color: #95a5a6; }
|
||||
.empty-state { padding: 40px; text-align: center; color: #95a5a6; font-size: 0.9rem; line-height: 1.6; }
|
||||
.analyzing-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #7f8c8d; }
|
||||
.spinner { width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #2ecc71; border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 15px; }
|
||||
.empty-detail { display: flex; align-items: center; justify-content: center; height: 100%; color: #95a5a6; font-size: 1.1rem; }
|
||||
.result-header { border-bottom: 1px solid #eee; padding-bottom: 15px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; }
|
||||
.result-header h3 { margin: 0; color: #c0392b; font-size: 1.4rem; } .code-pill { background: #2c3e50; color: white; padding: 4px 10px; border-radius: 4px; font-family: monospace; font-weight: bold; }
|
||||
.card-section { margin-bottom: 25px; } .card-section h4 { margin: 0 0 10px 0; font-size: 1rem; color: #7f8c8d; text-transform: uppercase; letter-spacing: 0.5px; border-left: 3px solid #2ecc71; padding-left: 10px; }
|
||||
.human-text { font-size: 1.1rem; color: #2c3e50; line-height: 1.6; margin-bottom: 10px; }
|
||||
.recommendation { background: #eafaf1; color: #27ae60; padding: 15px; border-radius: 8px; font-weight: 500; }
|
||||
.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; } .d-item { background: #f8f9fa; padding: 10px; border-radius: 6px; display: flex; flex-direction: column; } .d-label { font-size: 0.75rem; color: #95a5a6; margin-bottom: 4px; }
|
||||
.message-box { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; font-size: 0.9rem; font-weight: 500; }
|
||||
.message-box.success { background-color: #eafaf1; color: #27ae60; border: 1px solid #d4efdf; }
|
||||
.message-box.error { background-color: #ffeaea; color: #c0392b; border: 1px solid #ffcccc; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user