Files
Veyra/apps/mobile/app/candidates/[id].tsx
T

15 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {Linking,Text} from "react-native"
import {useLocalSearchParams,useRouter} from "expo-router"
import {useQuery} from "@tanstack/react-query"
import {useState} from "react"
import {useSession} from "../../src/state/session"
import {Body,Button,ErrorMessage,Panel,Screen,Title,styles} from "../../src/components/ui"
import {publicationAvailable,type Candidate} from "../../src/lib/candidates"
export default function CandidateDetail(){
const {id}=useLocalSearchParams<{id:string}>(),{session,client}=useSession(),[linkError,setLinkError]=useState("")
const query=useQuery({queryKey:["candidate",session?.user.id,id],enabled:!!session&&typeof id==="string",queryFn:({signal})=>client<Candidate>(`/api/v1/candidates/${encodeURIComponent(id)}`,{signal}),refetchInterval:15000})
const c=query.data,router=useRouter()
async function open(url:string){setLinkError("");try{const parsed=new URL(url);if(parsed.protocol!=="https:"||parsed.username||parsed.password)throw new Error("证据链接无效。");await Linking.openURL(url)}catch{setLinkError("无法打开证据来源,请稍后重试。")}}
return <Screen><Title>{c?.content.name??"候选详情"}</Title><Button secondary title={query.isFetching?"刷新中…":"刷新状态"} disabled={query.isFetching} onPress={()=>void query.refetch()}/>{query.isPending&&<Body>正在读取修订…</Body>}{query.error&&<ErrorMessage message={query.error.message}/>}{c&&<><Text style={styles.badge}>模拟候选 · 修订 {c.revision}</Text>{!publicationAvailable(c)&&<Panel accent><Text style={styles.subtitle}>此候选已失效</Text><Body>{c.status==="WITHDRAWN"?c.withdrawal_reason||"管理员已撤回此修订。":"此修订已过期、被替代,或关联策略已暂停 / 切换版本。"}历史依据保留供查看。</Body></Panel>}<Button title="预览模拟买入计划" disabled={!publicationAvailable(c)||query.isFetching||!!query.error} onPress={()=>router.push({pathname:"/plans/create",params:{candidate_id:c.id}})}/>{c.ai_analysis&&<Panel accent><Text style={styles.subtitle}>AI 分析与审核</Text><Body>{c.ai_analysis.analysis?.summary}</Body><Body>风险:{c.ai_analysis.analysis?.risks}</Body><Body>复核:{c.ai_analysis.review?.summary}</Body><Body>资料缺口:{c.ai_analysis.analysis?.missing_information.join(";")||"未报告"}</Body><Body>{c.ai_analysis.primary_model} / {c.ai_analysis.review_model} · {new Date(c.ai_analysis.updated_at).toLocaleString("zh-CN")}</Body></Panel>}<Text style={styles.subtitle}>{c.content.title}</Text><Body>{c.content.exchange}:{c.content.symbol} · 观察 {new Date(c.observed_at).toLocaleString("zh-CN")} · 有效截止 {new Date(c.valid_until).toLocaleString("zh-CN")}</Body><Panel>{c.screening&&<Body>自动筛选 · {c.screening.strategy_name} 修订 {c.screening.strategy_revision} · 日线 {c.screening.bar_date} · {c.screening.source_adapter} 配置版本 {c.screening.source_version}</Body>}<Text style={styles.subtitle}>观察逻辑</Text><Body>{c.content.thesis}</Body></Panel><Panel><Text style={styles.subtitle}>反证与风险</Text><Body>{c.content.counter_evidence}</Body><Text style={styles.subtitle}>失效条件</Text><Body>{c.content.invalidation}</Body></Panel><Text style={styles.subtitle}>证据来源</Text>{c.content.evidence.map((e,i)=><Panel key={i}><Text style={styles.subtitle}>{e.locator}</Text><Body>{e.summary}</Body><Button secondary title="打开原始来源" onPress={()=>void open(e.source_url)}/></Panel>)}<ErrorMessage message={linkError}/><Panel><Text style={styles.subtitle}>参考价格</Text><Body>区间 ¥{c.content.reference_plan.entry_low}–{c.content.reference_plan.entry_high}</Body><Body>风险价格 ¥{c.content.reference_plan.stop_loss} · 目标观察 ¥{c.content.reference_plan.target_price}</Body><Body>{c.screening&&c.screening.candidate_revision_id===c.id?"参考价格按后台策略规则从日线收盘价生成,经审核发布;不代表实时行情或已确认交易计划。":"参考价格经人工录入或修订,不代表实时行情或已确认交易计划。"}</Body></Panel></>}</Screen>
}