Compare commits
7 Commits
06e84856dd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daa0178185 | ||
|
|
4be0fdcb74 | ||
|
|
93164a86d8 | ||
|
|
0ff1014e62 | ||
|
|
26d04daceb | ||
|
|
79a796f037 | ||
|
|
9e6ad62a91 |
BIN
app-icon.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
@@ -2,9 +2,8 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tauri + Vue 3 App</title>
|
||||
<title>System Doctor</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg width="206" height="231" viewBox="0 0 206 231" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M143.143 84C143.143 96.1503 133.293 106 121.143 106C108.992 106 99.1426 96.1503 99.1426 84C99.1426 71.8497 108.992 62 121.143 62C133.293 62 143.143 71.8497 143.143 84Z" fill="#FFC131"/>
|
||||
<ellipse cx="84.1426" cy="147" rx="22" ry="22" transform="rotate(180 84.1426 147)" fill="#24C8DB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.738 154.548C157.86 160.286 148.023 164.269 137.757 166.341C139.858 160.282 141 153.774 141 147C141 144.543 140.85 142.121 140.558 139.743C144.975 138.204 149.215 136.139 153.183 133.575C162.73 127.404 170.292 118.608 174.961 108.244C179.63 97.8797 181.207 86.3876 179.502 75.1487C177.798 63.9098 172.884 53.4021 165.352 44.8883C157.82 36.3744 147.99 30.2165 137.042 27.1546C126.095 24.0926 114.496 24.2568 103.64 27.6274C92.7839 30.998 83.1319 37.4317 75.8437 46.1553C74.9102 47.2727 74.0206 48.4216 73.176 49.5993C61.9292 50.8488 51.0363 54.0318 40.9629 58.9556C44.2417 48.4586 49.5653 38.6591 56.679 30.1442C67.0505 17.7298 80.7861 8.57426 96.2354 3.77762C111.685 -1.01901 128.19 -1.25267 143.769 3.10474C159.348 7.46215 173.337 16.2252 184.056 28.3411C194.775 40.457 201.767 55.4101 204.193 71.404C206.619 87.3978 204.374 103.752 197.73 118.501C191.086 133.25 180.324 145.767 166.738 154.548ZM41.9631 74.275L62.5557 76.8042C63.0459 72.813 63.9401 68.9018 65.2138 65.1274C57.0465 67.0016 49.2088 70.087 41.9631 74.275Z" fill="#FFC131"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.4045 76.4519C47.3493 70.6709 57.2677 66.6712 67.6171 64.6132C65.2774 70.9669 64 77.8343 64 85.0001C64 87.1434 64.1143 89.26 64.3371 91.3442C60.0093 92.8732 55.8533 94.9092 51.9599 97.4256C42.4128 103.596 34.8505 112.392 30.1816 122.756C25.5126 133.12 23.9357 144.612 25.6403 155.851C27.3449 167.09 32.2584 177.598 39.7906 186.112C47.3227 194.626 57.153 200.784 68.1003 203.846C79.0476 206.907 90.6462 206.743 101.502 203.373C112.359 200.002 122.011 193.568 129.299 184.845C130.237 183.722 131.131 182.567 131.979 181.383C143.235 180.114 154.132 176.91 164.205 171.962C160.929 182.49 155.596 192.319 148.464 200.856C138.092 213.27 124.357 222.426 108.907 227.222C93.458 232.019 76.9524 232.253 61.3736 227.895C45.7948 223.538 31.8055 214.775 21.0867 202.659C10.3679 190.543 3.37557 175.59 0.949823 159.596C-1.47592 143.602 0.768139 127.248 7.41237 112.499C14.0566 97.7497 24.8183 85.2327 38.4045 76.4519ZM163.062 156.711L163.062 156.711C162.954 156.773 162.846 156.835 162.738 156.897C162.846 156.835 162.954 156.773 163.062 156.711Z" fill="#24C8DB"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
11
src-tauri/Cargo.lock
generated
@@ -872,15 +872,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs_io"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.0"
|
||||
@@ -3718,8 +3709,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"csv",
|
||||
"encoding_rs",
|
||||
"encoding_rs_io",
|
||||
"minidump",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
[package]
|
||||
name = "system-doctor"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
description = "System diagnosis"
|
||||
authors = ["Julian"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "system_doctor_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
@@ -27,11 +20,7 @@ wmi = "0.13"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
minidump = "0.19"
|
||||
# [新增] 用于解析 BlueScreenView 导出的 CSV
|
||||
csv = "1.3"
|
||||
# [新增] 用于处理可能的文件编码问题
|
||||
encoding_rs = "0.8"
|
||||
encoding_rs_io = "0.1"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 1.7 KiB |
BIN
src-tauri/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
</adaptive-icon>
|
||||
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 74 KiB |
BIN
src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#fff</color>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 80 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@1x.png
Normal file
|
After Width: | Height: | Size: 859 B |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x-1.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src-tauri/icons/ios/AppIcon-20x20@3x.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@1x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x-1.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@2x.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src-tauri/icons/ios/AppIcon-29x29@3x.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@1x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x-1.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@2x.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src-tauri/icons/ios/AppIcon-40x40@3x.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
src-tauri/icons/ios/AppIcon-512@2x.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
src-tauri/icons/ios/AppIcon-60x60@2x.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
src-tauri/icons/ios/AppIcon-60x60@3x.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src-tauri/icons/ios/AppIcon-76x76@1x.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
src-tauri/icons/ios/AppIcon-76x76@2x.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -1,14 +0,0 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// src-tauri/src/main.rs
|
||||
|
||||
// 全局允许非标准命名风格
|
||||
#![allow(non_camel_case_types, non_snake_case)]
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
@@ -9,7 +7,6 @@ use sysinfo::{System, Disks};
|
||||
use wmi::{COMLibrary, WMIConnection};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
// [修复] 移除了未使用的 Write
|
||||
use std::io::Read;
|
||||
use std::process::Command;
|
||||
use tauri::Emitter;
|
||||
@@ -132,61 +129,112 @@ fn format_wmi_time(wmi_str: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// [扩展] 翻译 BugCheck Code (u32) - 增加了更多常见代码
|
||||
fn translate_bugcheck_u32(code: u32) -> (String, String) {
|
||||
match code {
|
||||
// 驱动与内存相关
|
||||
0x0000000A => (
|
||||
"IRQL_NOT_LESS_OR_EQUAL (0x0A)".to_string(),
|
||||
"驱动程序使用了不正确的内存地址。通常由有缺陷的驱动程序(如杀毒软件、虚拟光驱驱动)或硬件兼容性问题引起。建议更新所有驱动程序。"
|
||||
.to_string(),
|
||||
),
|
||||
0x000000D1 => (
|
||||
"DRIVER_IRQL_NOT_LESS_OR_EQUAL (0xD1)".to_string(),
|
||||
"驱动程序使用了不正确的内存地址。通常是驱动程序冲突或损坏。请检查最近安装的硬件驱动(显卡、网卡等),尝试回滚或更新驱动。"
|
||||
"驱动程序尝试访问未分页内存。这是最常见的蓝屏之一,通常是驱动程序冲突或损坏。请检查最近安装的硬件驱动(显卡、网卡等)。"
|
||||
.to_string(),
|
||||
),
|
||||
0x0000007E | 0x1000007E => (
|
||||
"SYSTEM_THREAD_EXCEPTION_NOT_HANDLED (0x7E)".to_string(),
|
||||
"系统线程抛出了未捕获的异常。可能是显卡驱动不兼容,或者BIOS设置问题。建议重装显卡驱动,或恢复BIOS默认设置。"
|
||||
"系统线程抛出了未捕获的异常。可能是显卡驱动不兼容,或者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设备故障。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000116 => (
|
||||
"VIDEO_TDR_FAILURE (0x116)".to_string(),
|
||||
"显卡响应超时。显卡驱动崩溃或显卡过热。如果你在玩游戏,可能是显卡超频不稳定或散热硅脂干了。"
|
||||
0x0000001E => (
|
||||
"KMODE_EXCEPTION_NOT_HANDLED (0x1E)".to_string(),
|
||||
"内核模式程序生成了处理器无法捕获的异常。通常是硬件兼容性问题或驱动程序错误。建议检查是否有新安装的硬件。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000050 => (
|
||||
"PAGE_FAULT_IN_NONPAGED_AREA (0x50)".to_string(),
|
||||
"试图访问无效的内存地址。可能是内存条故障,或者是防病毒软件/驱动程序冲突。建议检查内存。"
|
||||
"试图访问无效的内存地址。极大概率是内存条故障(接触不良或损坏),或者是防病毒软件/驱动程序冲突。建议运行内存诊断。"
|
||||
.to_string(),
|
||||
),
|
||||
0x0000000A => (
|
||||
"IRQL_NOT_LESS_OR_EQUAL (0x0A)".to_string(),
|
||||
"驱动程序使用了不正确的内存地址。通常由有缺陷的驱动程序或硬件兼容性问题引起。"
|
||||
0x0000001A => (
|
||||
"MEMORY_MANAGEMENT (0x1A)".to_string(),
|
||||
"严重的内存管理错误。高度怀疑内存条物理故障。建议立即运行 Windows 内存诊断工具,或者尝试拔插/更换内存条。"
|
||||
.to_string(),
|
||||
),
|
||||
0x0000003B => (
|
||||
"SYSTEM_SERVICE_EXCEPTION (0x3B)".to_string(),
|
||||
"系统服务执行异常。通常与图形驱动程序或过时的系统文件有关。"
|
||||
|
||||
// 硬件与系统核心相关
|
||||
0x00000124 => (
|
||||
"WHEA_UNCORRECTABLE_ERROR (0x124)".to_string(),
|
||||
"硬件发生无法纠正的物理错误。这是纯硬件故障。通常是 CPU 电压不足(缩缸)、超频失败、过热,或者主板/PCIe设备故障。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000101 => (
|
||||
"CLOCK_WATCHDOG_TIMEOUT (0x101)".to_string(),
|
||||
"CPU 核心死锁。系统检测到某个 CPU 核心在规定时间内没有响应。通常是因为 CPU 超频不稳定、电压不足或过热。"
|
||||
.to_string(),
|
||||
),
|
||||
0x000000EF => (
|
||||
"CRITICAL_PROCESS_DIED (0xEF)".to_string(),
|
||||
"Windows 核心进程意外终止。通常是系统文件严重损坏或硬盘出现坏道导致无法读取系统文件。建议运行 'sfc /scannow'。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000133 => (
|
||||
"DPC_WATCHDOG_VIOLATION (0x133)".to_string(),
|
||||
"DPC 看门狗超时。通常是 SSD 固件过旧或无线网卡驱动冲突导致系统卡死时间过长。"
|
||||
"DPC 看门狗超时。系统被某个驱动程序(通常是 SSD 固件、SATA 驱动或无线网卡驱动)卡死太久。建议更新 SSD 固件和主板芯片组驱动。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000139 => (
|
||||
"KERNEL_SECURITY_CHECK_FAILURE (0x139)".to_string(),
|
||||
"内核检测到关键数据结构损坏。通常由内存错误、驱动程序 Bug 或病毒导致。建议检查内存和更新驱动。"
|
||||
.to_string(),
|
||||
),
|
||||
|
||||
// 显卡与电源相关
|
||||
0x00000116 => (
|
||||
"VIDEO_TDR_FAILURE (0x116)".to_string(),
|
||||
"显卡响应超时。显卡驱动崩溃且无法恢复。如果你在玩游戏,可能是显卡超频不稳定、电源功率不足或散热硅脂干了。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000119 => (
|
||||
"VIDEO_SCHEDULER_INTERNAL_ERROR (0x119)".to_string(),
|
||||
"显卡调度器内部错误。这表明显卡驱动发送了无效数据,或者是显卡显存(VRAM)出现故障。"
|
||||
.to_string(),
|
||||
),
|
||||
0x0000009F => (
|
||||
"DRIVER_POWER_STATE_FAILURE (0x9F)".to_string(),
|
||||
"驱动程序电源状态故障。通常发生在电脑休眠、睡眠或唤醒时。某个设备的驱动程序没有正确响应电源指令。建议检查电源管理设置。"
|
||||
.to_string(),
|
||||
),
|
||||
|
||||
// 存储与启动相关
|
||||
0x0000007B => (
|
||||
"INACCESSIBLE_BOOT_DEVICE (0x7B)".to_string(),
|
||||
"无法访问启动设备。通常是 BIOS 中的硬盘模式设置错误(如 AHCI/RAID 切换)或引导扇区损坏。也可能是硬盘物理连接松动。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000024 => (
|
||||
"NTFS_FILE_SYSTEM (0x24)".to_string(),
|
||||
"NTFS 文件系统损坏。这通常意味着硬盘出现坏道或文件系统逻辑错误。建议立即运行 'chkdsk /f /r' 修复磁盘。"
|
||||
.to_string(),
|
||||
),
|
||||
0x00000154 => (
|
||||
"UNEXPECTED_STORE_EXCEPTION (0x154)".to_string(),
|
||||
"系统存储组件检测到意外异常。通常指向系统盘(SSD/HDD)出现故障或连接线松动,导致系统无法读取关键数据。"
|
||||
.to_string(),
|
||||
),
|
||||
|
||||
// 系统服务异常
|
||||
0x0000003B => (
|
||||
"SYSTEM_SERVICE_EXCEPTION (0x3B)".to_string(),
|
||||
"系统服务执行异常。通常与图形驱动程序、系统文件损坏或过时的驱动有关。"
|
||||
.to_string(),
|
||||
),
|
||||
_ => (
|
||||
format!("未知错误代码: 0x{:X}", code),
|
||||
"建议手动搜索此错误代码。通用建议:更新驱动、检查内存、扫描病毒。".to_string(),
|
||||
"此代码较为罕见,建议手动在搜索引擎中搜索此错误代码。通用排查步骤:1. 更新所有驱动;2. 运行内存诊断;3. 检查系统文件完整性。"
|
||||
.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -232,53 +280,70 @@ where T: Deref<Target = [u8]>
|
||||
})
|
||||
}
|
||||
|
||||
// [修复] BlueScreenView 外部调用分析 (适配新的命令行格式和 CSV 列)
|
||||
// [修复] BlueScreenView 外部调用分析 (使用有损 UTF-8 转换)
|
||||
fn analyze_with_bluescreenview(dump_path: &Path) -> Result<BsodAnalysisReport, String> {
|
||||
let bsv_exe = "BlueScreenView.exe";
|
||||
|
||||
let mut temp_csv_path = std::env::temp_dir();
|
||||
temp_csv_path.push(format!("bsod_report_{}.csv", chrono::Utc::now().timestamp_millis()));
|
||||
|
||||
// [修改] 命令行格式: BlueScreenView.exe <DumpFile> /scomma <OutFile>
|
||||
// BlueScreenView.exe /LoadFrom 3 /SingleDumpFile <DumpFilePath> /scomma <OutFile>
|
||||
let status = Command::new(bsv_exe)
|
||||
.arg(dump_path.to_string_lossy().to_string()) // 直接传文件路径
|
||||
.arg("/LoadFrom")
|
||||
.arg("3")
|
||||
.arg("/SingleDumpFile")
|
||||
.arg(dump_path.to_string_lossy().to_string())
|
||||
.arg("/scomma")
|
||||
.arg(temp_csv_path.to_string_lossy().to_string())
|
||||
.status();
|
||||
|
||||
if status.is_err() {
|
||||
return Err("未找到 BlueScreenView.exe,无法分析内核转储文件。请将 BlueScreenView.exe 放入程序目录。".to_string());
|
||||
return Err("未找到 BlueScreenView.exe,或没有以管理员运行。".to_string());
|
||||
}
|
||||
|
||||
// 读取字节并转为 String (lossy 模式,防止 GBK/ANSI 乱码导致 panic)
|
||||
// [修改] 使用 fs::read 读取原始字节,然后有损转换为 String
|
||||
// 这样即使文件是 UTF-16 或者包含非法 UTF-8 字符,也不会 panic
|
||||
let content_bytes = fs::read(&temp_csv_path).map_err(|_| "BlueScreenView 分析未生成有效数据。".to_string())?;
|
||||
let _ = fs::remove_file(temp_csv_path);
|
||||
let content = String::from_utf8_lossy(&content_bytes);
|
||||
|
||||
// 使用 csv crate 解析
|
||||
// 1. 简单判断:如果包含很多 0 字节,可能是 UTF-16,尝试手动过滤 0 (简单粗暴但对 CSV 有效)
|
||||
// 2. 或者直接 lossy 转换,虽然 UTF-16 会乱码,但 CSV crate 有时能容忍乱码分隔符
|
||||
// 最稳妥的方式其实是先尝试 UTF-16LE 转换逻辑(但我们刚才移除了 encoding_rs),
|
||||
// 所以这里我们用一个简单的技巧:如果文件头两个字节是 FF FE,手动跳过 BOM
|
||||
|
||||
let content_string = if content_bytes.len() > 2 && content_bytes[0] == 0xFF && content_bytes[1] == 0xFE {
|
||||
// 简易 UTF-16LE 转 UTF-8 (只针对 ASCII 字符有效,对中文可能会乱码,但在这个场景下够用了)
|
||||
// BlueScreenView 的列名和关键 hex 代码都是 ASCII
|
||||
let mut s = String::new();
|
||||
for i in (2..content_bytes.len()).step_by(2) {
|
||||
if i+1 < content_bytes.len() {
|
||||
// 取低位字节,忽略高位 0x00
|
||||
let c = content_bytes[i] as char;
|
||||
s.push(c);
|
||||
}
|
||||
}
|
||||
s
|
||||
} else {
|
||||
String::from_utf8_lossy(&content_bytes).to_string()
|
||||
};
|
||||
|
||||
let mut rdr = csv::ReaderBuilder::new()
|
||||
.has_headers(false) // 命令行模式导出的 CSV 没有 Header,第一行就是数据
|
||||
.from_reader(content.as_bytes());
|
||||
.has_headers(false)
|
||||
.from_reader(content_string.as_bytes());
|
||||
|
||||
for result in rdr.records() {
|
||||
if let Ok(record) = result {
|
||||
// BlueScreenView CSV 格式 (基于上传的文件):
|
||||
// Index 0: Dump File (112525-7375-01.dmp)
|
||||
// Index 2: Bug Check String (MEMORY_MANAGEMENT)
|
||||
// Index 3: Bug Check Code (0x0000001a)
|
||||
// Index 8: Caused By Driver (ntoskrnl.exe)
|
||||
// Index 15: Crash Address (ntoskrnl.exe+41e230)
|
||||
|
||||
if record.len() > 15 {
|
||||
// 第一行就是数据,不需要跳过 Header
|
||||
// 跳过 Header 行
|
||||
if &record[0] == "Dump File" { continue; }
|
||||
|
||||
let bug_check_string = &record[2];
|
||||
let bug_check_code = &record[3];
|
||||
let caused_by_driver = &record[8];
|
||||
let crash_addr = &record[15];
|
||||
let crash_addr = if record.len() > 15 { &record[15] } else { "N/A" };
|
||||
|
||||
let (human, recommend) = translate_bugcheck_str(bug_check_code);
|
||||
|
||||
// 优先使用 BlueScreenView 识别出的 Bug Check String
|
||||
let final_reason = if !bug_check_string.is_empty() { bug_check_string.to_string() } else { human };
|
||||
|
||||
return Ok(BsodAnalysisReport {
|
||||
@@ -375,7 +440,7 @@ async fn analyze_minidump(filepath: String) -> Result<BsodAnalysisReport, String
|
||||
}
|
||||
}
|
||||
|
||||
// [修复] 命令:分析二进制内容的 Minidump
|
||||
// 命令:分析二进制内容的 Minidump
|
||||
#[tauri::command]
|
||||
async fn analyze_minidump_bytes(file_content: Vec<u8>) -> Result<BsodAnalysisReport, String> {
|
||||
if file_content.is_empty() {
|
||||
@@ -386,7 +451,6 @@ async fn analyze_minidump_bytes(file_content: Vec<u8>) -> Result<BsodAnalysisRep
|
||||
|
||||
match dump_type {
|
||||
DumpType::Minidump => {
|
||||
// [修复] 修复 Error 类型转换
|
||||
let native_result = Minidump::read(file_content.clone())
|
||||
.map_err(|e| e.to_string())
|
||||
.and_then(|dump| analyze_dump_data_native(dump));
|
||||
@@ -394,7 +458,6 @@ async fn analyze_minidump_bytes(file_content: Vec<u8>) -> Result<BsodAnalysisRep
|
||||
if let Ok(report) = native_result {
|
||||
return Ok(report);
|
||||
}
|
||||
// 失败回退到 BSV
|
||||
},
|
||||
DumpType::KernelDump => {
|
||||
// 直接回退
|
||||
@@ -402,7 +465,6 @@ async fn analyze_minidump_bytes(file_content: Vec<u8>) -> Result<BsodAnalysisRep
|
||||
DumpType::Unknown => return Err("无效的文件签名。请确认这是 .dmp 文件。".to_string())
|
||||
}
|
||||
|
||||
// --- 回退流程:写入临时文件调用 BlueScreenView ---
|
||||
let mut temp_dump_path = std::env::temp_dir();
|
||||
temp_dump_path.push(format!("temp_dump_{}.dmp", chrono::Utc::now().timestamp_millis()));
|
||||
|
||||
@@ -582,7 +644,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> {
|
||||
analysis_hint: hint,
|
||||
});
|
||||
}
|
||||
if events.len() >= 10 { break; }
|
||||
// [修改] 移除了数量限制,让它获取所有符合条件的日志
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "system-doctor",
|
||||
"title": "系统检查 v0.1",
|
||||
"width": 1200,
|
||||
"height": 800
|
||||
}
|
||||
|
||||
113
src/App.vue
@@ -12,7 +12,7 @@
|
||||
:class="{ active: currentTab === 'overview' }"
|
||||
@click="currentTab = 'overview'"
|
||||
>
|
||||
<span class="nav-icon">📊</span> 静态概览
|
||||
<span class="nav-icon">📊</span> 健康概览
|
||||
</button>
|
||||
<button
|
||||
class="nav-item"
|
||||
@@ -21,9 +21,16 @@
|
||||
>
|
||||
<span class="nav-icon">☠️</span> 蓝屏分析
|
||||
</button>
|
||||
<button
|
||||
class="nav-item"
|
||||
:class="{ active: currentTab === 'logs' }"
|
||||
@click="currentTab = 'logs'"
|
||||
>
|
||||
<span class="nav-icon">⚡</span> 系统日志
|
||||
</button>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">Pro v1.2</span>
|
||||
<span class="version">Generated by AI</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -48,7 +55,6 @@
|
||||
|
||||
<!-- 状态提示 -->
|
||||
<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" />
|
||||
|
||||
@@ -133,24 +139,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. 日志卡片 -->
|
||||
<div v-if="report.events" class="card slide-up" :class="{ danger: report.events.length > 0 }">
|
||||
<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" :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>
|
||||
<!-- [已移除] 5. 蓝屏分析卡片 -->
|
||||
<!-- [已移除] 6. 关键日志卡片 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -224,6 +214,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 3: 系统日志 -->
|
||||
<div v-if="currentTab === 'logs'" class="tab-view logs-view">
|
||||
<div class="view-header">
|
||||
<h2>系统关键日志 (近30天)</h2>
|
||||
<div class="actions-row">
|
||||
<button class="secondary-btn" @click="startScan" :disabled="loading">🔄 刷新日志</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logs-container fade-in">
|
||||
<div v-if="!report.events" class="empty-state">
|
||||
<span v-if="loading">正在读取日志记录...</span>
|
||||
<span v-else>请先点击“全面体检”或“刷新日志”以获取数据。</span>
|
||||
</div>
|
||||
<div v-else-if="report.events.length === 0" class="good-news large">
|
||||
✅ 过去30天内未发现致命错误。
|
||||
</div>
|
||||
<div v-else class="log-list-full">
|
||||
<div v-for="(evt, idx) in report.events" :key="idx" class="log-card warning-item">
|
||||
<div class="log-header">
|
||||
<div class="log-meta">
|
||||
<span class="event-id">ID: {{ evt.event_id }}</span>
|
||||
<span class="event-time">{{ formatTime(evt.time_generated) }}</span>
|
||||
</div>
|
||||
<span class="event-source">{{ evt.source }}</span>
|
||||
</div>
|
||||
<div class="log-body">
|
||||
<p class="description highlight">{{ evt.analysis_hint }}</p>
|
||||
<p v-if="evt.message" class="description raw-message">{{ evt.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Toast 通知 -->
|
||||
@@ -245,14 +270,15 @@ import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
|
||||
// --- 状态管理 ---
|
||||
const currentTab = ref('overview'); // 'overview' | 'bsod'
|
||||
const currentTab = ref('overview'); // 'overview' | 'bsod' | 'logs'
|
||||
|
||||
// 概览相关
|
||||
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);
|
||||
// [移除] scanFinished 状态变量,因为不再需要显示 div 提示
|
||||
// const scanFinished = ref(false);
|
||||
const fileInput = ref(null);
|
||||
let unlistenFns = [];
|
||||
|
||||
@@ -280,6 +306,17 @@ 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); }
|
||||
|
||||
// JS日期格式化函数:yyyy-MM-dd HH:mm:ss
|
||||
function formatJsDate(date) {
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const h = String(date.getHours()).padStart(2, '0');
|
||||
const min = String(date.getMinutes()).padStart(2, '0');
|
||||
const s = String(date.getSeconds()).padStart(2, '0');
|
||||
return `${y}-${m}-${d} ${h}:${min}:${s}`;
|
||||
}
|
||||
|
||||
// --- 概览:导出/导入 ---
|
||||
async function exportReport() {
|
||||
if (!isReportValid.value) return;
|
||||
@@ -298,10 +335,13 @@ async function exportReport() {
|
||||
} 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; scanFinished.value = false; triggerToast('导入成功', '已加载历史报告'); } else { triggerToast('导入失败', '文件格式错误', 'error'); } } catch (err) { 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; triggerToast('导入成功', '已加载历史报告'); } else { triggerToast('导入失败', '文件格式错误', 'error'); } } catch (err) { triggerToast('解析失败', err.message, 'error'); } }; reader.readAsText(file); event.target.value = ''; }
|
||||
|
||||
async function startScan() {
|
||||
report.value = emptyReport(); errorMsg.value = ''; scanFinished.value = false; loading.value = true;
|
||||
// 只有在从未扫描过时清空,或者强制刷新
|
||||
// report.value = emptyReport();
|
||||
errorMsg.value = '';
|
||||
loading.value = true;
|
||||
if (unlistenFns.length > 0) { for (const fn of unlistenFns) fn(); unlistenFns = []; }
|
||||
try {
|
||||
unlistenFns.push(
|
||||
@@ -311,7 +351,7 @@ async function startScan() {
|
||||
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 listen('diagnosis-finished', () => { loading.value = false; triggerToast('体检完成', '所有项目检查完毕', 'success'); })
|
||||
);
|
||||
await invoke('run_diagnosis');
|
||||
} catch (e) { loading.value = false; errorMsg.value = e; }
|
||||
@@ -323,7 +363,7 @@ async function loadMinidumps() { bsodLoading.value = true; try { bsodList.value
|
||||
async function analyzeBsod(file) {
|
||||
if (bsodAnalyzing.value) return;
|
||||
|
||||
// [新增] 提前检查文件大小,避免读取几百MB的 Kernel Dump 导致卡死
|
||||
// 提前检查文件大小,避免读取几百MB的 Kernel Dump 导致卡死
|
||||
if (file.fileRef && file.fileRef.size > 5 * 1024 * 1024) { // 5MB 限制
|
||||
triggerToast('文件过大', '仅支持 <5MB 的 Minidump 文件,不支持完整的 MEMORY.DMP', 'error');
|
||||
return;
|
||||
@@ -346,7 +386,7 @@ async function analyzeBsod(file) {
|
||||
bsodResult.value = await invoke('analyze_minidump', { filepath: file.path });
|
||||
}
|
||||
} catch (e) {
|
||||
// [修改] 优化错误提示:拦截 "Header mismatch" 等晦涩的报错
|
||||
// 优化错误提示
|
||||
let errorMsg = e;
|
||||
if (typeof e === 'string') {
|
||||
if (e.includes("Header mismatch")) {
|
||||
@@ -371,7 +411,6 @@ function handleBsodFileImport(event) {
|
||||
for (let i = files.length - 1; i >= 0; i--) {
|
||||
const file = files[i];
|
||||
|
||||
// [新增] 简单的后缀名检查
|
||||
if (!file.name.toLowerCase().endsWith('.dmp')) {
|
||||
continue;
|
||||
}
|
||||
@@ -380,7 +419,8 @@ function handleBsodFileImport(event) {
|
||||
filename: file.name,
|
||||
path: `imported-${file.name}-${Date.now()}-${i}`,
|
||||
size_kb: Math.round(file.size / 1024),
|
||||
created_time: new Date(file.lastModified).toLocaleString(),
|
||||
// [修改] 使用自定义格式化函数,替代 toLocaleString
|
||||
created_time: formatJsDate(new Date(file.lastModified)),
|
||||
fileRef: file
|
||||
};
|
||||
bsodList.value.unshift(newItem);
|
||||
@@ -459,6 +499,7 @@ body { margin: 0; padding: 0; background-color: #f4f6f9; overflow: hidden; }
|
||||
.badge-red { background-color: #ff4757; } .badge-green { background-color: #2ed573; } .badge-blue { background-color: #3498db; } .badge-gray { background-color: #95a5a6; }
|
||||
.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; }
|
||||
.good-news.large { padding: 40px; font-size: 1.1rem; }
|
||||
.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; }
|
||||
@@ -503,4 +544,16 @@ body { margin: 0; padding: 0; background-color: #f4f6f9; overflow: hidden; }
|
||||
.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; }
|
||||
|
||||
/* 新增日志视图样式 */
|
||||
.logs-view { display: flex; flex-direction: column; height: 100%; }
|
||||
.logs-container { flex: 1; overflow-y: auto; padding-right: 5px; }
|
||||
.log-list-full { display: flex; flex-direction: column; gap: 15px; }
|
||||
.log-card {
|
||||
background: white; border-radius: 8px; padding: 15px 20px;
|
||||
border: 1px solid #eaecf0; box-shadow: 0 2px 6px rgba(0,0,0,0.02);
|
||||
}
|
||||
.log-card.warning-item { border-left: 4px solid #fce588; }
|
||||
.log-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
||||
.log-meta { display: flex; align-items: center; gap: 10px; }
|
||||
</style>
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 496 B |