"use client" import { useEffect, useMemo, useState } from 'react' import Link from 'next/link' type RecordItem = { id: string timestamp: string sampleCount: number duration: number hasFit: boolean fit?: { a: number; b: number } stats: { codeMin: number codeMax: number codeAvg: number forceMin?: number forceMax?: number forceAvg?: number } } export default function Home() { const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [data, setData] = useState([]) const [total, setTotal] = useState(0) const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(12) const [hasFit, setHasFit] = useState<'all' | 'fit' | 'raw'>('all') const [sortBy, setSortBy] = useState<'timestamp' | 'duration' | 'maxValue'>('timestamp') const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc') const [startDate, setStartDate] = useState('') const [endDate, setEndDate] = useState('') const totalPages = useMemo(() => Math.max(1, Math.ceil(total / pageSize)), [total, pageSize]) useEffect(() => { const controller = new AbortController() async function run() { setLoading(true) setError(null) try { const params = new URLSearchParams() params.set('page', String(page)) params.set('pageSize', String(pageSize)) params.set('sortBy', sortBy) params.set('sortOrder', sortOrder) if (startDate) params.set('startDate', new Date(startDate).toISOString()) if (endDate) params.set('endDate', new Date(endDate).toISOString()) if (hasFit === 'fit') params.set('hasFit', 'true') if (hasFit === 'raw') params.set('hasFit', 'false') const res = await fetch(`/api/records?${params.toString()}`, { signal: controller.signal }) if (!res.ok) throw new Error(`HTTP ${res.status}`) const j = await res.json() setData(j.data) setTotal(j.pagination.total) } catch (e: any) { if (e.name !== 'AbortError') setError(e.message || '加载失败') } finally { setLoading(false) } } run() return () => controller.abort() }, [page, pageSize, hasFit, sortBy, sortOrder, startDate, endDate]) return (

ESP32 数据采集系统

setStartDate(e.target.value)} className="rounded border border-zinc-300 bg-white px-2 py-1 text-sm dark:bg-zinc-900" />
setEndDate(e.target.value)} className="rounded border border-zinc-300 bg-white px-2 py-1 text-sm dark:bg-zinc-900" />
{error &&
{error}
}
{loading && Array.from({ length: 6 }).map((_, i) => (
))} {!loading && data.map((r) => (
{new Date(r.timestamp).toLocaleString()}
{r.hasFit ? '已拟合' : '未拟合'}
{r.sampleCount} 点 · {r.duration.toFixed(2)} s
{r.hasFit ? ( <> 力值: {r.stats.forceMin?.toFixed(2)} ~ {r.stats.forceMax?.toFixed(2)} mN · 均值 {r.stats.forceAvg?.toFixed(2)} ) : ( <> 码值: {r.stats.codeMin} ~ {r.stats.codeMax} · 均值 {r.stats.codeAvg.toFixed(2)} )}
{r.hasFit && r.fit && (
y = {r.fit.a.toFixed(4)}x + {r.fit.b.toFixed(3)}
)}
查看
))}
{page} / {totalPages}
) }