diff --git a/src/client/views/StrategyTab.jsx b/src/client/views/StrategyTab.jsx index 033ec41..3b24679 100644 --- a/src/client/views/StrategyTab.jsx +++ b/src/client/views/StrategyTab.jsx @@ -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 (
@@ -287,15 +340,16 @@ function StrategyTabInner({ strategyId, strategyName }) { 代码 名称 现价 + 涨幅{pctArrow} + 昨收 成本价 最后一笔成交价 策略份额 - 总持仓 操作 - {(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 }) { {p.code} {p.name} + {fmtPct(p.pctChange)} + {fmtPrice(getPrice(p.code)?.lastClose)} {fmtPrice(p.avgPrice)} {fmtPrice(p.lastTradePrice)} {p.shares} - {p.volume} e.stopPropagation()}> {removeTarget && removeTarget.code === p.code ? ( @@ -383,7 +438,7 @@ function StrategyTabInner({ strategyId, strategyName }) { {isExpanded && ( - + {isLoading ? (
交易记录加载中...
) : !trades || trades.length === 0 ? (