[fix] table width + text selectiion + batch note naming

This commit is contained in:
2025-11-07 00:18:46 +08:00
parent f7c9e455f7
commit 8bef96bb02

View File

@@ -214,7 +214,7 @@ function extractFpsMetrics(result: string): { avg: number | null; p1: number | n
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">
<span className="flex-1 min-w-0 truncate select-text">
{note || "无备注"}
</span>
<Button
@@ -278,6 +278,10 @@ export function FpsTest() {
const testStartVideoSettingRef = useRef<typeof tool.state.videoSetting | null>(null)
// 记录当前测试的分辨率信息(用于备注)
const currentTestResolutionRef = useRef<{ width: string; height: string; label: string } | null>(null)
// 记录当前分辨率在分辨率组中的索引和总测试次数(用于批量测试备注)
const currentResolutionGroupInfoRef = useRef<{ resIndex: number; totalResolutions: number; totalTestCount: number; currentBatchIndex: number; batchCount: number } | null>(null)
// 记录最后一次测试的时间戳(用于平均值记录)
const lastTestTimestampRef = useRef<string | null>(null)
// 检测游戏是否运行
const checkGameRunning = useCallback(async () => {
@@ -426,6 +430,8 @@ export function FpsTest() {
setTestResult(parsed.data)
setTestTimestamp(parsed.timestamp)
// 保存最后一次测试的时间戳(用于平均值记录)
lastTestTimestampRef.current = parsed.timestamp
// 成功读取后,清除测试开始时间戳(测试已完成)
testStartTimestampRef.current = null
testStartTimeRef.current = null
@@ -461,6 +467,7 @@ export function FpsTest() {
// 如果是批量测试,保存结果到批量结果数组,否则直接保存
const currentBatchProgress = batchTestProgress
const currentResolution = currentTestResolutionRef.current
const resolutionGroupInfo = currentResolutionGroupInfoRef.current
if (currentBatchProgress) {
const result = { avg, p1 }
setBatchTestResults((prev) => [...prev, result])
@@ -474,9 +481,18 @@ export function FpsTest() {
if (testNote) {
batchNote = batchNote ? `${testNote} ${batchNote}` : testNote
}
batchNote = batchNote
? `${batchNote} [批量${currentBatchProgress.current}/${currentBatchProgress.total}]`
: `[批量${currentBatchProgress.current}/${currentBatchProgress.total}]`
// 如果启用了分辨率组,使用新的备注格式:[分辨率] [批量当前测试/该分辨率批量总数]
if (resolutionGroupInfo && isResolutionGroupEnabled) {
const { currentBatchIndex, batchCount } = resolutionGroupInfo
const batchInfo = `[批量${currentBatchIndex}/${batchCount}]`
batchNote = batchNote ? `${batchNote} ${batchInfo}` : batchInfo
} else {
// 普通批量测试,使用原来的格式
batchNote = batchNote
? `${batchNote} [批量${currentBatchProgress.current}/${currentBatchProgress.total}]`
: `[批量${currentBatchProgress.current}/${currentBatchProgress.total}]`
}
fpsTest.addResult({
id: `${now.getTime()}-${Math.random().toString(36).slice(2, 11)}`,
@@ -550,6 +566,8 @@ export function FpsTest() {
// 清除保存的启动时视频设置和分辨率信息
testStartVideoSettingRef.current = null
currentTestResolutionRef.current = null
currentResolutionGroupInfoRef.current = null
// 注意不清除lastTestTimestampRef因为平均值记录需要使用它
if (!silent) {
if (avg !== null || p1 !== null) {
@@ -633,6 +651,7 @@ export function FpsTest() {
testStartTimeRef.current = null
testStartVideoSettingRef.current = null
currentTestResolutionRef.current = null
currentResolutionGroupInfoRef.current = null
addToast({
title: "测试超时200秒测试失败",
variant: "flat",
@@ -826,6 +845,7 @@ export function FpsTest() {
testStartTimeRef.current = null
testStartVideoSettingRef.current = null
currentTestResolutionRef.current = null
currentResolutionGroupInfoRef.current = null
return false
}
}
@@ -848,6 +868,17 @@ export function FpsTest() {
return
}
// 检查垂直同步设置,如果开启则提醒(但不阻止测试)
if (steam.state.steamDirValid && steam.currentUser()) {
await tool.getVideoConfig(steam.state.steamDir, steam.currentUser()?.steam_id32 || 0)
if (tool.state.videoSetting?.mat_vsync === "1") {
addToast({
title: "警告:垂直同步已开启,可能会影响测试结果准确性",
color: "warning",
})
}
}
const totalTests = batchTestCount
batchTestAbortRef.current = false
setBatchTestResults([])
@@ -879,6 +910,15 @@ export function FpsTest() {
globalTestIndex++
setBatchTestProgress({ current: globalTestIndex, total: totalTestCount })
// 设置当前分辨率组信息(用于备注)
currentResolutionGroupInfoRef.current = {
resIndex,
totalResolutions,
totalTestCount,
currentBatchIndex: i,
batchCount: totalTests,
}
// 执行单次测试第一次测试跳过5秒等待
const isFirstTest = resIndex === 0 && i === 1
const success = await runSingleTest(i, totalTests, isFirstTest, currentResolution)
@@ -915,23 +955,50 @@ export function FpsTest() {
validResults.reduce((sum, r) => sum + (r.avg || 0), 0) / validResults.length
const avgP1 = validResults.reduce((sum, r) => sum + (r.p1 || 0), 0) / validResults.length
const now = new Date()
const testDate = now.toISOString()
const month = String(now.getMonth() + 1).padStart(2, "0")
const day = String(now.getDate()).padStart(2, "0")
const hour = String(now.getHours()).padStart(2, "0")
const minute = String(now.getMinutes()).padStart(2, "0")
const second = String(now.getSeconds()).padStart(2, "0")
const testTime = `${month}/${day} ${hour}:${minute}:${second}`
// 使用最后一次测试的时间戳作为平均值记录的时间戳(更准确)
// 如果最后一次测试时间戳存在,使用它;否则使用当前时间
let testTime: string
let testDate: string
if (lastTestTimestampRef.current) {
testTime = lastTestTimestampRef.current
// 将时间戳转换为ISO格式用于排序
const now = new Date()
const [monthDay, time] = testTime.split(" ")
const [month, day] = monthDay.split("/")
const [hour, minute, second] = time.split(":")
const testDateTime = new Date(
now.getFullYear(),
parseInt(month) - 1,
parseInt(day),
parseInt(hour),
parseInt(minute),
parseInt(second)
)
testDate = testDateTime.toISOString()
} else {
const now = new Date()
testDate = now.toISOString()
const month = String(now.getMonth() + 1).padStart(2, "0")
const day = String(now.getDate()).padStart(2, "0")
const hour = String(now.getHours()).padStart(2, "0")
const minute = String(now.getMinutes()).padStart(2, "0")
const second = String(now.getSeconds()).padStart(2, "0")
testTime = `${month}/${day} ${hour}:${minute}:${second}`
}
// 修改备注格式:[分辨率] [批量N次平均]
let averageNote = `[${currentResolution.label}]`
if (testNote) {
averageNote = `${testNote} ${averageNote}`
}
averageNote = `${averageNote} [批量${totalTests}次平均]`
// 生成唯一ID
const idTime = lastTestTimestampRef.current
? new Date(testDate).getTime()
: Date.now()
fpsTest.addResult({
id: `${now.getTime()}-${Math.random().toString(36).slice(2, 11)}`,
id: `${idTime}-${Math.random().toString(36).slice(2, 11)}`,
testTime,
testDate,
mapName: mapConfig?.name || "unknown",
@@ -1016,21 +1083,46 @@ export function FpsTest() {
validResults.reduce((sum, r) => sum + (r.avg || 0), 0) / validResults.length
const avgP1 = validResults.reduce((sum, r) => sum + (r.p1 || 0), 0) / validResults.length
const now = new Date()
const testDate = now.toISOString()
const month = String(now.getMonth() + 1).padStart(2, "0")
const day = String(now.getDate()).padStart(2, "0")
const hour = String(now.getHours()).padStart(2, "0")
const minute = String(now.getMinutes()).padStart(2, "0")
const second = String(now.getSeconds()).padStart(2, "0")
const testTime = `${month}/${day} ${hour}:${minute}:${second}`
// 使用最后一次测试的时间戳作为平均值记录的时间戳(更准确)
let testTime: string
let testDate: string
if (lastTestTimestampRef.current) {
testTime = lastTestTimestampRef.current
// 将时间戳转换为ISO格式用于排序
const now = new Date()
const [monthDay, time] = testTime.split(" ")
const [month, day] = monthDay.split("/")
const [hour, minute, second] = time.split(":")
const testDateTime = new Date(
now.getFullYear(),
parseInt(month) - 1,
parseInt(day),
parseInt(hour),
parseInt(minute),
parseInt(second)
)
testDate = testDateTime.toISOString()
} else {
const now = new Date()
testDate = now.toISOString()
const month = String(now.getMonth() + 1).padStart(2, "0")
const day = String(now.getDate()).padStart(2, "0")
const hour = String(now.getHours()).padStart(2, "0")
const minute = String(now.getMinutes()).padStart(2, "0")
const second = String(now.getSeconds()).padStart(2, "0")
testTime = `${month}/${day} ${hour}:${minute}:${second}`
}
const averageNote = testNote
? `${testNote} [批量${totalTests}次平均]`
: `[批量${totalTests}次平均]`
// 生成唯一ID
const idTime = lastTestTimestampRef.current
? new Date(testDate).getTime()
: Date.now()
fpsTest.addResult({
id: `${now.getTime()}-${Math.random().toString(36).slice(2, 11)}`,
id: `${idTime}-${Math.random().toString(36).slice(2, 11)}`,
testTime,
testDate,
mapName: mapConfig?.name || "unknown",
@@ -1183,6 +1275,84 @@ export function FpsTest() {
}
}
// 仅导出平均结果CSV
const handleExportAverageCSV = async () => {
// 过滤备注中包含"平均"的结果
const averageResults = fpsTest.state.results.filter(
(result) => result.note && result.note.includes("平均")
)
if (averageResults.length === 0) {
addToast({ title: "没有平均结果数据可导出", color: "warning" })
return
}
try {
// 构建CSV内容
const headers = [
"测试时间",
"测试地图",
"平均帧",
"P1低帧",
"CPU",
"系统版本",
"GPU",
"内存(GB)",
"分辨率",
"视频设置",
"备注",
]
const csvRows = [headers.join(",")]
for (const result of averageResults) {
const row = [
`"${result.testTime}"`,
`"${result.mapLabel}"`,
result.avg !== null ? result.avg.toFixed(1) : "N/A",
result.p1 !== null ? result.p1.toFixed(1) : "N/A",
`"${result.hardwareInfo?.cpu || "N/A"}"`,
`"${result.hardwareInfo?.os || "N/A"}"`,
`"${result.hardwareInfo?.gpu || "N/A"}"`,
result.hardwareInfo?.memory ? result.hardwareInfo.memory.toString() : "N/A",
result.videoSetting
? `${result.videoSetting.defaultres}x${result.videoSetting.defaultresheight}`
: "N/A",
`"${formatVideoSettingSummary(result.videoSetting)}"`,
`"${result.note || ""}"`,
]
csvRows.push(row.join(","))
}
const csvContent = csvRows.join("\n")
// 添加UTF-8 BOM以确保Excel等软件正确识别编码
const csvContentWithBOM = "\uFEFF" + csvContent
// 使用文件保存对话框
const filePath = await save({
filters: [
{
name: "CSV",
extensions: ["csv"],
},
],
defaultPath: `fps_test_average_results_${new Date().toISOString().split("T")[0]}.csv`,
})
if (filePath) {
await writeTextFile(filePath, csvContentWithBOM)
addToast({ title: "导出成功", color: "success" })
}
} catch (error) {
console.error("导出CSV失败:", error)
addToast({
title: `导出失败: ${error instanceof Error ? error.message : String(error)}`,
color: "danger",
})
}
}
// 应用预设分辨率
const handlePresetResolution = (preset: { width: string; height: string; label: string }) => {
fpsTest.setResolution(preset.width, preset.height)
@@ -1228,10 +1398,16 @@ export function FpsTest() {
</div>
)}
{showResultsTable && (
<Button size="sm" variant="flat" onPress={handleExportCSV} className="font-medium">
<DownloadOne size={14} />
CSV
</Button>
<>
<Button size="sm" variant="flat" onPress={handleExportAverageCSV} className="font-medium">
<DownloadOne size={14} />
</Button>
<Button size="sm" variant="flat" onPress={handleExportCSV} className="font-medium">
<DownloadOne size={14} />
CSV
</Button>
</>
)}
<Button
size="sm"
@@ -1291,7 +1467,7 @@ export function FpsTest() {
<TableColumn width={80}></TableColumn>
<TableColumn width={80}></TableColumn>
<TableColumn width={80}>P1低帧</TableColumn>
<TableColumn width={150}>CPU</TableColumn>
<TableColumn width={100}>CPU</TableColumn>
<TableColumn minWidth={80}></TableColumn>
<TableColumn minWidth={100}>GPU</TableColumn>
<TableColumn width={80}></TableColumn>
@@ -1314,10 +1490,16 @@ export function FpsTest() {
<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 className="text-xs max-w-[175px]">
<Tooltip
content={result.hardwareInfo?.cpu || "N/A"}
delay={500}
placement="top"
>
<div className="truncate cursor-help">
{result.hardwareInfo?.cpu || "N/A"}
</div>
</Tooltip>
</TableCell>
<TableCell className="text-xs">
<div className="truncate">
@@ -1474,9 +1656,9 @@ export function FpsTest() {
}
}}
isDisabled={isMonitoring}
className="h-5 min-w-[40px] px-1.5 text-xs font-medium"
className="h-5 gap-1 flex px-1.5 min-w-fit text-xs font-medium"
>
<List size={14} />
<List size={12} />
</Button>
{!isResolutionGroupEnabled && (