feat(R-013): 策略自定义字段完整实现 + 列显隐配置 + 单元格编辑

功能实现(迭代 11):
- 数据层: strategies.configSchema 自定义字段定义(text/number/boolean/enum + def + unit 单位可空)
  + strategy_holdings "values" JSON 列(幂等 ALTER)+ readValues/writeValues + 归一化
- API: holdings/values-update 端点(服务端按 configSchema 校验: number有限/enum在选项/boolean布尔)
- 设置页: 「策略分组」策略行展开配置字段(StrategyFieldsEditor 独立组件)
- 持仓表: 自定义字段列化 + 列显隐/排序(ColumnSettingsPopover,每策略独立 strategyColumns 配置)
- 单元格点击编辑(FieldCellEditor): 文本/数字输入框、布尔开关、枚举下拉 + ✓ 确认
- UI 细节: ✎ 可编辑标记(值后)、编辑列宽稳定、数字输入框固定 5 位宽

修复:
- SettingsSection Fragment/StrategyFieldsEditor 未导入导致的配置页白屏
- z.dict schema dts 推断 cosmokit 引用(显式类型注解)
- 自定义字段从展开交易明细移出(字段已列化)

文档: R-013/迭代11 技术方案/验收标准/迭代目标 同步(D6 演进 + unit + 列配置)
回归: test-r013-custom-fields 21/21 + test-r013-columns 16/16 + tabs 23/23
This commit is contained in:
2026-09-02 17:21:41 +08:00
parent 6f74ed4a79
commit aac16b904c
17 changed files with 1270 additions and 61 deletions
+13 -1
View File
@@ -11,6 +11,7 @@
import {
getStrategies, updateStrategies, addStrategy, removeStrategy,
getTabs, updateTabs,
getStrategyColumns, updateStrategyColumns,
} from '../settings.js';
/** 策略/份额/tabs 端点方法表 */
@@ -27,6 +28,9 @@ export const STRATEGY_METHODS = new Set([
'remove-shares',
'tabs',
'tabs/update',
'holdings/values-update', // R-013:写某持仓行的自定义字段值
'strategy-columns', // 迭代11列显隐:读某策略列配置(归一化)
'strategy-columns/update', // 迭代11列显隐:写某策略列配置
]);
/**
@@ -66,7 +70,15 @@ export async function handleStrategy(method, args, { manager, settings }) {
return await manager.moveAllUnallocatedToStrategy(args.code, args.strategyId);
case 'remove-shares':
return await manager.removeFromStrategy(args.code, args.strategyId, args.shares);
case 'holdings/values-update':
// R-013:写持仓行自定义字段值(服务端按 configSchema 校验在 manager 内完成)
return await manager.updateHoldingValues(args.holdingId, args.values);
case 'strategy-columns':
return getStrategyColumns(settings, args.strategyId);
case 'strategy-columns/update':
await updateStrategyColumns(settings, args.strategyId, args.columns);
return getStrategyColumns(settings, args.strategyId);
default:
throw Object.assign(new Error('unknown strategy method: ' + method), { code: 'not-found' });
}
}
}