From 2acda5959a9dd4daf03b6c85d5bb4428e110f2cf Mon Sep 17 00:00:00 2001 From: feie9456 Date: Sun, 16 Nov 2025 20:23:19 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=AF=BC=E5=87=BA=20CSV=20?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=EF=BC=8C=E7=A7=BB=E9=99=A4=20recordId=20?= =?UTF-8?q?=E5=92=8C=20timestamp=EF=BC=8C=E6=97=B6=E9=97=B4=E5=8D=95?= =?UTF-8?q?=E4=BD=8D=E6=94=B9=E4=B8=BA=E6=AF=AB=E7=A7=92=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=9B=BE=E8=A1=A8=E6=98=BE=E7=A4=BA=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E9=BB=98=E8=AE=A4=E9=9A=90=E8=97=8F=E5=8E=9F=E5=A7=8B?= =?UTF-8?q?=E5=92=8C=E6=BB=A4=E6=B3=A2=E7=A0=81=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/export/route.ts | 13 ++++--- app/records/[id]/page.tsx | 77 +++++++++++++++------------------------ 2 files changed, 37 insertions(+), 53 deletions(-) diff --git a/app/api/export/route.ts b/app/api/export/route.ts index 1ba3a65..802a8a8 100644 --- a/app/api/export/route.ts +++ b/app/api/export/route.ts @@ -48,20 +48,21 @@ export async function POST(req: NextRequest) { }) } - // CSV - let csv = 'recordId,timestamp,index,timeSec,code,force\n' + // CSV(去掉 recordId、timestamp,timeSec 改为毫秒单位) + let csv = 'index,timeMs,code,force\n' for (const r of rows) { - let step = r.sample_count > 0 ? r.duration / r.sample_count : 0 + // 以毫秒为单位的步长 + let stepMs = r.sample_count > 0 ? (r.duration * 1000) / r.sample_count : 0 if (r.rec_start_ms != null && r.rec_end_ms != null && r.sample_count > 0) { const dtMs = Number((r.rec_end_ms as bigint) - (r.rec_start_ms as bigint)) - if (Number.isFinite(dtMs) && dtMs > 0) step = dtMs / 1000 / r.sample_count + if (Number.isFinite(dtMs) && dtMs > 0) stepMs = dtMs / r.sample_count } const hasFit = r.fit_a != null for (let i = 0; i < r.code_data.length; i++) { - const t = (i * step).toFixed(6) + const t = (i * stepMs).toFixed(3) const code = r.code_data[i] const force = hasFit ? r.fit_a! * code + (r.fit_b ?? 0) : '' - csv += `${r.id},${new Date(r.timestamp).toISOString()},${i},${t},${code},${hasFit ? force : ''}\n` + csv += `${i},${t},${code},${hasFit ? force : ''}\n` } } return new Response(csv, { diff --git a/app/records/[id]/page.tsx b/app/records/[id]/page.tsx index f1a3eae..108f6c5 100644 --- a/app/records/[id]/page.tsx +++ b/app/records/[id]/page.tsx @@ -45,15 +45,14 @@ export default function Page({ params }: { params: Promise<{ id: string }> }) { const [error, setError] = useState(null) const [rec, setRec] = useState(null) // 滤波相关状态 - const [filterType, setFilterType] = useState<'none' | 'ema' | 'sg' | 'hampel_sg' | 'gauss'>('hampel_sg') + const [filterType, setFilterType] = useState<'none' | 'ema' | 'sg' | 'hampel_sg' | 'gauss'>('ema') const [emaAlpha, setEmaAlpha] = useState(0.01) const [sgWindow, setSgWindow] = useState(21) const [sgOrder, setSgOrder] = useState<2 | 3>(3) const [hampelWindow, setHampelWindow] = useState(11) const [hampelK, setHampelK] = useState(2) const [gaussSigma, setGaussSigma] = useState(5) - const [showOriginal, setShowOriginal] = useState(false) - const [showFiltered, setShowFiltered] = useState(true) + // 图例控制显示,不再使用自定义 checkbox;默认隐藏在数据集上配置 useEffect(() => { const controller = new AbortController() @@ -76,10 +75,7 @@ export default function Page({ params }: { params: Promise<{ id: string }> }) { return () => controller.abort() }, [id]) - // 若存在拟合,默认不显示滤波码值(仅在记录加载时初始化一次) - useEffect(() => { - if (rec?.fit) setShowFiltered(false) - }, [rec?.fit]) + // 显示策略改由 Chart.js 图例控制,默认隐藏在数据集 hidden 字段中设置 // 侦测小屏(手机)以调整 Chart 布局 const [isSmall, setIsSmall] = useState(false) @@ -121,7 +117,8 @@ export default function Page({ params }: { params: Promise<{ id: string }> }) { const timeAxis = useMemo(() => { if (!rec) return [] as number[] - const step = rec.sampleCount > 0 ? (rec.duration * 1000) / rec.sampleCount : 0 + // 使用秒为单位,保留3位小数 + const step = rec.sampleCount > 0 ? rec.duration / rec.sampleCount : 0 return Array.from({ length: rec.sampleCount }, (_, i) => +(i * step).toFixed(3)) }, [rec]) @@ -351,15 +348,7 @@ export default function Page({ params }: { params: Promise<{ id: string }> }) { )} - - - + {/* 由 Chart.js 图例控制数据集显示,不再使用本地 checkbox */} 按 Ctrl+滚轮 缩放,拖拽平移 @@ -369,35 +358,29 @@ export default function Page({ params }: { params: Promise<{ id: string }> }) { data={{ labels: timeAxis, datasets: [ - ...(showOriginal && rec - ? [ - { - type: 'line' as const, - label: '原始码值', - data: rec.code, - borderColor: '#a1a1aa', - backgroundColor: 'rgba(161,161,170,0.2)', - borderDash: [4, 4] as any, - borderWidth: 1, - yAxisID: 'yCode', - pointRadius: 0, - }, - ] - : []), - ...(showFiltered - ? [ - { - type: 'line' as const, - label: '滤波码值', - data: filterType === 'none' ? rec.code : filteredCode, - borderColor: '#0ea5e9', - backgroundColor: 'rgba(14,165,233,0.2)', - borderWidth: 1.5, - yAxisID: 'yCode', - pointRadius: 0, - }, - ] - : []), + { + type: 'line' as const, + label: '原始码值', + data: rec.code, + borderColor: '#a1a1aa', + backgroundColor: 'rgba(161,161,170,0.2)', + borderDash: [4, 4] as any, + borderWidth: 1, + yAxisID: 'yCode', + pointRadius: 0, + hidden: true, // 默认不显示原始 + }, + { + type: 'line' as const, + label: '滤波码值', + data: filterType === 'none' ? rec.code : filteredCode, + borderColor: '#0ea5e9', + backgroundColor: 'rgba(14,165,233,0.2)', + borderWidth: 1.5, + yAxisID: 'yCode', + pointRadius: 0, + hidden: Boolean(rec?.fit), // 存在拟合时默认不显示滤波码值 + }, ...(rec.fit && forceSeries ? [ { @@ -431,7 +414,7 @@ export default function Page({ params }: { params: Promise<{ id: string }> }) { }, }, scales: { - x: { title: { display: !isSmall, text: '时间 (ms)' }, ticks: { maxRotation: 0, font: { size: isSmall ? 10 : 12 } } }, + x: { title: { display: !isSmall, text: '时间 (s)' }, ticks: { maxRotation: 0, font: { size: isSmall ? 10 : 12 } } }, yCode: { type: 'linear' as const, position: 'left' as const, title: { display: !isSmall, text: '码值' }, ticks: { font: { size: isSmall ? 10 : 12 } } }, yForce: { type: 'linear' as const, position: 'right' as const, title: { display: !isSmall, text: '力值 (mN)' }, grid: { drawOnChartArea: false }, ticks: { font: { size: isSmall ? 10 : 12 } } }, },