feat(策略持仓): 表格加昨收/涨幅列(涨幅支持升降排序)+ 去掉总持仓列
- 昨收列:行情缓存 lastClose - 涨幅列:(现价-昨收)/昨收,红涨绿跌着色 - 涨幅排序:点击表头降序/升序切换(▼/▲指示) - 移除总持仓列
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
* - 窄屏(<480px):每元素一行,两端对齐
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useRpc } from './connection.jsx';
|
||||
import { LoadState } from './LoadState.jsx';
|
||||
import { ToastProvider, useToast } from './Toast.jsx';
|
||||
@@ -28,6 +28,30 @@ function fmtPrice(v) {
|
||||
return Number(v).toFixed(2);
|
||||
}
|
||||
|
||||
/** 涨幅计算:(现价 - 昨收) / 昨收 × 100%;缺数据返回 null */
|
||||
function calcPctChange(snap) {
|
||||
if (!snap) return null;
|
||||
const last = Number(snap.lastPrice);
|
||||
const close = Number(snap.lastClose);
|
||||
if (!isFinite(last) || !isFinite(close) || close === 0) return null;
|
||||
return ((last - close) / close) * 100;
|
||||
}
|
||||
|
||||
/** 涨幅着色:涨红跌绿(A股习惯) */
|
||||
function pctStyle(pct) {
|
||||
if (pct == null) return { color: '#999' };
|
||||
if (pct > 0) return { color: '#d32f2f', fontWeight: 'bold' };
|
||||
if (pct < 0) return { color: '#2e7d32', fontWeight: 'bold' };
|
||||
return { color: '#999' };
|
||||
}
|
||||
|
||||
/** 涨幅文本:+x.xx% / -x.xx% / — */
|
||||
function fmtPct(pct) {
|
||||
if (pct == null || !isFinite(pct)) return '—';
|
||||
const sign = pct > 0 ? '+' : '';
|
||||
return sign + pct.toFixed(2) + '%';
|
||||
}
|
||||
|
||||
/** 添加持仓区域响应式样式(内联注入,含媒体查询) */
|
||||
const ADD_FORM_CSS = `
|
||||
.odl-add-form {
|
||||
@@ -120,6 +144,9 @@ function StrategyTabInner({ strategyId, strategyName }) {
|
||||
const [expandedCode, setExpandedCode] = useState(null); // 当前展开的 code
|
||||
const [holdingTrades, setHoldingTrades] = useState(null); // { code: [委托汇总] }
|
||||
const [holdingTradesLoading, setHoldingTradesLoading] = useState(null); // 展开加载中的 code
|
||||
// 涨幅排序(2026-09-02):sortKey='pctChange' 时按涨幅排序;null=默认顺序
|
||||
const [sortKey, setSortKey] = useState(null); // 'pctChange' | null
|
||||
const [sortDir, setSortDir] = useState('desc'); // 'asc' | 'desc'
|
||||
const call = useRpc();
|
||||
const toast = useToast();
|
||||
const { getPrice, registerCodes } = useMarket();
|
||||
@@ -226,8 +253,34 @@ function StrategyTabInner({ strategyId, strategyName }) {
|
||||
}
|
||||
};
|
||||
|
||||
/** 涨幅排序:点击表头切换升/降序 */
|
||||
const togglePctSort = () => {
|
||||
if (sortKey === 'pctChange') {
|
||||
setSortDir((d) => (d === 'desc' ? 'asc' : 'desc'));
|
||||
} else {
|
||||
setSortKey('pctChange');
|
||||
setSortDir('desc');
|
||||
}
|
||||
};
|
||||
|
||||
const name = strategyName || (strategyId === 'grid-supermarket' ? '网格策略持仓' : strategyId === 'manual-t' ? '做T持仓' : strategyId);
|
||||
|
||||
// 计算每行涨幅 + 按涨幅排序(sortKey='pctChange' 时)
|
||||
const rowsForRender = useMemo(() => {
|
||||
const rows = (positions ?? []).map((p) => ({ ...p, pctChange: calcPctChange(getPrice(p.code)) }));
|
||||
if (sortKey === 'pctChange') {
|
||||
const dir = sortDir === 'asc' ? 1 : -1;
|
||||
rows.sort((a, b) => {
|
||||
const va = a.pctChange ?? -Infinity;
|
||||
const vb = b.pctChange ?? -Infinity;
|
||||
return (va - vb) * dir;
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}, [positions, sortKey, sortDir, getPrice]);
|
||||
|
||||
const pctArrow = sortKey === 'pctChange' ? (sortDir === 'desc' ? ' ▼' : ' ▲') : '';
|
||||
|
||||
return (
|
||||
<div className="odl-root" style={{ padding: 16, fontFamily: 'system-ui' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, flexWrap: 'wrap', gap: 8 }}>
|
||||
@@ -287,15 +340,16 @@ function StrategyTabInner({ strategyId, strategyName }) {
|
||||
<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 onClick={togglePctSort} style={{ padding: '8px 6px', lineHeight: '24px', cursor: 'pointer', userSelect: 'none' }}>涨幅{pctArrow}</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 ?? []).map((p) => {
|
||||
{rowsForRender.map((p) => {
|
||||
const isExpanded = expandedCode === p.code;
|
||||
const trades = (holdingTrades ?? {})[p.code];
|
||||
const isLoading = holdingTradesLoading === p.code;
|
||||
@@ -340,10 +394,11 @@ function StrategyTabInner({ strategyId, strategyName }) {
|
||||
<td style={{ padding: '8px 6px' }}>{p.code}</td>
|
||||
<td style={{ padding: '8px 6px' }}>{p.name}</td>
|
||||
<PriceCell price={getPrice(p.code)?.lastPrice} lastClose={getPrice(p.code)?.lastClose} />
|
||||
<td style={{ padding: '8px 6px', ...pctStyle(p.pctChange) }}>{fmtPct(p.pctChange)}</td>
|
||||
<td style={{ padding: '8px 6px' }}>{fmtPrice(getPrice(p.code)?.lastClose)}</td>
|
||||
<td style={{ padding: '8px 6px' }}>{fmtPrice(p.avgPrice)}</td>
|
||||
<td style={{ padding: '8px 6px' }}>{fmtPrice(p.lastTradePrice)}</td>
|
||||
<td style={{ padding: '8px 6px', fontWeight: 'bold' }}>{p.shares}</td>
|
||||
<td style={{ padding: '8px 6px' }}>{p.volume}</td>
|
||||
<td style={{ padding: '8px 6px', whiteSpace: 'nowrap' }} onClick={(e) => e.stopPropagation()}>
|
||||
{removeTarget && removeTarget.code === p.code ? (
|
||||
<span>
|
||||
@@ -383,7 +438,7 @@ function StrategyTabInner({ strategyId, strategyName }) {
|
||||
</tr>
|
||||
{isExpanded && (
|
||||
<tr style={{ borderBottom: '1px solid #eee', background: '#fafafa' }}>
|
||||
<td colSpan={9} style={{ padding: '4px 8px 12px' }}>
|
||||
<td colSpan={10} style={{ padding: '4px 8px 12px' }}>
|
||||
{isLoading ? (
|
||||
<div style={{ padding: 8, color: '#999', fontSize: 12 }}>交易记录加载中...</div>
|
||||
) : !trades || trades.length === 0 ? (
|
||||
|
||||
Reference in New Issue
Block a user