feat: integrate AI candidate review and automatic publication

This commit is contained in:
2026-10-09 22:33:38 +08:00
parent 04dbeb2ac6
commit 86f1e7a897
46 changed files with 2460 additions and 2362 deletions
+1 -1
View File
@@ -15,5 +15,5 @@ export default function CreatePlan(){
const c=candidate.data,eligible=!!c&&publicationAvailable(c)&&account?.mode==="simulation",currentPreview=preview?.account_id===account?.id?preview:null
async function calculate(){if(!c||!account)return;setBusy(true);setError("");setPreview(null);try{setPreview(await client<Preview>("/api/v1/plan-previews",{method:"POST",body:{account_id:account.id,candidate_revision_id:c.id,expected_candidate_version:c.version,quantity,fee_budget:fee}}))}catch(e){setError(e instanceof Error?e.message:"预览失败");void candidate.refetch();void budget.refetch()}finally{setBusy(false)}}
async function confirm(){if(!currentPreview)return;setBusy(true);setError("");try{await client(`/api/v1/plan-previews/${currentPreview.id}/confirm`,{method:"POST",body:{}});await cache.invalidateQueries({queryKey:["plans"]});await cache.invalidateQueries({queryKey:["budget"]});router.replace("/(tabs)/plans")}catch(e){setError(e instanceof Error?e.message:"确认失败");setPreview(null);void candidate.refetch();void budget.refetch()}finally{setBusy(false)}}
return <Screen><Title>创建模拟买入计划</Title><Body>确认计划仅保留资金预算;提醒与计划不会产生交易或扣款。</Body>{candidate.error&&<ErrorMessage message={candidate.error.message}/>} {c&&<Panel><Text style={styles.subtitle}>{c.content.name} · {c.content.symbol}</Text><Body>候选修订 {c.revision} · 区间 ¥{c.content.reference_plan.entry_low}–{c.content.reference_plan.entry_high}</Body><Body>风险价格 ¥{c.content.reference_plan.stop_loss} · 有效截止 {new Date(c.valid_until).toLocaleString("zh-CN")}</Body></Panel>}{!account&&<><Body>请先创建或选择模拟账户。</Body><Button secondary title="账户管理" onPress={()=>router.push("/settings/accounts")}/></>}{account&&<Body>当前账户:{account.name} {account.mode!=="simulation"?"· 真实记录账户暂不支持模拟计划":""}</Body>}{c&&!publicationAvailable(c)&&<ErrorMessage message="此候选已失效,不能创建新计划。"/>}{budget.error&&<ErrorMessage message={budget.error.message}/>} {budget.data&&<Panel><Body>账本现金 ¥{budget.data.cash_balance}</Body><Body>其他计划保留 ¥{budget.data.reserved_amount} · 可用 ¥{budget.data.available_amount}</Body></Panel>}<Field label="计划数量(模拟规则:100 股整数倍)" keyboardType="number-pad" editable={!busy} value={quantity} onChangeText={v=>{setQuantity(v);setPreview(null)}}/><Field label="预计费用预算(元,允许 0)" keyboardType="decimal-pad" editable={!busy} value={fee} onChangeText={v=>{setFee(v);setPreview(null)}}/><ErrorMessage message={error}/><Button title="预览资金预算" disabled={!eligible||!quantity||!fee} busy={busy} onPress={()=>void calculate()}/>{currentPreview&&<Panel accent><Text style={styles.subtitle}>确认前核对</Text><Body>数量 {currentPreview.quantity} 股 · 预计费用 ¥{currentPreview.fee_budget}</Body><Body>按区间上限保留 ¥{currentPreview.reserved_amount},预览截止 {new Date(currentPreview.expires_at).toLocaleTimeString("zh-CN")}</Body><Body>确认时会重新检查候选版本与现金;确认后不代表成交,实际发生交易后另行记录。</Body><Button title="确认计划并保留预算" disabled={!eligible||Date.parse(currentPreview.expires_at)<=Date.now()} busy={busy} onPress={()=>void confirm()}/></Panel>}</Screen>
return <Screen><Title>创建模拟买入计划</Title><Body>确认计划仅保留资金预算;提醒与计划不会产生交易或扣款。</Body>{candidate.error&&<ErrorMessage message={candidate.error.message}/>}{c&&<Panel><Text style={styles.subtitle}>{c.content.name} · {c.content.symbol}</Text><Body>候选修订 {c.revision} · 区间 ¥{c.content.reference_plan.entry_low}–{c.content.reference_plan.entry_high}</Body><Body>风险价格 ¥{c.content.reference_plan.stop_loss} · 有效截止 {new Date(c.valid_until).toLocaleString("zh-CN")}</Body></Panel>}{!account&&<><Body>请先创建或选择模拟账户。</Body><Button secondary title="账户管理" onPress={()=>router.push("/settings/accounts")}/></>}{account&&<Body>当前账户:{account.name} {account.mode!=="simulation"?"· 真实记录账户暂不支持模拟计划":""}</Body>}{c&&!publicationAvailable(c)&&<ErrorMessage message="此候选已失效,不能创建新计划。"/>}{budget.error&&<ErrorMessage message={budget.error.message}/>}{budget.data&&<Panel><Body>账本现金 ¥{budget.data.cash_balance}</Body><Body>其他计划保留 ¥{budget.data.reserved_amount} · 可用 ¥{budget.data.available_amount}</Body></Panel>}<Field label="计划数量(模拟规则:100 股整数倍)" keyboardType="number-pad" editable={!busy} value={quantity} onChangeText={v=>{setQuantity(v);setPreview(null)}}/><Field label="预计费用预算(元,允许 0)" keyboardType="decimal-pad" editable={!busy} value={fee} onChangeText={v=>{setFee(v);setPreview(null)}}/><ErrorMessage message={error}/><Button title="预览资金预算" disabled={!eligible||!quantity||!fee} busy={busy} onPress={()=>void calculate()}/>{currentPreview&&<Panel accent><Text style={styles.subtitle}>确认前核对</Text><Body>数量 {currentPreview.quantity} 股 · 预计费用 ¥{currentPreview.fee_budget}</Body><Body>按区间上限保留 ¥{currentPreview.reserved_amount},预览截止 {new Date(currentPreview.expires_at).toLocaleTimeString("zh-CN")}</Body><Body>确认时会重新检查候选版本与现金;确认后不代表成交,实际发生交易后另行记录。</Body><Button title="确认计划并保留预算" disabled={!eligible||Date.parse(currentPreview.expires_at)<=Date.now()} busy={busy} onPress={()=>void confirm()}/></Panel>}</Screen>
}