feat(UI): 展开箭头全站统一为 ExpandChevron 共享组件

- 新增 src/client/views/ExpandChevron.jsx:14x14 chevron-right、success 绿、展开旋转 90°(源自策略 tab 表格行图标)
- 设置页策略分组行首 ▸/▾ 文字箭头改用该组件,并修复箭头与策略名的水平对齐(基线 trick 改 flex 精确居中)
- StrategyTab / TradeRecordsTab 两处重复内联 SVG 收敛为共享组件
- 去掉字段编辑器展开区冗余「收起」按钮(行首箭头/「收起字段」已可收起),清理 onClose prop
- docs: 新增 UI约束-006(展开/收起箭头全站统一)
This commit is contained in:
2026-09-03 17:39:49 +08:00
parent d793d2cf35
commit ed43ecae39
6 changed files with 49 additions and 50 deletions
+38
View File
@@ -0,0 +1,38 @@
/**
* ExpandChevron —— 全站统一的「展开/收起」箭头图标
*
* 视觉语言与策略 tab 表格行展开图标一致:
* - 14×14 chevron-right(描边圆角),展开时顺时针旋转 90°(0.15s 过渡)
* - 默认使用主题 success 色,可通过 color 覆盖
*
* 使用方:
* - StrategyTab / TradeRecordsTab:表格行内(纯装饰,行点击切换展开)
* - SettingsSection 策略分组:包在 button 内作为展开开关
*/
export function ExpandChevron({ expanded = false, size = 14, color, style }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 14 14"
aria-hidden="true"
style={{
display: 'inline-block',
transition: 'transform .15s ease',
transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
color: color ?? 'var(--dsw-alias-state-success-primary, #2e7d32)',
verticalAlign: 'middle',
...style,
}}
>
<path
d="M4.5 2.5 L9.5 7 L4.5 11.5"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
+4 -4
View File
@@ -13,6 +13,7 @@ import { useState, useEffect, useCallback, Fragment } from 'react';
import { useRpc } from './connection.jsx';
import { ToastProvider, useToast } from './Toast.jsx';
import { StrategyFieldsEditor } from './StrategyFieldsEditor.jsx'; // R-013:策略自定义字段编辑器
import { ExpandChevron } from './ExpandChevron.jsx'; // 展开箭头与策略 tab 表格行图标统一
/** 确认弹窗 */
function ConfirmDialog({ title, message, onConfirm, onCancel }) {
@@ -349,12 +350,12 @@ function StrategyGroupSettings() {
<button onClick={() => setEditingId(null)} style={{ padding: '2px 8px', cursor: 'pointer', marginLeft: 4 }}>取消</button>
</span>
) : (
<span>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
<button
onClick={() => { setExpandedId(expandedId === s.id ? null : s.id); }}
style={{ padding: '2px 6px', cursor: 'pointer', border: 'none', background: 'none', fontSize: 12, color: 'var(--dsw-alias-label-secondary, #666)', marginRight: 6 }}
style={{ padding: '2px 4px', cursor: 'pointer', border: 'none', background: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', lineHeight: 0 }}
title={expandedId === s.id ? '收起配置' : '展开配置自定义字段'}
>{expandedId === s.id ? '▾' : '▸'}</button>
><ExpandChevron expanded={expandedId === s.id} /></button>
{s.name}
</span>
)}
@@ -382,7 +383,6 @@ function StrategyGroupSettings() {
strategy={s}
saving={fieldSaving}
onSave={(fields) => handleSaveConfig(s.id, fields)}
onClose={() => setExpandedId(null)}
/>
</td>
</tr>
+1 -3
View File
@@ -19,10 +19,9 @@ const dangerBtn = { ...btnStyle, borderColor: 'var(--dsw-alias-state-error-prima
* @param {object} props
* @param {object} props.strategy 策略(含 configSchema
* @param {Function} props.onSave 保存回调 (fields) => Promise
* @param {Function} props.onClose 收起回调
* @param {boolean} props.saving 保存中
*/
export function StrategyFieldsEditor({ strategy, onSave, onClose, saving }) {
export function StrategyFieldsEditor({ strategy, onSave, saving }) {
const toast = useToast();
const [fields, setFields] = useState(() => (Array.isArray(strategy?.configSchema) ? strategy.configSchema.map((x) => ({ ...x })) : []));
// 表单草稿:null=关闭;{ index:-1=新增 | 编辑下标, ... }
@@ -102,7 +101,6 @@ export function StrategyFieldsEditor({ strategy, onSave, onClose, saving }) {
<span>
<button onClick={startAdd} style={solidBtn('var(--dsw-alias-state-success-primary, #2e7d32)')}>+ 添加字段</button>
<button onClick={saveAll} disabled={saving} style={solidBtn('var(--dsw-alias-state-business-primary, #1565c0)')}>{saving ? '保存中...' : '保存字段'}</button>
<button onClick={onClose} style={btnStyle}>收起</button>
</span>
</div>
+2 -21
View File
@@ -23,6 +23,7 @@ import { useMarket } from '../market/MarketDataProvider.jsx';
import { PriceCell } from './PriceCell.jsx';
import { ColumnSettingsPopover } from './ColumnSettingsPopover.jsx'; // 迭代11:列显隐设置
import { FieldCellEditor } from './FieldCellEditor.jsx'; // 迭代11:自定义字段单元格点击编辑
import { ExpandChevron } from './ExpandChevron.jsx'; // 行展开图标共享组件
/** 价格格式化(2 位小数) */
function fmtPrice(v) {
@@ -508,27 +509,7 @@ function StrategyTabInner({ strategyId, strategyName }) {
>
<td style={{ padding: '8px 6px', textAlign: 'center', width: 28 }}>
{hasHolding ? (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
style={{
display: 'inline-block',
transition: 'transform .15s ease',
transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
color: 'var(--dsw-alias-state-success-primary, #2e7d32)',
verticalAlign: 'middle',
}}
>
<path
d="M4.5 2.5 L9.5 7 L4.5 11.5"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<ExpandChevron expanded={isExpanded} />
) : (
<span style={{ color: 'var(--dsw-alias-border-l3, #ccc)', fontSize: 10 }}>·</span>
)}
+2 -22
View File
@@ -18,6 +18,7 @@
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useRpc } from './connection.jsx';
import { LoadState } from './LoadState.jsx';
import { ExpandChevron } from './ExpandChevron.jsx'; // 行展开图标共享组件
import { RangeSelector } from './RangeSelector.jsx';
const POLL_INTERVAL_MS = 4000; // 3-5s 轮询(取 4s
@@ -411,28 +412,7 @@ function OrderRows({ order, expanded, onToggle, attributionCandidates, onLoadCan
>
<td style={{ ...tdStyle, textAlign: 'center', width: 28 }}>
{hasTrades ? (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
style={{
display: 'inline-block',
transition: 'transform .15s ease',
transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
cursor: 'pointer',
color: 'var(--dsw-alias-state-success-primary, #2e7d32)',
verticalAlign: 'middle',
}}
>
<path
d="M4.5 2.5 L9.5 7 L4.5 11.5"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<ExpandChevron expanded={expanded} style={{ cursor: 'pointer' }} />
) : (
<span style={{ color: 'var(--dsw-alias-border-l3, #ccc)', fontSize: 10 }}>·</span>
)}