Files
one_divine_lot/docs/04-迭代记录/11-策略自定义字段配置/技术实现方案.md
T
kyugao a4902bb730 feat(R-013+重构): 策略自定义字段需求文档 + 结构归类优化
需求与文档:
- R-013 策略自定义字段配置(定义随策略 configSchema 存 settings、值落
  strategy_holdings.values JSON、类型化文本/数字/布尔/枚举)定稿并进入
  PLAN-012 / 迭代 11(含目标数据模型 + 端到端示例 + 技术方案 + 验收标准)
- R-011(Tab设置)/ R-012(UI主题适配)归档至 已完成/,索引标记已归档
- 沉淀约束: 产品约束-010 / 技术约束-015 / 技术约束-016 / UI约束-005

结构重构(技术约束-016):
- src/component/ 平铺还原为语义分域目录: data-source/ storage/ position/
  market/ trades/(git rename 保留历史)
- 清理死代码: 删除 AllocationStorage.js(0 引用)、DataStore
  setDataset/removeDataset(无调用方)
- 同步更新 src/index.js / scripts/*.mjs / tsdown.config.ts 引用
- typecheck + build + test-r009 回归 14/14 通过
2026-09-02 15:38:51 +08:00

90 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 技术实现方案:11-策略自定义字段配置
> 迭代编号:11 依据:PLAN-012 + R-013(目标数据模型 / 端到端示例)+ 技术约束-015 / 产品约束-010 / UI约束-005
## 1. 数据层
### 1.1 settings.strategies 扩展 configSchemasrc/settings.js
```js
// strategySchema 中 strategies 项扩展:
strategies: z.array(z.object({
id: z.string().required(),
name: z.string().required(),
configSchema: z.array(z.object({
key: z.string().required(), // 稳健标识(英文 slug
label: z.string(), // UI 展示名(可中文)
type: z.union([z.const('text'), z.const('number'), z.const('boolean'), z.const('enum')]).required(),
enum: z.array(z.string()), // 仅 type=enum 时使用
def: z.any(), // 默认值(text=string / number=number / boolean=boolean / enum=枚举项)
})).default([]), // 缺省 []
})).default(DEFAULT_STRATEGIES),
```
- 读取归一化:旧项无 configSchema → 补默认空数组(读取时或 schema default 保证);
- addStrategy 返回值默认带 configSchema: []renameStrategy / updateStrategies 整表更新天然携带;
- 兼容:settings 旧数据({id,name})经 schemastery default/union 归一化,无迁移脚本。
### 1.2 strategy_holdings 新增 values 列 + 读写(src/component/SqliteStore.js
```js
// init() 内补列(幂等,沿用 _ensureTradeAttributionColumns 模式;只读连接容忍)
_ensureHoldingValuesColumn() {
const cols = new Set(this.db.prepare('PRAGMA table_info(strategy_holdings)').all().map(c => c.name));
if (!cols.has('values')) this.db.exec('ALTER TABLE strategy_holdings ADD COLUMN values TEXT');
}
readValues(holdingId) // SELECT values FROM strategy_holdings WHERE holding_id=? → JSON.parse ?? null
writeValues(holdingId, values) // UPDATE strategy_holdings SET values=? WHERE holding_id=? → JSON.stringify
```
- openHolding/addShares/reduceShares/closeHolding **不碰 values 列**(份额生命周期与字段值独立);
- getCurrentHoldings / getHoldingHistory 的 _mapHolding 附加 values 字段(解析 JSON)。
## 2. API 层(src/api/strategies.js
```js
// 既有:strategy-positions → manager.getStrategyPositions(strategyId)
// PositionManager 返回项附加 valuesfrom SqliteStore 映射)
// 新增端点:
case 'holdings/values-update':
return await manager.updateHoldingValues(args.holdingId, args.values);
```
```js
// PositionManager.updateHoldingValues(holdingId, values)
// 1) 定位 holdings 行(须存在且当前持仓 closed_at IS NULL;否则 404
// 2) 服务端校验(按所属策略 configSchema):
// number → Number.isFinite(Number(v))enum → enum.includes(v)boolean → typeof v === 'boolean'
// 未定义 key 的额外键放行(可扩展);空值/缺省可写入
// 3) 校验失败抛 { code: 'field-validation' },成功 writeValues 并返回 { holdingId, values }
```
## 3. 设置页 UIsrc/client/views/SettingsSection.jsx
- 「策略分组」子 tab 策略行加展开手柄(▶/▸ toggle,样式沿用 expand 惯例);
- 展开区:
- 字段列表表格:展示名 | key | 类型 | 枚举选项 | 默认值 | 操作(删除)—— 空态提示「尚未配置自定义字段」;
- 添加/编辑字段表单:字段名(label)→ 自动生成 key(拼音 slug,复用 generateStrategyId 思路)/ 可手改校验唯一、类型下拉(文本/数字/布尔/枚举)、枚举选项(type=enum 时逗号分隔输入)、默认值(按类型渲染输入);
- 行内编辑/删除:编辑回填表单,删除后保存生效;
- 保存:整表提交 strategies/update(映射为 { ...s, configSchema }),成功 Toast + 刷新(沿用「刷新页面后生效」机制)。
## 4. 策略持仓 tab UIsrc/client/views/StrategyTab.jsx
- 持仓行展开区(R-010 展开逻辑基础上)增加「自定义字段」区块(configSchema 非空才渲染,空则无此区块);
- 块内按 configSchema 渲染控件:text=输入框 / number=数字输入 / boolean=开关(复用 Switch 组件)/ enum=下拉;
- 值来源:strategy-positions 返回的 values;控件初值 = values[key] ?? def
- 编辑即保存:单个字段变更(或区块「保存」按钮)→ 调 one-divine-lot/holdings/values-update;成功 Toast;校验失败(服务端返回)Toast 错误并回滚输入;
- 与交易记录展开区块并存(展开区纵向分区:交易记录 / 自定义字段)。
## 5. 回归脚本(scripts/test-r013-custom-fields.mjs
- ODL_TEST_DATA_DIR 独立数据目录(技术约束-011);
- 用例:addStrategy 带 configSchema(四类型)→ openHolding ×2 同策略不同 code → writeValues 各自值 → 读回校验 → 校验拦截(数字非法 / 枚举越界)→ 旧策略无 configSchema 持仓行 readValues 为 NULL
- 断言存储与读取一致、校验拒绝。
## 6. 验证
- build + typecheck 通过;
- 老师人工验收(见验收标准)。