import {useEffect,useRef,useState,type FormEvent} from "react" import {Button} from "./ui/button" import {Input} from "./ui/input" import {Badge} from "./ui/badge" import {Card,CardContent,CardHeader,CardTitle,CardDescription} from "./ui/card" import {ApiError,request} from "@/lib/auth" type Quote={id:string;symbol:string;exchange:string;price:string;observed_at:string;version:number} export function SimulationQuotesPanel({onSessionExpired}:{onSessionExpired:()=>void}){ const [quotes,setQuotes]=useState([]),[symbol,setSymbol]=useState(""),[exchange,setExchange]=useState("SSE"),[price,setPrice]=useState(""),[error,setError]=useState(""),[busy,setBusy]=useState(false),controller=useRef(null) function failed(e:unknown){if(e instanceof ApiError&&e.status===401)onSessionExpired();else setError(e instanceof Error?e.message:"操作失败")} async function refresh(){controller.current?.abort();const next=new AbortController();controller.current=next;try{const result=await request("/api/admin/v1/simulation-quotes",{signal:next.signal});if(!next.signal.aborted)setQuotes(result)}catch(e){if(!next.signal.aborted)failed(e)}} useEffect(()=>{void refresh();return()=>controller.current?.abort()},[]) async function save(e:FormEvent){e.preventDefault();setBusy(true);setError("");try{await request("/api/admin/v1/simulation-quotes",{method:"POST",body:JSON.stringify({symbol,exchange,price,observed_at:new Date().toISOString()})});setPrice("");await refresh()}catch(e){failed(e)}finally{setBusy(false)}} return
模拟价格回放手工数据
用于验证计划条件和站内提醒。价格由此表单录入,不代表在线行情;触发不改变现金或持仓。
void save(e)} className="grid items-end gap-4 sm:grid-cols-4">
{error&&

{error}

}
{quotes.map(q=>)}
证券价格观察时间版本
{q.exchange}:{q.symbol}¥{q.price}{new Date(q.observed_at).toLocaleString("zh-CN")}{q.version}
{quotes.length===0&&

尚未录入模拟价格。

}
}