optim fpstest ui and try to fix steam path related crash
This commit is contained in:
@@ -156,6 +156,22 @@ pub fn check_path(path: &str) -> Result<bool, String> {
|
||||
Ok(std::path::Path::new(&path).exists())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn check_steam_dir_valid(steam_dir: &str) -> Result<bool, String> {
|
||||
use std::path::Path;
|
||||
|
||||
let path = Path::new(steam_dir);
|
||||
if !path.exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 检查是否存在 steam.exe 或 config 目录(至少有一个即可)
|
||||
let steam_exe = path.join("steam.exe");
|
||||
let config_dir = path.join("config");
|
||||
|
||||
Ok(steam_exe.exists() || config_dir.exists())
|
||||
}
|
||||
|
||||
///// 录像
|
||||
#[tauri::command]
|
||||
pub async fn analyze_replay(app: tauri::AppHandle, path: &str) -> Result<String, String> {
|
||||
|
||||
@@ -162,6 +162,7 @@ fn main() {
|
||||
cmds::get_cs2_video_config,
|
||||
cmds::set_cs2_video_config,
|
||||
cmds::check_path,
|
||||
cmds::check_steam_dir_valid,
|
||||
cmds::analyze_replay,
|
||||
cmds::get_console_log_path,
|
||||
cmds::read_vprof_report,
|
||||
|
||||
@@ -134,7 +134,10 @@ pub fn parse_login_users(steam_dir: &str) -> Result<Vec<LoginUser>> {
|
||||
|
||||
let mut users = Vec::new();
|
||||
for (k, v) in kv {
|
||||
let props = v.as_object().unwrap();
|
||||
let props = match v.as_object() {
|
||||
Some(p) => p,
|
||||
None => continue, // 跳过非对象类型的值
|
||||
};
|
||||
|
||||
let avatar = if let Some(img) = read_avatar(&steam_dir, &k) {
|
||||
img
|
||||
@@ -142,7 +145,11 @@ pub fn parse_login_users(steam_dir: &str) -> Result<Vec<LoginUser>> {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let id64 = k.parse::<u64>()?;
|
||||
// 跳过无法解析为 u64 的键
|
||||
let id64 = match k.parse::<u64>() {
|
||||
Ok(id) => id,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let user = LoginUser {
|
||||
steam_id32: steam::id::id64_to_32(id64),
|
||||
@@ -216,7 +223,11 @@ pub fn parse_local_users(steam_dir: &str) -> Result<Vec<LocalUser>> {
|
||||
|
||||
// 只处理目录
|
||||
if entry.file_type().is_dir() {
|
||||
let id = path.file_name().unwrap().to_str().unwrap();
|
||||
// 安全获取文件名
|
||||
let id = match path.file_name().and_then(|n| n.to_str()) {
|
||||
Some(id_str) => id_str,
|
||||
None => continue, // 跳过无法获取文件名的路径
|
||||
};
|
||||
|
||||
// 检查 localconfig.vdf 文件是否存在
|
||||
let local_config_path = path.join("config/localconfig.vdf");
|
||||
@@ -224,21 +235,26 @@ pub fn parse_local_users(steam_dir: &str) -> Result<Vec<LocalUser>> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 读取并解析 localconfig.vdf 文件
|
||||
let data = fs::read_to_string(local_config_path)?;
|
||||
// 读取并解析 localconfig.vdf 文件,如果失败则跳过
|
||||
let data = match fs::read_to_string(&local_config_path) {
|
||||
Ok(d) => d,
|
||||
Err(_) => continue, // 跳过无法读取的文件
|
||||
};
|
||||
|
||||
let json_data = super::parse::to_json(&data);
|
||||
let kv: HashMap<String, Value> = serde_json::from_str(&json_data)?;
|
||||
let kv = match serde_json::from_str::<HashMap<String, Value>>(&json_data) {
|
||||
Ok(kv) => kv,
|
||||
Err(_) => continue, // 跳过无法解析的 JSON
|
||||
};
|
||||
|
||||
// 剥离顶层 UserLocalConfigStore
|
||||
// let kv = kv.get("UserLocalConfigStore").and_then(|v| v.as_object()).unwrap();
|
||||
|
||||
// 获取 friends 节点
|
||||
let friends = kv.get("friends").and_then(|v| v.as_object());
|
||||
if friends.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let friends = friends.unwrap();
|
||||
let friends = match kv.get("friends").and_then(|v| v.as_object()) {
|
||||
Some(f) => f,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// 获取 PersonaName
|
||||
let persona_name = friends
|
||||
@@ -256,9 +272,15 @@ pub fn parse_local_users(steam_dir: &str) -> Result<Vec<LocalUser>> {
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
// 安全解析 ID,如果失败则跳过
|
||||
let steam_id32 = match id.parse::<u32>() {
|
||||
Ok(id) => id,
|
||||
Err(_) => continue, // 跳过无法解析为 u32 的 ID
|
||||
};
|
||||
|
||||
// 创建 LocalUser 并加入列表
|
||||
local_users.push(LocalUser {
|
||||
steam_id32: id.parse::<u32>().unwrap(),
|
||||
steam_id32,
|
||||
persona_name,
|
||||
avatar_key,
|
||||
});
|
||||
|
||||
@@ -19,12 +19,31 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
void init()
|
||||
|
||||
void listen<string>("tray://launch_game", async (event) => {
|
||||
await invoke("launch_game", {
|
||||
steamPath: `${steamStore.state.steamDir}\\steam.exe`,
|
||||
launchOption: toolStore.state.launchOptions[toolStore.state.launchIndex].option || "",
|
||||
server: event.payload || "worldwide",
|
||||
})
|
||||
addToast({ title: `启动${event.payload === "worldwide" ? "国际服" : "国服"}成功` })
|
||||
// 验证路径
|
||||
if (!steamStore.state.steamDir || !steamStore.state.steamDirValid) {
|
||||
addToast({
|
||||
title: "Steam 路径无效,请先配置路径",
|
||||
color: "warning"
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
await invoke("launch_game", {
|
||||
steamPath: `${steamStore.state.steamDir}\\steam.exe`,
|
||||
launchOption: toolStore.state.launchOptions[toolStore.state.launchIndex].option || "",
|
||||
server: event.payload || "worldwide",
|
||||
})
|
||||
addToast({
|
||||
title: `启动${event.payload === "worldwide" ? "国际服" : "国服"}成功`,
|
||||
color: "success"
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("启动游戏失败:", error)
|
||||
addToast({
|
||||
title: `启动失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
color: "danger"
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
void listen("tray://kill_steam", async () => {
|
||||
@@ -82,10 +101,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
void steam.checkCs2DirValid()
|
||||
}, [debounceCs2Dir])
|
||||
useEffect(() => {
|
||||
if (debounceSteamDirValid) {
|
||||
if (debounceSteamDirValid && steam.state.steamDir) {
|
||||
// 安全地获取用户列表(内部已有错误处理)
|
||||
void steam.getUsers()
|
||||
// 启动文件监听
|
||||
void invoke("start_watch_loginusers", { steamDir: steam.state.steamDir })
|
||||
// 启动文件监听,添加错误处理
|
||||
void invoke("start_watch_loginusers", { steamDir: steam.state.steamDir }).catch((error) => {
|
||||
console.error("启动文件监听失败:", error)
|
||||
})
|
||||
}
|
||||
}, [debounceSteamDirValid, steam.state.steamDir])
|
||||
|
||||
|
||||
@@ -20,13 +20,29 @@ const FastLaunch = () => {
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="md"
|
||||
onPress={() => {
|
||||
void invoke("launch_game", {
|
||||
steamPath: `${steam.state.steamDir}/steam.exe`,
|
||||
launchOption: tool.state.launchOptions[tool.state.launchIndex].option || "",
|
||||
server: "perfectworld",
|
||||
})
|
||||
addToast({ title: "启动国服成功" })
|
||||
onPress={async () => {
|
||||
// 验证路径
|
||||
if (!steam.state.steamDir || !steam.state.steamDirValid) {
|
||||
addToast({
|
||||
title: "Steam 路径无效,请先配置路径",
|
||||
color: "warning"
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
await invoke("launch_game", {
|
||||
steamPath: `${steam.state.steamDir}/steam.exe`,
|
||||
launchOption: tool.state.launchOptions[tool.state.launchIndex].option || "",
|
||||
server: "perfectworld",
|
||||
})
|
||||
addToast({ title: "启动国服成功", color: "success" })
|
||||
} catch (error) {
|
||||
console.error("启动游戏失败:", error)
|
||||
addToast({
|
||||
title: `启动失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
color: "danger"
|
||||
})
|
||||
}
|
||||
}}
|
||||
className="px-4 py-1 font-medium transition bg-red-200 rounded-full select-none dark:bg-red-900/60"
|
||||
>
|
||||
@@ -34,13 +50,29 @@ const FastLaunch = () => {
|
||||
</Button>
|
||||
<Button
|
||||
size="md"
|
||||
onPress={() => {
|
||||
void invoke("launch_game", {
|
||||
steamPath: `${steam.state.steamDir}/steam.exe`,
|
||||
launchOption: tool.state.launchOptions[tool.state.launchIndex].option || "",
|
||||
server: "worldwide",
|
||||
})
|
||||
addToast({ title: "启动国际服成功" })
|
||||
onPress={async () => {
|
||||
// 验证路径
|
||||
if (!steam.state.steamDir || !steam.state.steamDirValid) {
|
||||
addToast({
|
||||
title: "Steam 路径无效,请先配置路径",
|
||||
color: "warning"
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
await invoke("launch_game", {
|
||||
steamPath: `${steam.state.steamDir}/steam.exe`,
|
||||
launchOption: tool.state.launchOptions[tool.state.launchIndex].option || "",
|
||||
server: "worldwide",
|
||||
})
|
||||
addToast({ title: "启动国际服成功", color: "success" })
|
||||
} catch (error) {
|
||||
console.error("启动游戏失败:", error)
|
||||
addToast({
|
||||
title: `启动失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
color: "danger"
|
||||
})
|
||||
}
|
||||
}}
|
||||
className="px-4 py-1 font-medium transition bg-orange-200 rounded-full select-none dark:bg-orange-900/60"
|
||||
>
|
||||
|
||||
@@ -210,6 +210,26 @@ function extractFpsMetrics(result: string): { avg: number | null; p1: number | n
|
||||
return { avg, p1 }
|
||||
}
|
||||
|
||||
// 备注单元格组件
|
||||
function NoteCell({ note, onEdit }: { note: string; onEdit: () => void }) {
|
||||
return (
|
||||
<div className="flex items-center min-w-0 gap-1">
|
||||
<span className="flex-1 min-w-0 truncate">
|
||||
{note || "无备注"}
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={onEdit}
|
||||
className="h-5 min-w-5 shrink-0"
|
||||
>
|
||||
<Edit size={12} />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function FpsTest() {
|
||||
const steam = useSteamStore()
|
||||
const tool = useToolStore()
|
||||
@@ -291,9 +311,7 @@ export function FpsTest() {
|
||||
tool.state.videoSetting.defaultresheight || ""
|
||||
)
|
||||
}
|
||||
if (fpsTest.state.config.isFullscreen === true && tool.state.videoSetting.fullscreen !== "1") {
|
||||
fpsTest.setIsFullscreen(tool.state.videoSetting.fullscreen === "1")
|
||||
}
|
||||
// 全屏/窗口化设置不再同步游戏设置,只控制启动项参数
|
||||
}
|
||||
}, [tool.state.videoSetting, fpsTest])
|
||||
|
||||
@@ -360,14 +378,38 @@ export function FpsTest() {
|
||||
|
||||
try {
|
||||
// 获取 console.log 路径
|
||||
const consoleLogPath = await invoke<string>("get_console_log_path", {
|
||||
csPath: steam.state.cs2Dir,
|
||||
})
|
||||
let consoleLogPath: string
|
||||
try {
|
||||
consoleLogPath = await invoke<string>("get_console_log_path", {
|
||||
csPath: steam.state.cs2Dir,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("获取控制台日志路径失败:", error)
|
||||
if (!silent) {
|
||||
addToast({
|
||||
title: "获取控制台日志路径失败",
|
||||
color: "warning"
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 读取 VProf 报告
|
||||
const report = await invoke<string>("read_vprof_report", {
|
||||
consoleLogPath: consoleLogPath,
|
||||
})
|
||||
let report: string
|
||||
try {
|
||||
report = await invoke<string>("read_vprof_report", {
|
||||
consoleLogPath: consoleLogPath,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("读取性能报告失败:", error)
|
||||
if (!silent) {
|
||||
addToast({
|
||||
title: "读取性能报告失败",
|
||||
color: "warning"
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if (report && report.trim().length > 0) {
|
||||
const parsed = parseVProfReport(report)
|
||||
@@ -427,7 +469,7 @@ export function FpsTest() {
|
||||
// 添加带批量标识和分辨率信息的备注
|
||||
let batchNote = ""
|
||||
if (currentResolution) {
|
||||
batchNote = `[分辨率:${currentResolution.label}]`
|
||||
batchNote = `[${currentResolution.label}]`
|
||||
}
|
||||
if (testNote) {
|
||||
batchNote = batchNote ? `${testNote} ${batchNote}` : testNote
|
||||
@@ -472,7 +514,7 @@ export function FpsTest() {
|
||||
// 单次测试,添加分辨率信息到备注
|
||||
let singleNote = testNote
|
||||
if (currentResolution) {
|
||||
const resolutionNote = `[分辨率:${currentResolution.label}]`
|
||||
const resolutionNote = `[${currentResolution.label}]`
|
||||
singleNote = singleNote ? `${testNote} ${resolutionNote}` : resolutionNote
|
||||
}
|
||||
|
||||
@@ -636,7 +678,21 @@ export function FpsTest() {
|
||||
isFirstTest: boolean = false,
|
||||
resolution?: { width: string; height: string; label: string }
|
||||
): Promise<boolean> => {
|
||||
// 验证路径是否存在且有效
|
||||
if (!steam.state.steamDir || !steam.state.cs2Dir) {
|
||||
addToast({
|
||||
title: "Steam 或 CS2 路径未设置,请先配置路径",
|
||||
color: "warning"
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证 Steam 路径是否有效
|
||||
if (!steam.state.steamDirValid) {
|
||||
addToast({
|
||||
title: "Steam 路径无效,请检查路径设置",
|
||||
color: "warning"
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -684,14 +740,14 @@ export function FpsTest() {
|
||||
label: `${resolutionWidth}x${resolutionHeight}`,
|
||||
}
|
||||
|
||||
// 只有在启用分辨率和全屏设置时才添加相关参数
|
||||
if (isResolutionEnabled) {
|
||||
// 添加分辨率设置(如果启用分辨率功能或分辨率组)
|
||||
if (isResolutionEnabled || isResolutionGroupEnabled) {
|
||||
// 添加分辨率设置(如果有设置)
|
||||
if (currentResolution.width && currentResolution.height) {
|
||||
baseLaunchOption += ` -w ${currentResolution.width} -h ${currentResolution.height}`
|
||||
}
|
||||
|
||||
// 添加全屏/窗口化设置
|
||||
// 添加全屏/窗口化设置(独立控制,不依赖游戏设置)
|
||||
if (isFullscreen) {
|
||||
baseLaunchOption += ` -fullscreen`
|
||||
} else {
|
||||
@@ -705,11 +761,20 @@ export function FpsTest() {
|
||||
: baseLaunchOption
|
||||
|
||||
// 启动游戏(强制使用worldwide国际服)
|
||||
await invoke("launch_game", {
|
||||
steamPath: `${steam.state.steamDir}\\steam.exe`,
|
||||
launchOption: launchOption,
|
||||
server: "worldwide",
|
||||
})
|
||||
try {
|
||||
await invoke("launch_game", {
|
||||
steamPath: `${steam.state.steamDir}\\steam.exe`,
|
||||
launchOption: launchOption,
|
||||
server: "worldwide",
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("启动游戏失败:", error)
|
||||
addToast({
|
||||
title: `启动游戏失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
color: "danger"
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// 视频设置将在测试结束后读取(在readResult中)
|
||||
// 保存当前测试的分辨率信息
|
||||
@@ -859,7 +924,7 @@ export function FpsTest() {
|
||||
const second = String(now.getSeconds()).padStart(2, "0")
|
||||
const testTime = `${month}/${day} ${hour}:${minute}:${second}`
|
||||
|
||||
let averageNote = `[分辨率:${currentResolution.label}]`
|
||||
let averageNote = `[${currentResolution.label}]`
|
||||
if (testNote) {
|
||||
averageNote = `${testNote} ${averageNote}`
|
||||
}
|
||||
@@ -1209,121 +1274,102 @@ export function FpsTest() {
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
{showResultsTable ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="max-h-[500px] overflow-auto">
|
||||
<Table
|
||||
aria-label="测试结果表格"
|
||||
selectionMode="none"
|
||||
removeWrapper
|
||||
classNames={{
|
||||
base: "min-h-[222px]",
|
||||
th: "px-2 py-1.5 text-xs",
|
||||
td: "px-2 py-1.5 text-xs",
|
||||
}}
|
||||
>
|
||||
<TableHeader>
|
||||
<TableColumn>测试时间</TableColumn>
|
||||
<TableColumn>测试地图</TableColumn>
|
||||
<TableColumn>平均帧</TableColumn>
|
||||
<TableColumn>P1低帧</TableColumn>
|
||||
<TableColumn>CPU</TableColumn>
|
||||
<TableColumn>系统版本</TableColumn>
|
||||
<TableColumn>GPU</TableColumn>
|
||||
<TableColumn>内存</TableColumn>
|
||||
<TableColumn>视频设置</TableColumn>
|
||||
<TableColumn>备注</TableColumn>
|
||||
<TableColumn width={100}>操作</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody emptyContent="暂无测试记录">
|
||||
{fpsTest.state.results.map((result) => (
|
||||
<TableRow key={result.id}>
|
||||
<TableCell className="text-xs">{result.testTime}</TableCell>
|
||||
<TableCell className="text-xs">{result.mapLabel}</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{result.avg !== null ? `${result.avg.toFixed(1)}` : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{result.p1 !== null ? `${result.p1.toFixed(1)}` : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className="text-xs max-w-[160px]"
|
||||
title={result.hardwareInfo?.cpu || undefined}
|
||||
>
|
||||
<div className="truncate whitespace-nowrap">
|
||||
{result.hardwareInfo?.cpu || "N/A"}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className="text-xs max-w-[160px]"
|
||||
title={result.hardwareInfo?.os || undefined}
|
||||
>
|
||||
<div className="truncate whitespace-nowrap">
|
||||
{result.hardwareInfo?.os || "N/A"}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className="text-xs max-w-[180px]"
|
||||
title={result.hardwareInfo?.gpu || undefined}
|
||||
>
|
||||
<div className="truncate whitespace-nowrap">
|
||||
{result.hardwareInfo?.gpu || "N/A"}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{result.hardwareInfo?.memory ? `${result.hardwareInfo.memory}GB` : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className="text-xs"
|
||||
title={formatVideoSettingSummary(result.videoSetting)}
|
||||
>
|
||||
<Tooltip content={formatVideoSettingSummary(result.videoSetting)}>
|
||||
<span className="cursor-help">
|
||||
{result.videoSetting
|
||||
? `${result.videoSetting.defaultres}x${result.videoSetting.defaultresheight}`
|
||||
: "N/A"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs max-w-[150px]">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="truncate" title={result.note || "无备注"}>
|
||||
{result.note || "无备注"}
|
||||
</span>
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={() => handleEditNote(result.id, result.note || "")}
|
||||
className="h-5 min-w-5 shrink-0"
|
||||
>
|
||||
<Edit size={12} />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={() => {
|
||||
fpsTest.removeResult(result.id)
|
||||
addToast({
|
||||
title: "已删除测试记录",
|
||||
variant: "flat",
|
||||
})
|
||||
}}
|
||||
className="h-6 min-w-6"
|
||||
>
|
||||
<Delete size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="relative flex flex-col gap-2">
|
||||
<Table
|
||||
aria-label="测试结果表格"
|
||||
selectionMode="none"
|
||||
classNames={{
|
||||
wrapper: "overflow-auto",
|
||||
base: "min-h-[222px]",
|
||||
table: "min-w-full",
|
||||
th: "px-3 py-2 text-xs font-semibold whitespace-nowrap",
|
||||
td: "px-3 py-2 text-xs",
|
||||
}}
|
||||
>
|
||||
<TableHeader>
|
||||
<TableColumn minWidth={140}>测试时间</TableColumn>
|
||||
<TableColumn width={80}>测试地图</TableColumn>
|
||||
<TableColumn width={80}>平均帧</TableColumn>
|
||||
<TableColumn width={80}>P1低帧</TableColumn>
|
||||
<TableColumn width={150}>CPU</TableColumn>
|
||||
<TableColumn minWidth={80}>系统版本</TableColumn>
|
||||
<TableColumn minWidth={100}>GPU</TableColumn>
|
||||
<TableColumn width={80}>内存</TableColumn>
|
||||
<TableColumn width={120}>视频设置</TableColumn>
|
||||
<TableColumn minWidth={100}>备注</TableColumn>
|
||||
<TableColumn width={80} align="center">操作</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody emptyContent="暂无测试记录">
|
||||
{fpsTest.state.results.map((result) => (
|
||||
<TableRow key={result.id}>
|
||||
<TableCell className="text-xs whitespace-nowrap">{result.testTime}</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
<div className="truncate">
|
||||
{result.mapLabel}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{result.avg !== null ? `${result.avg.toFixed(1)}` : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
{result.p1 !== null ? `${result.p1.toFixed(1)}` : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs max-w-[150px]">
|
||||
<div className="truncate">
|
||||
{result.hardwareInfo?.cpu || "N/A"}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
<div className="truncate">
|
||||
{result.hardwareInfo?.os || "N/A"}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
<div className="truncate">
|
||||
{result.hardwareInfo?.gpu || "N/A"}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs whitespace-nowrap">
|
||||
{result.hardwareInfo?.memory ? `${result.hardwareInfo.memory}GB` : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
<Tooltip content={formatVideoSettingSummary(result.videoSetting)}>
|
||||
<span className="truncate cursor-help">
|
||||
{result.videoSetting
|
||||
? `${result.videoSetting.defaultres}x${result.videoSetting.defaultresheight}`
|
||||
: "N/A"}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs">
|
||||
<NoteCell
|
||||
note={result.note || ""}
|
||||
onEdit={() => handleEditNote(result.id, result.note || "")}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center justify-center">
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={() => {
|
||||
fpsTest.removeResult(result.id)
|
||||
addToast({
|
||||
title: "已删除测试记录",
|
||||
variant: "flat",
|
||||
})
|
||||
}}
|
||||
className="h-6 min-w-6"
|
||||
>
|
||||
<Delete size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -1554,7 +1600,7 @@ export function FpsTest() {
|
||||
variant={isFullscreen ? "solid" : "flat"}
|
||||
color={isFullscreen ? "primary" : "default"}
|
||||
onPress={() => fpsTest.setIsFullscreen(!isFullscreen)}
|
||||
isDisabled={!isResolutionEnabled || isMonitoring}
|
||||
isDisabled={isMonitoring || (!isResolutionEnabled && !isResolutionGroupEnabled)}
|
||||
className="font-medium"
|
||||
>
|
||||
{isFullscreen ? "全屏" : "窗口化"}
|
||||
|
||||
@@ -331,11 +331,15 @@ const VideoSetting = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (steam.state.steamDirValid && steam.currentUser()) {
|
||||
// 安全地获取视频配置(内部已有错误处理)
|
||||
void tool.getVideoConfig(steam.state.steamDir, steam.currentUser()?.steam_id32 || 0)
|
||||
// 启动文件监听
|
||||
// 启动文件监听,添加错误处理
|
||||
void invoke("start_watch_cs2_video", {
|
||||
steamDir: steam.state.steamDir,
|
||||
steamId32: steam.currentUser()?.steam_id32 || 0,
|
||||
}).catch((error) => {
|
||||
console.error("启动视频配置文件监听失败:", error)
|
||||
// 不显示错误提示,避免干扰用户
|
||||
})
|
||||
}
|
||||
// 清理函数:停止后端监听
|
||||
|
||||
@@ -74,11 +74,20 @@ const setCs2DirChecking = (checking: boolean) => {
|
||||
|
||||
const checkSteamDirValid = async () => {
|
||||
setSteamDirChecking(true)
|
||||
const pathExist = await invoke<boolean>("check_path", { path: steamStore.state.steamDir })
|
||||
setSteamDirValid(pathExist)
|
||||
setTimeout(() => {
|
||||
setSteamDirChecking(false)
|
||||
}, 500)
|
||||
try {
|
||||
// 使用专门的 Steam 路径验证,检查 steam.exe 或 config 目录
|
||||
const isValid = await invoke<boolean>("check_steam_dir_valid", {
|
||||
steamDir: steamStore.state.steamDir
|
||||
})
|
||||
setSteamDirValid(isValid)
|
||||
} catch (error) {
|
||||
console.error("验证 Steam 路径时出错:", error)
|
||||
setSteamDirValid(false)
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setSteamDirChecking(false)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
const checkCs2DirValid = async () => {
|
||||
@@ -95,8 +104,21 @@ const currentUser = () => {
|
||||
}
|
||||
|
||||
const getUsers = async () => {
|
||||
const users = await invoke<SteamUser[]>("get_steam_users", { steamDir: steamStore.state.steamDir })
|
||||
setUsers(users)
|
||||
// 只有在路径有效时才尝试获取用户
|
||||
if (!steamStore.state.steamDirValid || !steamStore.state.steamDir) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const users = await invoke<SteamUser[]>("get_steam_users", {
|
||||
steamDir: steamStore.state.steamDir
|
||||
})
|
||||
setUsers(users)
|
||||
} catch (error) {
|
||||
console.error("获取 Steam 用户列表失败:", error)
|
||||
// 如果获取失败,清空用户列表,避免显示错误数据
|
||||
setUsers([])
|
||||
}
|
||||
}
|
||||
|
||||
const selectUser = (index: number) => {
|
||||
|
||||
@@ -262,9 +262,15 @@ const setVideoSetting = (setting: VideoSetting) => {
|
||||
}
|
||||
|
||||
const getVideoConfig = async (steam_dir: string, steam_id32: number) => {
|
||||
const video = await invoke<VideoSetting>("get_cs2_video_config", { steamDir: steam_dir, steamId32: steam_id32 })
|
||||
// console.log(video)
|
||||
setVideoSetting(video)
|
||||
try {
|
||||
const video = await invoke<VideoSetting>("get_cs2_video_config", { steamDir: steam_dir, steamId32: steam_id32 })
|
||||
// console.log(video)
|
||||
setVideoSetting(video)
|
||||
} catch (error) {
|
||||
console.error("读取视频配置失败:", error)
|
||||
// 如果文件不存在或读取失败,使用默认配置或保持当前配置
|
||||
// 不抛出错误,避免影响其他功能
|
||||
}
|
||||
}
|
||||
|
||||
const setVideoConfig = async (steam_dir: string, steam_id32: number, video_config: VideoSetting) => {
|
||||
|
||||
Reference in New Issue
Block a user