import {useEffect,useRef,useState} from "react" import {ScanLine} from "lucide-react" import {request,ApiError} from "@/lib/auth" import {Button} from "./ui/button" type RunState={state:string;targets:number;completed:number;generated:number;skipped:number;failed:number;started_at:string|null;finished_at:string|null;message:string} export function ScreeningRunControl({onSessionExpired,onCompleted}:{onSessionExpired:()=>void;onCompleted:()=>void}){ const [run,setRun]=useState(null),[busy,setBusy]=useState(false),[error,setError]=useState("") const lastFinished=useRef(null),completed=useRef(onCompleted) completed.current=onCompleted function fail(e:unknown){if(e instanceof ApiError&&e.status===401)onSessionExpired();else setError(e instanceof Error?e.message:"筛选状态读取失败")} useEffect(()=>{const controller=new AbortController();let fetching=false const refresh=async()=>{if(fetching)return;fetching=true;try{const result=await request("/api/admin/v1/strategy-screening-status",{signal:controller.signal});if(controller.signal.aborted)return;setRun(result);if(result.finished_at&&lastFinished.current!==result.finished_at){lastFinished.current=result.finished_at;completed.current()}}catch(e){if(!controller.signal.aborted)fail(e)}finally{fetching=false}} void refresh();const timer=setInterval(()=>void refresh(),3000);return()=>{controller.abort();clearInterval(timer)} },[]) async function scan(){setBusy(true);setError("");try{setRun(await request("/api/admin/v1/strategy-screenings/run",{method:"POST",body:"{}"}))}catch(e){fail(e)}finally{setBusy(false)}} const running=run?.state==="RUNNING"||run?.state==="QUEUED" return

自动筛选 → 人工审核 → App 发布

从已启用的策略范围获取真实日线,自动填写依据与价格规则,无需逐条录入股票。

{run?.finished_at&&"最近一批:"}{run?.message??"正在读取筛选状态…"}{run?.finished_at&&{new Date(run.finished_at).toLocaleString("zh-CN")}}

{run?.state==="COMPLETED"&&run.targets===0&&

当前没有新的可处理日线:已处理的结果会保留,数据陈旧或策略未启用时会等待下一次更新。

}{error&&

{error}

}
}