"use client" import { useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' type AdminRecordItem = { id: string timestamp: string sampleCount: number duration: number } function toLocalInputValue(iso: string): string { const d = new Date(iso) if (Number.isNaN(d.getTime())) return '' const pad = (n: number) => n.toString().padStart(2, '0') const year = d.getFullYear() const month = pad(d.getMonth() + 1) const day = pad(d.getDate()) const hour = pad(d.getHours()) const minute = pad(d.getMinutes()) const second = pad(d.getSeconds()) return `${year}-${month}-${day}T${hour}:${minute}:${second}` } export default function AdminClient({ initialData, currentPage, pageSize, totalPages, }: { initialData: AdminRecordItem[] currentPage: number pageSize: number totalPages: number }) { const router = useRouter() const [data, setData] = useState(initialData) const [selected, setSelected] = useState>(new Set()) const [editingTimes, setEditingTimes] = useState>(() => { const initial: Record = {} for (const r of initialData) { initial[r.id] = toLocalInputValue(r.timestamp) } return initial }) const [dirty, setDirty] = useState>({}) function toggleSelect(id: string) { setSelected((prev) => { const next = new Set(prev) if (next.has(id)) next.delete(id) else next.add(id) return next }) } function selectAllCurrent() { setSelected(new Set(data.map((d) => d.id))) } function clearSelection() { setSelected(new Set()) } async function handleBatchDelete() { if (selected.size === 0) return if (!confirm(`确定要删除选中的 ${selected.size} 条记录吗?`)) return try { const res = await fetch('/api/records/batch', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ action: 'delete', ids: Array.from(selected) }), }) if (!res.ok) throw new Error('批量删除失败') const j = await res.json() const deletedIds: string[] = j.deletedIds || [] setData((prev) => prev.filter((r) => !deletedIds.includes(r.id))) clearSelection() } catch { alert('批量删除失败') } } function setNowFor(id: string) { const now = new Date() const isoLocal = toLocalInputValue(now.toISOString()) setEditingTimes((prev) => ({ ...prev, [id]: isoLocal })) setDirty((prev) => ({ ...prev, [id]: true })) } function shiftMinutes(id: string, minutes: number) { setEditingTimes((prev) => { const current = prev[id] const base = current ? new Date(current) : new Date() if (Number.isNaN(base.getTime())) return prev const shifted = new Date(base.getTime() + minutes * 60 * 1000) const isoLocal = toLocalInputValue(shifted.toISOString()) return { ...prev, [id]: isoLocal } }) setDirty((prev) => ({ ...prev, [id]: true })) } async function applyTime(id: string) { const input = editingTimes[id] if (!input) { alert('请输入时间') return } const dt = new Date(input) if (Number.isNaN(dt.getTime())) { alert('时间格式不正确') return } try { const res = await fetch('/api/records/batch', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ action: 'updateTimestamp', ids: [id], timestamp: dt.toISOString() }), }) if (!res.ok) throw new Error('修改时间失败') const j = await res.json() const updated: { id: string; timestamp: string }[] = j.updated || [] if (!updated.length) return const { timestamp } = updated[0] setData((prev) => prev.map((r) => (r.id === id ? { ...r, timestamp } : r))) setEditingTimes((prev) => ({ ...prev, [id]: toLocalInputValue(timestamp) })) setDirty((prev) => ({ ...prev, [id]: false })) } catch { alert('修改时间失败') } } function goToPage(page: number) { const params = new URLSearchParams() params.set('page', String(page)) params.set('pageSize', String(pageSize)) router.push(`/admin?${params.toString()}`) } return (

Admin 面板

批量删除记录,批量修改时间等维护操作

← 返回首页
已选 {selected.size} 条
选中 ID 时间 / 编辑 采样点数
{data.length === 0 && (
暂无数据
)}
{data.map((r) => (
toggleSelect(r.id)} className="h-4 w-4" /> {r.id}
{new Date(r.timestamp).toLocaleString()} {r.sampleCount}
详情
{ const v = e.target.value setEditingTimes((prev) => ({ ...prev, [r.id]: v })) const original = toLocalInputValue(r.timestamp) setDirty((prev) => ({ ...prev, [r.id]: v !== original })) }} className="w-full bg-transparent text-[11px] outline-none" />
时长 {r.duration} · 点数 {r.sampleCount}
))}
{currentPage} / {totalPages}
) }