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 通过
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# 技术实现方案:11-策略自定义字段配置
|
||||
|
||||
> 迭代编号:11 | 依据:PLAN-012 + R-013(目标数据模型 / 端到端示例)+ 技术约束-015 / 产品约束-010 / UI约束-005
|
||||
|
||||
## 1. 数据层
|
||||
|
||||
### 1.1 settings.strategies 扩展 configSchema(src/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 返回项附加 values(from 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. 设置页 UI(src/client/views/SettingsSection.jsx)
|
||||
|
||||
- 「策略分组」子 tab 策略行加展开手柄(▶/▸ toggle,样式沿用 expand 惯例);
|
||||
- 展开区:
|
||||
- 字段列表表格:展示名 | key | 类型 | 枚举选项 | 默认值 | 操作(删除)—— 空态提示「尚未配置自定义字段」;
|
||||
- 添加/编辑字段表单:字段名(label)→ 自动生成 key(拼音 slug,复用 generateStrategyId 思路)/ 可手改校验唯一、类型下拉(文本/数字/布尔/枚举)、枚举选项(type=enum 时逗号分隔输入)、默认值(按类型渲染输入);
|
||||
- 行内编辑/删除:编辑回填表单,删除后保存生效;
|
||||
- 保存:整表提交 strategies/update(映射为 { ...s, configSchema }),成功 Toast + 刷新(沿用「刷新页面后生效」机制)。
|
||||
|
||||
## 4. 策略持仓 tab UI(src/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 通过;
|
||||
- 老师人工验收(见验收标准)。
|
||||
Reference in New Issue
Block a user