2790b92378
- strategy-positions 附加 holding_id + avgPrice/lastTradePrice(无关联默认成本价) - trades/by-holding 端点(按 holding 查委托汇总) - StrategyTab 持仓行展开(懒加载 + 内嵌汇总表,含交易日/时间列) - 神之一手 tab 激活时隐藏 AI 对话输入框(纯 CSS :has() 方案)
76 lines
3.3 KiB
React
76 lines
3.3 KiB
React
/**
|
||
* 全部持仓 tab 组件
|
||
* 显示全量真实持仓(QMT),标注每只票的份额分配
|
||
*/
|
||
|
||
import { useState, useEffect, useCallback } from 'react';
|
||
import { useRpc } from './connection.jsx';
|
||
import { LoadState } from './LoadState.jsx';
|
||
import { useMarket } from '../market/MarketDataProvider.jsx';
|
||
import { PriceCell } from './PriceCell.jsx';
|
||
|
||
export function AllPositionsTab(props) {
|
||
const [positions, setPositions] = useState(null);
|
||
const [error, setError] = useState(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const call = useRpc();
|
||
const { getPrice, registerCodes } = useMarket();
|
||
|
||
const load = useCallback(async () => {
|
||
setLoading(true);
|
||
setError(null);
|
||
try {
|
||
const res = await call('one-divine-lot/summary');
|
||
if (res && res.ok) {
|
||
setPositions(res.value);
|
||
// 上报持仓 code 给行情 Provider(去重:不再由 Provider 自拉 positions)
|
||
const codes = (res.value?.positions ?? []).map((p) => p.code).filter(Boolean);
|
||
registerCodes(codes);
|
||
} else setError((res && res.error && res.error.message) || '加载失败');
|
||
} catch (e) {
|
||
setError(e.message);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [call]);
|
||
|
||
useEffect(() => {
|
||
load();
|
||
}, [load]);
|
||
|
||
return (
|
||
<div className="odl-root" style={{ padding: 16, fontFamily: 'system-ui' }}>
|
||
<h3 style={{ margin: '0 0 12px' }}>全部持仓({positions ? positions.positions.length : 0} 只)</h3>
|
||
<LoadState loading={loading} error={error} onRetry={load} autoRetry={2}>
|
||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
|
||
<thead>
|
||
<tr style={{ textAlign: 'left', borderBottom: '2px solid #ddd' }}>
|
||
<th style={{ padding: '8px 6px', lineHeight: '24px' }}>代码</th>
|
||
<th style={{ padding: '8px 6px', lineHeight: '24px' }}>名称</th>
|
||
<th style={{ padding: '8px 6px', lineHeight: '24px' }}>现价</th>
|
||
<th style={{ padding: '8px 6px', lineHeight: '24px' }}>总持仓</th>
|
||
<th style={{ padding: '8px 6px', lineHeight: '24px' }}>已分配</th>
|
||
<th style={{ padding: '8px 6px', lineHeight: '24px' }}>未分配</th>
|
||
<th style={{ padding: '8px 6px', lineHeight: '24px' }}>分配详情</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{(positions?.positions ?? []).map((p) => (
|
||
<tr key={p.code} style={{ borderBottom: '1px solid #eee' }}>
|
||
<td style={{ padding: '8px 6px', lineHeight: '24px' }}>{p.code}</td>
|
||
<td style={{ padding: '8px 6px', lineHeight: '24px' }}>{p.name}</td>
|
||
<PriceCell price={getPrice(p.code)?.lastPrice} lastClose={getPrice(p.code)?.lastClose} />
|
||
<td style={{ padding: '8px 6px', lineHeight: '24px' }}>{p.totalVolume}</td>
|
||
<td style={{ padding: '8px 6px', lineHeight: '24px' }}>{p.allocated}</td>
|
||
<td style={{ padding: '8px 6px', lineHeight: '24px', color: p.unallocated > 0 ? '#e65100' : '#2e7d32' }}>{p.unallocated}</td>
|
||
<td style={{ padding: '8px 6px', lineHeight: '24px', fontSize: 12, color: '#555' }}>
|
||
{Object.entries(p.shares).map(([sid, n]) => sid + ': ' + n).join(', ') || '—'}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</LoadState>
|
||
</div>
|
||
);
|
||
} |