better fps testing ui + more info + comment + users minor update
This commit is contained in:
@@ -45,6 +45,15 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
|
||||
addToast({ title: `电源计划已切换 → ${PowerPlans[current].title}` })
|
||||
})
|
||||
|
||||
void listen<number>("tray://set_launch_index", async (event) => {
|
||||
const index = event.payload
|
||||
if (typeof index === "number" && index >= 0 && index < toolStore.state.launchOptions.length) {
|
||||
tool.setLaunchIndex(index)
|
||||
const optionName = toolStore.state.launchOptions[index].name || `启动项 ${index + 1}`
|
||||
addToast({ title: `启动项已切换 → ${optionName}` })
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
// 检测steam路径和游戏路径是否有效
|
||||
|
||||
@@ -30,7 +30,7 @@ export function AuthButton() {
|
||||
isIconOnly
|
||||
variant="light"
|
||||
size="sm"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95 cursor-pointer"
|
||||
>
|
||||
<Spinner size="sm" />
|
||||
</Button>
|
||||
@@ -46,13 +46,13 @@ export function AuthButton() {
|
||||
isIconOnly
|
||||
variant="light"
|
||||
size="sm"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95 cursor-pointer [&>*]:cursor-pointer"
|
||||
>
|
||||
<Avatar
|
||||
src={state.user.user_metadata?.avatar_url}
|
||||
name={state.user.email || state.user.id}
|
||||
size="sm"
|
||||
className="w-6 h-6"
|
||||
className="w-6 h-6 cursor-pointer"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
@@ -115,9 +115,9 @@ export function AuthButton() {
|
||||
isIconOnly
|
||||
variant="light"
|
||||
size="sm"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95 cursor-pointer [&>*]:cursor-pointer"
|
||||
>
|
||||
<User size={16} />
|
||||
<User size={16} className="cursor-pointer" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu aria-label="登录菜单">
|
||||
|
||||
@@ -1,13 +1,36 @@
|
||||
"use client"
|
||||
import { useSteamStore } from "@/store/steam"
|
||||
import { useToolStore } from "@/store/tool"
|
||||
import { useFpsTestStore } from "@/store/fpsTest"
|
||||
import { useFpsTestStore } from "@/store/fps_test"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card"
|
||||
import { addToast, Button, Chip, Spinner, Switch, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from "@heroui/react"
|
||||
import {
|
||||
addToast,
|
||||
Button,
|
||||
Chip,
|
||||
Spinner,
|
||||
Table,
|
||||
TableHeader,
|
||||
TableColumn,
|
||||
TableBody,
|
||||
TableRow,
|
||||
TableCell,
|
||||
Tabs,
|
||||
Tab,
|
||||
Tooltip,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Textarea,
|
||||
useDisclosure,
|
||||
} from "@heroui/react"
|
||||
import { useState, useEffect, useRef, useCallback } from "react"
|
||||
import { TestTube, Power, List, Delete } from "@icon-park/react"
|
||||
import { TestTube, Power, List, Delete, Play, Edit, Check, Close, Square } from "@icon-park/react"
|
||||
import { allSysInfo, type AllSystemInfo } from "tauri-plugin-system-info-api"
|
||||
import { ToolButton } from "../window/ToolButton"
|
||||
|
||||
const BENCHMARK_MAPS = [
|
||||
{
|
||||
@@ -24,6 +47,8 @@ const BENCHMARK_MAPS = [
|
||||
},
|
||||
]
|
||||
|
||||
const TEST_TIMEOUT = 200000 // 200秒超时时间(毫秒)
|
||||
|
||||
// 解析性能报告,提取时间戳和性能数据
|
||||
function parseVProfReport(rawReport: string): { timestamp: string; data: string } | null {
|
||||
if (!rawReport) return null
|
||||
@@ -156,18 +181,47 @@ export function FpsTest() {
|
||||
const steam = useSteamStore()
|
||||
const tool = useToolStore()
|
||||
const fpsTest = useFpsTestStore()
|
||||
const [testing, setTesting] = useState(false)
|
||||
const [testResult, setTestResult] = useState<string | null>(null)
|
||||
const [testTimestamp, setTestTimestamp] = useState<string | null>(null)
|
||||
const [selectedMap, setSelectedMap] = useState<string | null>(null)
|
||||
const [selectedMapLabel, setSelectedMapLabel] = useState<string | null>(null)
|
||||
const [autoCloseGame, setAutoCloseGame] = useState(false)
|
||||
const [selectedMapIndex, setSelectedMapIndex] = useState(0)
|
||||
const [isMonitoring, setIsMonitoring] = useState(false)
|
||||
const [showResultsTable, setShowResultsTable] = useState(false)
|
||||
const [hardwareInfo, setHardwareInfo] = useState<AllSystemInfo | null>(null)
|
||||
const [isGameRunning, setIsGameRunning] = useState(false)
|
||||
const [testNote, setTestNote] = useState<string>("") // 测试备注
|
||||
const [editingNoteId, setEditingNoteId] = useState<string | null>(null) // 正在编辑的备注ID
|
||||
const [editingNoteValue, setEditingNoteValue] = useState<string>("") // 正在编辑的备注内容
|
||||
const { isOpen: isNoteModalOpen, onOpen: onNoteModalOpen, onClose: onNoteModalClose } = useDisclosure()
|
||||
const monitoringIntervalRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
// 记录测试开始的时间戳(用于过滤旧数据)
|
||||
const testStartTimestampRef = useRef<string | null>(null)
|
||||
const testStartTimeRef = useRef<number | null>(null)
|
||||
// 记录测试开始时的视频设置
|
||||
const testStartVideoSettingRef = useRef<typeof tool.state.videoSetting | null>(null)
|
||||
|
||||
// 检测游戏是否运行
|
||||
const checkGameRunning = useCallback(async () => {
|
||||
try {
|
||||
const result = await invoke<boolean>("check_process_running", {
|
||||
processName: "cs2.exe",
|
||||
}).catch(() => false)
|
||||
setIsGameRunning(result)
|
||||
return result
|
||||
} catch {
|
||||
setIsGameRunning(false)
|
||||
return false
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 定期检测游戏运行状态
|
||||
useEffect(() => {
|
||||
void checkGameRunning()
|
||||
const interval = setInterval(() => {
|
||||
void checkGameRunning()
|
||||
}, 3000)
|
||||
return () => clearInterval(interval)
|
||||
}, [checkGameRunning])
|
||||
|
||||
// 获取硬件信息
|
||||
useEffect(() => {
|
||||
@@ -182,6 +236,34 @@ export function FpsTest() {
|
||||
void fetchHardwareInfo()
|
||||
}, [])
|
||||
|
||||
// 停止测试
|
||||
const stopTest = useCallback(async () => {
|
||||
setIsMonitoring(false)
|
||||
if (monitoringIntervalRef.current) {
|
||||
clearInterval(monitoringIntervalRef.current)
|
||||
monitoringIntervalRef.current = null
|
||||
}
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
}
|
||||
testStartTimestampRef.current = null
|
||||
testStartTimeRef.current = null
|
||||
testStartVideoSettingRef.current = null
|
||||
|
||||
// 如果启用了自动关闭游戏,则关闭游戏
|
||||
if (tool.state.autoCloseGame) {
|
||||
try {
|
||||
await invoke("kill_game")
|
||||
addToast({ title: "已停止测试并关闭游戏" })
|
||||
} catch (error) {
|
||||
console.error("关闭游戏失败:", error)
|
||||
}
|
||||
} else {
|
||||
addToast({ title: "已停止测试" })
|
||||
}
|
||||
}, [tool.state.autoCloseGame])
|
||||
|
||||
// 读取结果函数
|
||||
const readResult = useCallback(
|
||||
async (silent = false): Promise<boolean> => {
|
||||
@@ -220,49 +302,80 @@ export function FpsTest() {
|
||||
setTestTimestamp(parsed.timestamp)
|
||||
// 成功读取后,清除测试开始时间戳(测试已完成)
|
||||
testStartTimestampRef.current = null
|
||||
|
||||
testStartTimeRef.current = null
|
||||
|
||||
// 停止监控和超时
|
||||
setIsMonitoring(false)
|
||||
if (monitoringIntervalRef.current) {
|
||||
clearInterval(monitoringIntervalRef.current)
|
||||
monitoringIntervalRef.current = null
|
||||
}
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
}
|
||||
|
||||
// 提取 avg 和 p1 值
|
||||
const { avg, p1 } = extractFpsMetrics(parsed.data)
|
||||
|
||||
// 保存测试结果(即使没有 selectedMap 也保存,使用 "未知地图" 作为默认值)
|
||||
|
||||
// 保存测试结果
|
||||
const now = new Date()
|
||||
const testDate = now.toISOString()
|
||||
const mapConfig = selectedMap ? BENCHMARK_MAPS.find(m => m.name === selectedMap) : null
|
||||
|
||||
// 从 store 直接获取最新的 videoSetting,避免依赖项问题
|
||||
const currentVideoSetting = tool.store.state.videoSetting
|
||||
|
||||
const mapConfig = BENCHMARK_MAPS[selectedMapIndex]
|
||||
|
||||
// 使用测试开始时的视频设置(如果有的话),否则使用当前的
|
||||
const currentVideoSetting = testStartVideoSettingRef.current || tool.store.state.videoSetting
|
||||
|
||||
fpsTest.addResult({
|
||||
id: `${now.getTime()}-${Math.random().toString(36).slice(2, 11)}`,
|
||||
testTime: parsed.timestamp,
|
||||
testDate,
|
||||
mapName: selectedMap || "unknown",
|
||||
mapLabel: mapConfig?.label || selectedMapLabel || "未知地图",
|
||||
mapName: mapConfig?.name || "unknown",
|
||||
mapLabel: mapConfig?.label || "未知地图",
|
||||
avg,
|
||||
p1,
|
||||
rawResult: parsed.data,
|
||||
videoSetting: currentVideoSetting,
|
||||
hardwareInfo: hardwareInfo ? {
|
||||
cpu: hardwareInfo.cpus[0]?.brand || null,
|
||||
cpuCount: hardwareInfo.cpu_count || null,
|
||||
os: hardwareInfo.name && hardwareInfo.os_version
|
||||
? `${hardwareInfo.name} ${hardwareInfo.os_version}`
|
||||
: null,
|
||||
memory: hardwareInfo.total_memory
|
||||
? Math.round(hardwareInfo.total_memory / 1024 / 1024 / 1024)
|
||||
: null,
|
||||
} : null,
|
||||
hardwareInfo: hardwareInfo
|
||||
? {
|
||||
cpu: hardwareInfo.cpus[0]?.brand || null,
|
||||
cpuCount: hardwareInfo.cpu_count || null,
|
||||
os:
|
||||
hardwareInfo.name && hardwareInfo.os_version
|
||||
? `${hardwareInfo.name} ${hardwareInfo.os_version}`
|
||||
: null,
|
||||
memory: hardwareInfo.total_memory
|
||||
? Math.round(hardwareInfo.total_memory / 1024 / 1024 / 1024)
|
||||
: null,
|
||||
gpu: null,
|
||||
monitor: null,
|
||||
}
|
||||
: null,
|
||||
note: testNote, // 保存备注
|
||||
})
|
||||
|
||||
|
||||
// 清除保存的启动时视频设置
|
||||
testStartVideoSettingRef.current = null
|
||||
|
||||
if (!silent) {
|
||||
if (avg !== null || p1 !== null) {
|
||||
addToast({
|
||||
title: `已读取并保存测试结果${avg !== null ? ` (avg: ${avg.toFixed(1)} FPS)` : ""}${p1 !== null ? ` (p1: ${p1.toFixed(1)} FPS)` : ""}`
|
||||
addToast({
|
||||
title: `已读取并保存测试结果${
|
||||
avg !== null ? ` (avg: ${avg.toFixed(1)} FPS)` : ""
|
||||
}${p1 !== null ? ` (p1: ${p1.toFixed(1)} FPS)` : ""}`,
|
||||
})
|
||||
} else {
|
||||
addToast({ title: "已读取并保存测试结果(未能提取帧数数据)" })
|
||||
}
|
||||
}
|
||||
|
||||
// 如果启用了自动关闭游戏,则关闭游戏
|
||||
if (tool.state.autoCloseGame) {
|
||||
setTimeout(() => {
|
||||
void invoke("kill_game").catch(() => {})
|
||||
}, 2000) // 延迟2秒关闭,让用户看到结果
|
||||
}
|
||||
|
||||
return true
|
||||
} else if (!silent) {
|
||||
addToast({
|
||||
@@ -288,28 +401,9 @@ export function FpsTest() {
|
||||
return false
|
||||
}
|
||||
},
|
||||
[steam.state.cs2Dir, selectedMap, selectedMapLabel, fpsTest, tool.store, hardwareInfo]
|
||||
[steam.state.cs2Dir, selectedMapIndex, fpsTest, tool.store, hardwareInfo, tool.state.autoCloseGame]
|
||||
)
|
||||
|
||||
// 关闭游戏
|
||||
const closeGame = useCallback(async () => {
|
||||
try {
|
||||
await invoke("kill_game")
|
||||
addToast({ title: "已关闭CS2" })
|
||||
setIsMonitoring(false)
|
||||
if (monitoringIntervalRef.current) {
|
||||
clearInterval(monitoringIntervalRef.current)
|
||||
monitoringIntervalRef.current = null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("关闭游戏失败:", error)
|
||||
addToast({
|
||||
title: `关闭游戏失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
variant: "flat",
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 开始监控文件更新
|
||||
useEffect(() => {
|
||||
if (isMonitoring && steam.state.cs2Dir) {
|
||||
@@ -317,27 +411,46 @@ export function FpsTest() {
|
||||
monitoringIntervalRef.current = setInterval(async () => {
|
||||
const success = await readResult(true) // 静默读取
|
||||
if (success) {
|
||||
// 读取成功,停止监控
|
||||
// 读取成功,监控会在readResult中停止
|
||||
return
|
||||
}
|
||||
}, 2000) // 每2秒检查一次
|
||||
|
||||
// 设置超时
|
||||
if (testStartTimeRef.current) {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
// 超时,认为测试失败
|
||||
setIsMonitoring(false)
|
||||
if (monitoringIntervalRef.current) {
|
||||
clearInterval(monitoringIntervalRef.current)
|
||||
monitoringIntervalRef.current = null
|
||||
}
|
||||
|
||||
testStartTimestampRef.current = null
|
||||
testStartTimeRef.current = null
|
||||
testStartVideoSettingRef.current = null
|
||||
addToast({
|
||||
title: "测试超时(200秒),测试失败",
|
||||
variant: "flat",
|
||||
color: "warning",
|
||||
})
|
||||
// 如果启用了自动关闭游戏,则关闭游戏
|
||||
if (autoCloseGame) {
|
||||
if (tool.state.autoCloseGame) {
|
||||
setTimeout(() => {
|
||||
void closeGame()
|
||||
}, 2000) // 延迟2秒关闭,让用户看到结果
|
||||
void invoke("kill_game").catch(() => {})
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}, 2000) // 每2秒检查一次
|
||||
}, TEST_TIMEOUT)
|
||||
}
|
||||
} else {
|
||||
// 停止监控
|
||||
if (monitoringIntervalRef.current) {
|
||||
clearInterval(monitoringIntervalRef.current)
|
||||
monitoringIntervalRef.current = null
|
||||
}
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
}
|
||||
}
|
||||
|
||||
// 清理函数
|
||||
@@ -346,20 +459,48 @@ export function FpsTest() {
|
||||
clearInterval(monitoringIntervalRef.current)
|
||||
monitoringIntervalRef.current = null
|
||||
}
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
}
|
||||
}
|
||||
}, [isMonitoring, steam.state.cs2Dir, autoCloseGame, readResult, closeGame])
|
||||
}, [isMonitoring, steam.state.cs2Dir, readResult, tool.state.autoCloseGame])
|
||||
|
||||
const startTest = async (mapConfig: (typeof BENCHMARK_MAPS)[0]) => {
|
||||
const startTest = async () => {
|
||||
if (!steam.state.steamDir || !steam.state.cs2Dir) {
|
||||
addToast({ title: "请先配置 Steam 和 CS2 路径", variant: "flat", color: "warning" })
|
||||
return
|
||||
}
|
||||
|
||||
setTesting(true)
|
||||
setSelectedMap(mapConfig.name)
|
||||
setSelectedMapLabel(mapConfig.label)
|
||||
const mapConfig = BENCHMARK_MAPS[selectedMapIndex]
|
||||
if (!mapConfig) {
|
||||
addToast({ title: "请选择测试地图", variant: "flat", color: "warning" })
|
||||
return
|
||||
}
|
||||
|
||||
// 如果启用了自动关闭游戏,检测并关闭正在运行的游戏
|
||||
if (tool.state.autoCloseGame) {
|
||||
const gameRunning = await checkGameRunning()
|
||||
if (gameRunning) {
|
||||
try {
|
||||
await invoke("kill_game")
|
||||
addToast({ title: "检测到游戏正在运行,已关闭" })
|
||||
// 等待一下确保游戏关闭
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
} catch (error) {
|
||||
console.error("关闭游戏失败:", error)
|
||||
addToast({
|
||||
title: `关闭游戏失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
variant: "flat",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTestResult(null)
|
||||
setTestTimestamp(null)
|
||||
// 注意:不清空备注,让用户可以在测试过程中记住备注内容
|
||||
|
||||
// 记录测试开始时间戳(格式:MM/DD HH:mm:ss)
|
||||
const now = new Date()
|
||||
@@ -369,6 +510,9 @@ export function FpsTest() {
|
||||
const minute = String(now.getMinutes()).padStart(2, "0")
|
||||
const second = String(now.getSeconds()).padStart(2, "0")
|
||||
testStartTimestampRef.current = `${month}/${day} ${hour}:${minute}:${second}`
|
||||
testStartTimeRef.current = now.getTime()
|
||||
// 保存启动时的视频设置
|
||||
testStartVideoSettingRef.current = { ...tool.store.state.videoSetting }
|
||||
|
||||
try {
|
||||
const launchOption = `-allow_third_party_software -condebug -conclearlog +map_workshop ${mapConfig.workshopId} ${mapConfig.map}`
|
||||
@@ -381,7 +525,6 @@ export function FpsTest() {
|
||||
})
|
||||
|
||||
addToast({ title: `已启动 ${mapConfig.label} 测试,正在自动监听结果...` })
|
||||
setTesting(false)
|
||||
|
||||
// 开始自动监听文件更新
|
||||
setIsMonitoring(true)
|
||||
@@ -391,10 +534,43 @@ export function FpsTest() {
|
||||
title: `启动测试失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||
variant: "flat",
|
||||
})
|
||||
setTesting(false)
|
||||
setIsMonitoring(false)
|
||||
// 启动失败,清除测试开始时间戳
|
||||
// 启动失败,清除测试开始时间戳和视频设置
|
||||
testStartTimestampRef.current = null
|
||||
testStartTimeRef.current = null
|
||||
testStartVideoSettingRef.current = null
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否可以开始测试
|
||||
const canStartTest = !tool.state.autoCloseGame ? !isGameRunning : true
|
||||
|
||||
// 格式化视频设置摘要
|
||||
const formatVideoSettingSummary = (videoSetting: typeof tool.state.videoSetting | null): string => {
|
||||
if (!videoSetting) return "N/A"
|
||||
const resolution = `${videoSetting.defaultres}x${videoSetting.defaultresheight}`
|
||||
const refreshRate = videoSetting.refreshrate_denominator === "1"
|
||||
? videoSetting.refreshrate_numerator
|
||||
: `${videoSetting.refreshrate_numerator}/${videoSetting.refreshrate_denominator}`
|
||||
const msaa = videoSetting.msaa_samples === "0" ? "无" : `${videoSetting.msaa_samples}x`
|
||||
return `${resolution}@${refreshRate}Hz, MSAA:${msaa}`
|
||||
}
|
||||
|
||||
// 打开备注编辑对话框
|
||||
const handleEditNote = (resultId: string, currentNote: string) => {
|
||||
setEditingNoteId(resultId)
|
||||
setEditingNoteValue(currentNote)
|
||||
onNoteModalOpen()
|
||||
}
|
||||
|
||||
// 保存备注
|
||||
const handleSaveNote = () => {
|
||||
if (editingNoteId) {
|
||||
fpsTest.updateNote(editingNoteId, editingNoteValue)
|
||||
addToast({ title: "备注已更新" })
|
||||
onNoteModalClose()
|
||||
setEditingNoteId(null)
|
||||
setEditingNoteValue("")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,62 +579,145 @@ export function FpsTest() {
|
||||
<CardHeader>
|
||||
<CardIcon>
|
||||
<TestTube size={16} /> 帧数测试
|
||||
{isGameRunning && !tool.state.autoCloseGame && (
|
||||
<Chip size="sm" color="warning" variant="flat" className="ml-2 cursor-help">
|
||||
游戏运行中 关闭游戏后从这里启动
|
||||
</Chip>
|
||||
)}
|
||||
</CardIcon>
|
||||
<CardTool>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={showResultsTable ? "solid" : "flat"}
|
||||
onPress={() => setShowResultsTable(!showResultsTable)}
|
||||
className="px-3"
|
||||
>
|
||||
<List size={14} className="mr-1" />
|
||||
测试结果
|
||||
</Button>
|
||||
<CardTool className="justify-end">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={showResultsTable ? "solid" : "flat"}
|
||||
color={showResultsTable ? "secondary" : "default"}
|
||||
onPress={() => setShowResultsTable(!showResultsTable)}
|
||||
className="font-medium"
|
||||
>
|
||||
<List size={14} />
|
||||
测试结果
|
||||
</Button>
|
||||
{isMonitoring ? (
|
||||
<Button
|
||||
size="sm"
|
||||
color="danger"
|
||||
onPress={() => {
|
||||
void stopTest()
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
<Square size={14} />
|
||||
停止测试
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
color="primary"
|
||||
isDisabled={!canStartTest}
|
||||
onPress={() => {
|
||||
void startTest()
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
<Play size={14} />
|
||||
开始测试
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardTool>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
{showResultsTable ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="max-h-[500px] overflow-auto">
|
||||
<Table aria-label="测试结果表格" selectionMode="none">
|
||||
<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 width={80}>操作</TableColumn>
|
||||
<TableColumn>AVG平均帧</TableColumn>
|
||||
<TableColumn>P1低帧</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>{result.testTime}</TableCell>
|
||||
<TableCell>{result.mapLabel}</TableCell>
|
||||
<TableCell>
|
||||
<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>
|
||||
<TableCell className="text-xs">
|
||||
{result.p1 !== null ? `${result.p1.toFixed(1)}` : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{result.hardwareInfo?.cpu || "N/A"}
|
||||
<TableCell className="text-xs" title={result.hardwareInfo?.os || undefined}>
|
||||
{result.hardwareInfo?.os || "N/A"}
|
||||
</TableCell>
|
||||
<TableCell className="text-xs" title={result.hardwareInfo?.gpu || undefined}>
|
||||
{result.hardwareInfo?.gpu || "N/A"}
|
||||
</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>
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={() => {
|
||||
fpsTest.removeResult(result.id)
|
||||
addToast({
|
||||
title: "已删除测试记录",
|
||||
variant: "flat"
|
||||
})
|
||||
}}
|
||||
className="min-w-8"
|
||||
>
|
||||
<Delete size={16} />
|
||||
</Button>
|
||||
<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>
|
||||
))}
|
||||
@@ -467,63 +726,127 @@ export function FpsTest() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{BENCHMARK_MAPS.map((mapConfig) => (
|
||||
<Button
|
||||
key={mapConfig.name}
|
||||
size="sm"
|
||||
isDisabled={testing || isMonitoring}
|
||||
onPress={() => {
|
||||
void startTest(mapConfig)
|
||||
<div className="flex flex-col gap-5">
|
||||
{/* 表单区域:地图和备注同一行 */}
|
||||
<div className="flex items-start gap-4">
|
||||
{/* 测试地图 */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs text-default-500">测试地图</label>
|
||||
<Tabs
|
||||
selectedKey={String(selectedMapIndex)}
|
||||
onSelectionChange={(key) => {
|
||||
if (!isMonitoring) {
|
||||
setSelectedMapIndex(Number(key))
|
||||
}
|
||||
}}
|
||||
className="font-medium transition bg-blue-200 rounded-full select-none dark:bg-blue-900/60"
|
||||
aria-label="测试地图选择"
|
||||
size="md"
|
||||
radius="lg"
|
||||
>
|
||||
{testing && selectedMap === mapConfig.name ? (
|
||||
<Spinner size="sm" className="mr-2" />
|
||||
) : null}
|
||||
{mapConfig.label}
|
||||
</Button>
|
||||
))}
|
||||
{BENCHMARK_MAPS.map((map, index) => (
|
||||
<Tab key={String(index)} title={map.label} />
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{/* 备注 */}
|
||||
<div className="flex flex-col gap-1.5 grow">
|
||||
<label className="text-xs text-default-500">备注</label>
|
||||
<Input
|
||||
size="md"
|
||||
placeholder="输入测试备注"
|
||||
value={testNote}
|
||||
onValueChange={setTestNote}
|
||||
isDisabled={isMonitoring}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 工具栏:按钮靠右对齐 */}
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
isDisabled={isMonitoring}
|
||||
variant={tool.state.autoCloseGame ? "solid" : "flat"}
|
||||
color={tool.state.autoCloseGame ? "primary" : "default"}
|
||||
onPress={() => {
|
||||
void readResult()
|
||||
tool.setAutoCloseGame(!tool.state.autoCloseGame)
|
||||
}}
|
||||
className="font-medium transition bg-green-200 rounded-full select-none dark:bg-green-900/60"
|
||||
className="font-medium"
|
||||
>
|
||||
手动读取结果
|
||||
{tool.state.autoCloseGame ? <Check size={14} /> : <Close size={14} />}
|
||||
自动关闭游戏
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="flat"
|
||||
onPress={() => {
|
||||
void closeGame()
|
||||
void invoke("kill_game")
|
||||
.then(() => {
|
||||
addToast({ title: "已关闭CS2" })
|
||||
void checkGameRunning()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("关闭游戏失败:", error)
|
||||
addToast({
|
||||
title: `关闭游戏失败: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
variant: "flat",
|
||||
})
|
||||
})
|
||||
}}
|
||||
className="font-medium transition bg-orange-200 rounded-full select-none dark:bg-orange-900/60"
|
||||
className="font-medium"
|
||||
>
|
||||
<Power size={14} />
|
||||
关闭游戏
|
||||
</Button>
|
||||
<Switch size="sm" isSelected={autoCloseGame} onValueChange={setAutoCloseGame} className="ml-4">
|
||||
测试完成自动关闭游戏
|
||||
</Switch>
|
||||
|
||||
{isMonitoring && (
|
||||
<Chip size="sm" color="primary" variant="flat">
|
||||
正在监听中...
|
||||
</Chip>
|
||||
)}
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
variant="flat"
|
||||
isDisabled={isMonitoring}
|
||||
onPress={() => {
|
||||
void readResult()
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
手动读取结果
|
||||
</Button>
|
||||
|
||||
{/* 测试结果显示:测试时间、平均帧、P1低帧 */}
|
||||
{testResult && testTimestamp && (() => {
|
||||
const { avg, p1 } = extractFpsMetrics(testResult)
|
||||
return (
|
||||
<>
|
||||
<div className="px-3 py-1.5 h-12 rounded-md bg-default-100 dark:bg-default-50 text-xs flex flex-col justify-center">
|
||||
<div className="text-default-500">测试时间</div>
|
||||
<div className="font-medium">{testTimestamp}</div>
|
||||
</div>
|
||||
<div className="px-3 py-1.5 h-12 rounded-md bg-default-100 dark:bg-default-50 text-xs flex items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<div>
|
||||
<div className="text-default-500">平均帧</div>
|
||||
<div className="font-medium">{avg !== null ? `${avg.toFixed(1)}` : "N/A"}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-default-500">P1低帧</div>
|
||||
<div className="font-medium">{p1 !== null ? `${p1.toFixed(1)}` : "N/A"}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{testResult && (
|
||||
<div className="mt-2">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{testTimestamp && (
|
||||
<Chip size="sm" variant="flat" color="default">
|
||||
测试时间: {testTimestamp}
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
<pre className="p-3 overflow-auto font-mono text-xs rounded-md bg-black/5 dark:bg-white/5 hide-scrollbar">
|
||||
{testResult}
|
||||
</pre>
|
||||
@@ -532,6 +855,34 @@ export function FpsTest() {
|
||||
</div>
|
||||
)}
|
||||
</CardBody>
|
||||
|
||||
{/* 备注编辑对话框 */}
|
||||
<Modal isOpen={isNoteModalOpen} onClose={onNoteModalClose} size="md">
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<ModalHeader className="flex flex-col gap-1">编辑备注</ModalHeader>
|
||||
<ModalBody>
|
||||
<Textarea
|
||||
placeholder="输入备注内容"
|
||||
value={editingNoteValue}
|
||||
onValueChange={setEditingNoteValue}
|
||||
minRows={3}
|
||||
maxRows={5}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="danger" variant="light" onPress={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button color="primary" onPress={handleSaveNote}>
|
||||
保存
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Refresh, User } from "@icon-park/react"
|
||||
import { Refresh, User, FolderFocusOne, Login, Check } from "@icon-park/react"
|
||||
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card"
|
||||
import { addToast, Button, Chip } from "@heroui/react"
|
||||
import { useSteamStore } from "@/store/steam"
|
||||
import { ToolButton } from "../window/ToolButton"
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import path from "path"
|
||||
|
||||
const SteamUsers = ({ className }: { className?: string }) => {
|
||||
const steam = useSteamStore()
|
||||
@@ -68,10 +70,26 @@ const SteamUsers = ({ className }: { className?: string }) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-end gap-2 p-2">
|
||||
<Button size="sm" onPress={() => steam.switchLoginUser(id)}>
|
||||
<Button
|
||||
size="sm"
|
||||
onPress={async () => {
|
||||
if (!steam.state.steamDirValid) {
|
||||
addToast({ title: "Steam路径不可用", color: "warning" })
|
||||
return
|
||||
}
|
||||
await invoke("open_path", {
|
||||
path: path.resolve(steam.state.steamDir, "userdata", user.steam_id32.toString(), "730", "local", "cfg"),
|
||||
})
|
||||
addToast({ title: "个人CFG" })
|
||||
}} className="gap-1"
|
||||
>
|
||||
个人CFG
|
||||
</Button>
|
||||
<Button size="sm" onPress={() => steam.switchLoginUser(id)} className="gap-1">
|
||||
切换登录
|
||||
</Button>
|
||||
<Button size="sm" onPress={() => steam.selectUser(id)}>
|
||||
<Button size="sm" onPress={() => steam.selectUser(id)} className="gap-1">
|
||||
<Check size={14} />
|
||||
选择
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { CloseSmall, Down, Edit, Plus, SettingConfig, Up } from "@icon-park/react"
|
||||
import { useEffect, useState, useCallback } from "react"
|
||||
import { useEffect, useState, useCallback, useRef } from "react"
|
||||
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card"
|
||||
import { ToolButton } from "../window/ToolButton"
|
||||
import { addToast, NumberInput, Tab, Tabs, Tooltip, Chip } from "@heroui/react"
|
||||
import { motion } from "framer-motion"
|
||||
import { useToolStore, VideoSetting as VideoConfig, VideoSettingTemplate } from "@/store/tool"
|
||||
import { useSteamStore } from "@/store/steam"
|
||||
import { useDebounce, useDebounceFn } from "ahooks"
|
||||
import { useDebounce, useDebounceFn, useThrottleFn } from "ahooks"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { listen } from "@tauri-apps/api/event"
|
||||
|
||||
const VideoSetting = () => {
|
||||
const [hide, setHide] = useState(false)
|
||||
@@ -15,6 +16,11 @@ const VideoSetting = () => {
|
||||
const [isGameRunning, setIsGameRunning] = useState(false)
|
||||
const tool = useToolStore()
|
||||
const steam = useSteamStore()
|
||||
// 使用 ref 存储 edit 的最新值,供 throttle 回调使用
|
||||
const editRef = useRef(edit)
|
||||
useEffect(() => {
|
||||
editRef.current = edit
|
||||
}, [edit])
|
||||
|
||||
// 检测游戏是否运行
|
||||
const checkGameRunning = useCallback(async () => {
|
||||
@@ -42,7 +48,7 @@ const VideoSetting = () => {
|
||||
addToast({ title: "请先选择用户", color: "danger" })
|
||||
}
|
||||
},
|
||||
{ wait: 500, leading: true, trailing: false }
|
||||
{ wait: 500, leading: false, trailing: true }
|
||||
)
|
||||
const videoSettings = (video: VideoConfig) => {
|
||||
return [
|
||||
@@ -286,7 +292,7 @@ const VideoSetting = () => {
|
||||
// 定期检测游戏运行状态
|
||||
const interval = setInterval(() => {
|
||||
void checkGameRunning()
|
||||
}, 2000)
|
||||
}, 4000)
|
||||
return () => clearInterval(interval)
|
||||
}, [checkGameRunning])
|
||||
|
||||
@@ -301,10 +307,55 @@ const VideoSetting = () => {
|
||||
trailing: true,
|
||||
maxWait: 2500,
|
||||
})
|
||||
// 节流重新读取配置函数,2秒间隔,trailing模式
|
||||
const { run: throttledRefreshVideoConfig } = useThrottleFn(
|
||||
async () => {
|
||||
if (steam.state.steamDirValid && steam.currentUser()) {
|
||||
await tool.getVideoConfig(
|
||||
steam.state.steamDir,
|
||||
steam.currentUser()?.steam_id32 || 0
|
||||
)
|
||||
// 如果不在编辑状态,更新本地状态(使用 ref 获取最新的 edit 值)
|
||||
if (!editRef.current) {
|
||||
setVconfig(tool.state.videoSetting)
|
||||
}
|
||||
addToast({ title: "检测到视频设置文件变动,已自动刷新", color: "success" })
|
||||
}
|
||||
},
|
||||
{
|
||||
wait: 2000,
|
||||
leading: false,
|
||||
trailing: true,
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (steam.state.steamDirValid && steam.currentUser())
|
||||
if (steam.state.steamDirValid && steam.currentUser()) {
|
||||
void tool.getVideoConfig(steam.state.steamDir, steam.currentUser()?.steam_id32 || 0)
|
||||
}, [debounceCurrentUserId])
|
||||
// 启动文件监听
|
||||
void invoke("start_watch_cs2_video", {
|
||||
steamDir: steam.state.steamDir,
|
||||
steamId32: steam.currentUser()?.steam_id32 || 0,
|
||||
})
|
||||
}
|
||||
// 清理函数:停止后端监听
|
||||
return () => {
|
||||
void invoke("stop_watch_cs2_video").catch(() => {
|
||||
// 忽略错误,可能监听器已经不存在
|
||||
})
|
||||
}
|
||||
}, [debounceCurrentUserId, steam.state.steamDirValid, steam.state.steamDir, tool])
|
||||
|
||||
// 监听 cs2_video.txt 文件变动事件
|
||||
useEffect(() => {
|
||||
const unlisten = listen("steam://cs2_video_changed", () => {
|
||||
// 文件变化时使用节流函数重新读取配置
|
||||
throttledRefreshVideoConfig()
|
||||
})
|
||||
return () => {
|
||||
void unlisten.then((fn) => fn())
|
||||
}
|
||||
}, [throttledRefreshVideoConfig])
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@@ -346,35 +397,42 @@ const VideoSetting = () => {
|
||||
>
|
||||
推荐
|
||||
</ToolButton>
|
||||
<ToolButton
|
||||
onClick={async () => {
|
||||
// 检查游戏是否运行
|
||||
const gameRunning = await checkGameRunning()
|
||||
if (gameRunning) {
|
||||
addToast({
|
||||
title: "无法应用设置",
|
||||
description: "检测到游戏正在运行,请先关闭游戏后再应用设置",
|
||||
color: "warning",
|
||||
})
|
||||
return
|
||||
}
|
||||
await tool.setVideoConfig(
|
||||
steam.state.steamDir,
|
||||
steam.currentUser()?.steam_id32 || 0,
|
||||
vconfig
|
||||
)
|
||||
await tool.getVideoConfig(
|
||||
steam.state.steamDir,
|
||||
steam.currentUser()?.steam_id32 || 0
|
||||
)
|
||||
setEdit(false)
|
||||
addToast({ title: "应用设置成功" })
|
||||
}}
|
||||
disabled={isGameRunning}
|
||||
<Tooltip
|
||||
content={isGameRunning ? "游戏运行中,无法修改视频设置" : ""}
|
||||
isDisabled={!isGameRunning}
|
||||
>
|
||||
<Plus />
|
||||
应用
|
||||
</ToolButton>
|
||||
<div>
|
||||
<ToolButton
|
||||
onClick={async () => {
|
||||
// 检查游戏是否运行
|
||||
const gameRunning = await checkGameRunning()
|
||||
if (gameRunning) {
|
||||
addToast({
|
||||
title: "无法应用设置",
|
||||
description: "检测到游戏正在运行,请先关闭游戏后再应用设置",
|
||||
color: "warning",
|
||||
})
|
||||
return
|
||||
}
|
||||
await tool.setVideoConfig(
|
||||
steam.state.steamDir,
|
||||
steam.currentUser()?.steam_id32 || 0,
|
||||
vconfig
|
||||
)
|
||||
await tool.getVideoConfig(
|
||||
steam.state.steamDir,
|
||||
steam.currentUser()?.steam_id32 || 0
|
||||
)
|
||||
setEdit(false)
|
||||
addToast({ title: "应用设置成功" })
|
||||
}}
|
||||
disabled={isGameRunning}
|
||||
>
|
||||
<Plus />
|
||||
应用
|
||||
</ToolButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<ToolButton
|
||||
|
||||
@@ -61,38 +61,38 @@ const Nav = () => {
|
||||
{pathname !== "/" && (
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded cursor-pointer hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={() => {
|
||||
app.setInited(false)
|
||||
if (pathname !== "/") router.push("/")
|
||||
}}
|
||||
>
|
||||
<RocketOne size={16} />
|
||||
<RocketOne size={16} className="cursor-pointer" />
|
||||
</button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip content="深色模式" showArrow={true} delay={300}>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded cursor-pointer hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={() => (theme === "light" ? setAppTheme("dark") : setAppTheme("light"))}
|
||||
>
|
||||
{theme === "light" ? <SunOne size={16} /> : <Moon size={16} />}
|
||||
{theme === "light" ? <SunOne size={16} className="cursor-pointer" /> : <Moon size={16} className="cursor-pointer" />}
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="反馈" showArrow={true} delay={300}>
|
||||
<Link
|
||||
href="https://docs.qq.com/form/page/DZU1ieW9SQkxWU1RF"
|
||||
target="_blank"
|
||||
className="px-2 py-0 text-black rounded transition duration-150 dark:text-white hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 text-black transition duration-150 rounded cursor-pointer dark:text-white hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
>
|
||||
<button type="button">
|
||||
<Communication size={16} />
|
||||
<button type="button" className="cursor-pointer">
|
||||
<Communication size={16} className="cursor-pointer" />
|
||||
</button>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
<AuthButtonWrapper />
|
||||
{/* <AuthButtonWrapper /> */}
|
||||
|
||||
<ResetModal />
|
||||
|
||||
@@ -100,24 +100,24 @@ const Nav = () => {
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded cursor-pointer hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={minimize}
|
||||
>
|
||||
<Minus size={16} />
|
||||
<Minus size={16} className="cursor-pointer" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded cursor-pointer hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={toggleMaximize}
|
||||
>
|
||||
<Square size={16} />
|
||||
<Square size={16} className="cursor-pointer" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded cursor-pointer hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={close}
|
||||
>
|
||||
<Close size={16} />
|
||||
<Close size={16} className="cursor-pointer" />
|
||||
</button>
|
||||
</>
|
||||
{/* )} */}
|
||||
@@ -149,10 +149,10 @@ function ResetModal() {
|
||||
<Tooltip content="重置设置" showArrow={true} delay={300}>
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 rounded transition duration-150 hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
className="px-2 py-0 transition duration-150 rounded cursor-pointer hover:bg-black/10 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={onOpen}
|
||||
>
|
||||
<Refresh size={16} />
|
||||
<Refresh size={16} className="cursor-pointer" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
|
||||
@@ -43,7 +43,7 @@ const SideButton = ({
|
||||
onClick={() => router.push(route || "/")}
|
||||
className={cn(
|
||||
className,
|
||||
"p-2.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg transition relative active:scale-90 cursor-pointer",
|
||||
"p-2.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg transition relative active:scale-90 cursor-pointer [&>*]:cursor-pointer",
|
||||
path.startsWith(route) && "bg-black/5 dark:bg-white/5"
|
||||
)}
|
||||
{...rest}
|
||||
|
||||
@@ -6,12 +6,17 @@ interface ToolButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
className?: string
|
||||
selected?: boolean
|
||||
}
|
||||
export const ToolButton = ({ children, className, selected, ...rest }: ToolButtonProps) => {
|
||||
export const ToolButton = ({ children, className, selected, disabled, ...rest }: ToolButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"flex shrink-0 gap-0.5 active:scale-95 cursor-pointer items-center min-w-7 justify-center px-2 py-1.5 bg-black/5 transition hover:bg-black/10 dark:bg-white/5 dark:hover:bg-white/10 rounded-md text-sm leading-none",
|
||||
"flex shrink-0 gap-0.5 items-center min-w-7 justify-center px-2 py-1.5 bg-black/5 transition rounded-md text-sm leading-none",
|
||||
disabled
|
||||
? "opacity-50 cursor-not-allowed"
|
||||
: "active:scale-95 cursor-pointer hover:bg-black/10 dark:hover:bg-white/10",
|
||||
"dark:bg-white/5",
|
||||
className,
|
||||
selected &&
|
||||
"bg-purple-500/40 hover:bg-purple-500/20 text-purple-900 dark:text-purple-100 drop-shadow-sm dark:bg-purple-500/40 dark:hover:bg-purple-500/20"
|
||||
|
||||
@@ -19,7 +19,10 @@ export interface FpsTestResult {
|
||||
cpuCount: number | null
|
||||
os: string | null
|
||||
memory: number | null // GB
|
||||
gpu: string | null
|
||||
monitor: string | null
|
||||
} | null // 硬件信息
|
||||
note?: string // 备注(可选,用于向后兼容)
|
||||
}
|
||||
|
||||
const defaultValue = {
|
||||
@@ -27,7 +30,7 @@ const defaultValue = {
|
||||
}
|
||||
|
||||
export const fpsTestStore = store(
|
||||
"fpsTest",
|
||||
"fps_test",
|
||||
{ ...defaultValue },
|
||||
DEFAULT_STORE_CONFIG,
|
||||
)
|
||||
@@ -42,6 +45,7 @@ export const useFpsTestStore = () => {
|
||||
addResult,
|
||||
removeResult,
|
||||
clearResults,
|
||||
updateNote,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,3 +67,10 @@ const clearResults = () => {
|
||||
fpsTestStore.state.results = []
|
||||
}
|
||||
|
||||
const updateNote = (id: string, note: string) => {
|
||||
const result = fpsTestStore.state.results.find((r) => r.id === id)
|
||||
if (result) {
|
||||
result.note = note
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ const defaultValue = {
|
||||
] as LaunchOption[],
|
||||
launchIndex: 0,
|
||||
powerPlan: 0,
|
||||
autoCloseGame: true, // 帧数测试自动关闭游戏
|
||||
videoSetting: {
|
||||
version: "15",
|
||||
vendor_id: "0",
|
||||
@@ -184,6 +185,7 @@ export const useToolStore = () => {
|
||||
setLaunchIndex,
|
||||
removeLaunchOption,
|
||||
setPowerPlan,
|
||||
setAutoCloseGame,
|
||||
setVideoSetting,
|
||||
getVideoConfig,
|
||||
setVideoConfig,
|
||||
@@ -198,10 +200,17 @@ const setLaunchOption = (option: LaunchOption, index: number) => {
|
||||
option,
|
||||
...toolStore.state.launchOptions.slice(index + 1),
|
||||
]
|
||||
// 同步更新托盘
|
||||
sendCurrentLaunchOptionToTray(toolStore.state.launchIndex)
|
||||
}
|
||||
|
||||
const setLaunchOptions = (options: LaunchOption[]) => {
|
||||
toolStore.state.launchOptions = options
|
||||
// 确保索引在有效范围内
|
||||
if (toolStore.state.launchIndex >= options.length) {
|
||||
toolStore.state.launchIndex = options.length > 0 ? options.length - 1 : 0
|
||||
}
|
||||
sendCurrentLaunchOptionToTray(toolStore.state.launchIndex)
|
||||
}
|
||||
|
||||
const setLaunchIndex = (index: number) => {
|
||||
@@ -214,10 +223,27 @@ const removeLaunchOption = (index: number) => {
|
||||
...toolStore.state.launchOptions.slice(0, index),
|
||||
...toolStore.state.launchOptions.slice(index + 1),
|
||||
]
|
||||
// 如果删除的是当前项或当前项在删除项之后,需要调整索引
|
||||
if (index <= toolStore.state.launchIndex) {
|
||||
if (toolStore.state.launchIndex > 0) {
|
||||
toolStore.state.launchIndex -= 1
|
||||
} else {
|
||||
toolStore.state.launchIndex = 0
|
||||
}
|
||||
}
|
||||
// 确保索引在有效范围内
|
||||
if (toolStore.state.launchIndex >= toolStore.state.launchOptions.length) {
|
||||
toolStore.state.launchIndex = toolStore.state.launchOptions.length > 0 ? toolStore.state.launchOptions.length - 1 : 0
|
||||
}
|
||||
sendCurrentLaunchOptionToTray(toolStore.state.launchIndex)
|
||||
}
|
||||
|
||||
const sendCurrentLaunchOptionToTray = (index: number) => {
|
||||
void emit("tray://get_current_launch_option", toolStore.state.launchOptions[index].name || index + 1)
|
||||
// 发送完整的启动项列表和当前索引到托盘
|
||||
void emit("tray://update_launch_options", {
|
||||
options: toolStore.state.launchOptions,
|
||||
currentIndex: index,
|
||||
})
|
||||
}
|
||||
const setPowerPlan = (plan: number) => {
|
||||
toolStore.state.powerPlan = plan
|
||||
@@ -227,6 +253,10 @@ const sendPowerPlanToTray = (plan: number) => {
|
||||
void emit("tray://get_powerplan", plan)
|
||||
}
|
||||
|
||||
const setAutoCloseGame = (enabled: boolean) => {
|
||||
toolStore.state.autoCloseGame = enabled
|
||||
}
|
||||
|
||||
const setVideoSetting = (setting: VideoSetting) => {
|
||||
toolStore.state.videoSetting = setting
|
||||
}
|
||||
@@ -248,11 +278,14 @@ const addLaunchOption = (option: LaunchOption) => {
|
||||
return
|
||||
}
|
||||
toolStore.state.launchOptions = [...toolStore.state.launchOptions, option]
|
||||
// 同步更新托盘
|
||||
sendCurrentLaunchOptionToTray(toolStore.state.launchIndex)
|
||||
}
|
||||
|
||||
const resetToolStore = () => {
|
||||
setLaunchOptions(defaultValue.launchOptions)
|
||||
setLaunchIndex(defaultValue.launchIndex)
|
||||
setPowerPlan(defaultValue.powerPlan)
|
||||
setAutoCloseGame(defaultValue.autoCloseGame)
|
||||
setVideoSetting(defaultValue.videoSetting)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user