🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@complex-suite/data

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@complex-suite/data - npm Package Compare versions

Comparing version
5.0.13
to
5.0.14
+5
-0
history.md

@@ -0,1 +1,6 @@

### `5.0.14`
- fix(dictionary): `SimpleDateEdit` 泛型约束 `R extends Boolean` 修正为 `R extends boolean`,共 4 处(T5)。`Boolean` 是对象包装类型,`boolean` 是原始类型。
- docs(readme): README.md 重写为快速使用文档(759 行),聚焦快速上手,含标准列表页完整示例、字典配置、搜索区、数据加载、生命周期、嵌套字典等实用内容。
- docs(todo): ToDo.md 新增 C1(DictionaryValue 职责拆分方案)、R3(destroy 重复代码方案对比含 A/B/C 三方案),整体优化结构:合并「延后审核」与「待审核」重复区、统一编号体系、新增长期方向与已完成区分区。
### `5.0.13`

@@ -2,0 +7,0 @@ - docs(policy): **发版规范** — 所有子包必须同时升级 minor 并统一发版,禁止单独发版。详见根目录 `README.md`。

+3
-3
{
"name": "@complex-suite/data",
"version": "5.0.13",
"version": "5.0.14",
"description": "a complex data",

@@ -15,4 +15,4 @@ "type": "module",

"dependencies": {
"@complex-suite/utils": "3.0.13",
"@complex-suite/plugin": "5.0.13"
"@complex-suite/utils": "3.0.14",
"@complex-suite/plugin": "5.0.14"
},

@@ -19,0 +19,0 @@ "devDependencies": {

+235
-404
# @complex-suite/data 快速使用
`@complex-suite/data` 是 complex-suite 的核心数据层。它不负责渲染 UI,而是负责描述业务页面的数据结构、搜索条件、字典字段、分页、选择、编辑、详情、异步加载和业务操作。
`@complex-suite/data` 是 complex-suite 的核心数据层。它不渲染 UI,只负责描述业务页面的**数据结构、搜索条件、字典字段、分页、选择、编辑、详情、异步加载和业务操作**。
在当前 monorepo 中,它位于依赖链中间:`utils → plugin → data → component → component-antd`。实际业务页面通常通过 `data` 创建无头数据模型,再交给 `component-antd` 的 `ComplexQuickList`、`ComplexQuickCascade` 等组件渲染。
依赖链:`utils → plugin → data → component → component-antd`。业务页面通过 `data` 创建无头数据模型,再交给 `component-antd` 的 `ComplexQuickList` / `ComplexQuickCascade` 渲染。

@@ -13,72 +13,27 @@ ## 安装

## 架构概览
## 核心导出
```ts
import {
// 业务数据类
ComplexList, ComplexInfo, SelectData, TrackData,
// 数据类继承链
Data, SimpleData, DefaultData, BaseData, ComplexData,
// 模块
ModuleData, DictionaryData, SearchData, PaginationData,
ChoiceData, SortData, UpdateData, StatusData, DependData, PromiseData, ResetData,
// 值对象
DictionaryValue, ArrayValue, SelectValue, CascaderValue, FormValue,
StorageValue, ForceValue, InterfaceValue, AttrsValue, TipValue, FileValue,
GridParse, LayoutParse,
// 字典项
DefaultList, DefaultInfo, DefaultEdit, InputEdit, InputNumberEdit,
TextAreaEdit, SelectEdit, SwitchEdit, DateEdit, DateRangeEdit, SimpleDateEdit,
FileEdit, ButtonEdit, ButtonGroupEdit, ContentEdit, CustomEdit, CustomLoadEdit,
FormEdit, ListEdit, ObserveList, DefaultMod, DefaultSimpleEdit, DefaultLoadEdit, DefaultSelectEdit,
// 配置
dataConfig, dataLocale
} from '@complex-suite/data'
```
packages/data/
├── index.ts # 包入口,按 7 组导出
├── dataConfig.ts # 全局配置(颜色/empty Symbol/工具函数)
├── src/
│ ├── data/ # 数据类继承链
│ │ ├── Data.ts # 基类:_id/_buffer/_syncData 钩子
│ │ ├── SimpleData.ts # $extra 额外数据
│ │ ├── DefaultData.ts # $prop/$life 生命周期/Storage
│ │ ├── BaseData.ts # $status/$promise/$depend/$module/active/loadData
│ │ └── ComplexData.ts # 业务回调(buildData/changeData/...)+ 模块代理方法
│ ├── core/ # 业务数据类
│ │ ├── ComplexList.ts # 列表页主模型
│ │ ├── ComplexInfo.ts # 详情页模型
│ │ ├── SelectData.ts # 选择器数据模型
│ │ └── TrackData.ts # 轨迹回放模型(独立模块)
│ ├── module/ # 可复用功能模块
│ │ ├── ModuleData.ts # 模块容器
│ │ ├── DictionaryData.ts # 字典模块(必填)
│ │ ├── SearchData.ts # 检索模块
│ │ ├── PaginationData.ts# 分页模块
│ │ ├── ChoiceData.ts # 多选模块
│ │ ├── SortData.ts # 排序模块
│ │ ├── UpdateData.ts # 定时刷新模块
│ │ ├── StatusData.ts # 状态机模块
│ │ ├── DependData.ts # 依赖加载模块
│ │ ├── PromiseData.ts # Promise 缓存模块
│ │ └── ResetData.ts # reset/destroy 基类
│ ├── lib/ # 值对象与工具类
│ │ ├── DictionaryValue.ts# 字典项容器
│ │ ├── ArrayValue.ts # 可观测数组
│ │ ├── SelectValue.ts # 选择器数据源
│ │ ├── CascaderValue.ts # 级联选择器
│ │ ├── FormValue.ts # 表单校验桥接
│ │ ├── StorageValue.ts # 持久化缓存
│ │ ├── ForceValue.ts # 强制加载配置
│ │ ├── InterfaceValue.ts # 多场景接口值
│ │ ├── AttrsValue.ts # 属性合并
│ │ ├── TipValue.ts # 提示文本
│ │ ├── FileValue.ts # 文件数据
│ │ ├── GridParse.ts # 栅格解析
│ │ └── LayoutParse.ts # 布局解析
│ ├── dictionary/ # 字典项实现(24 个)
│ │ ├── DefaultMod.ts # 字典项基类
│ │ ├── DefaultList.ts, DefaultInfo.ts, DefaultEdit.ts
│ │ ├── InputEdit.ts, TextAreaEdit.ts, InputNumberEdit.ts
│ │ ├── SelectEdit.ts, DefaultSelectEdit.ts, SwitchEdit.ts
│ │ ├── DateEdit.ts, DateRangeEdit.ts, SimpleDateEdit.ts
│ │ ├── FileEdit.ts, ButtonEdit.ts, ButtonGroupEdit.ts
│ │ ├── ContentEdit.ts, CustomEdit.ts, CustomLoadEdit.ts
│ │ ├── FormEdit.ts, ListEdit.ts
│ │ ├── DefaultSimpleEdit.ts, DefaultLoadEdit.ts
│ │ └── ObserveList.ts
│ ├── locale/ # 国际化(zh/en)
│ └── types/ # 类型定义
└── README.md
```
## 核心概念
- `ComplexList`:列表页主数据模型,维护 `$list`、加载状态、搜索、分页、选择、字典和增删改查方法。
- `ComplexInfo`:详情页数据模型,维护 `$info`,常用于详情弹窗或详情页。
- `module.dictionary`:字段字典,统一描述表格列、详情字段、编辑表单、创建表单和搜索字段。
- `module.search`:搜索区配置,包含搜索字段和顶部菜单按钮。
- `module.pagination`:分页模块,提供 `getPage()`、`getPageSize()`、`setPageCount()` 等能力。
- `SelectValue` / `SelectEdit`:选择器数据源与选择器字典项,支持静态选项和远程加载。
- `ButtonEdit`:按钮字典项,常用于搜索区菜单、自定义操作和上传入口。
## 数据类继承链

@@ -88,15 +43,15 @@

Data (utils._Data)
└─ SimpleData # $extra
└─ DefaultData # $prop / $life / $storage
└─ BaseData # $status / $promise / $depend / $module / loadData
└─ ComplexData # 业务回调(getData/buildData/...)+ 模块代理方法
├─ ComplexList # $list
└─ ComplexInfo # $info
└─ SimpleData # $extra 额外数据容器
└─ DefaultData # $prop / $life 生命周期 / $storage
└─ BaseData # $status / $promise / $depend / $module / loadData / reset / destroy
└─ ComplexData # 业务回调(getData/buildData/changeData/...) + 模块代理方法
├─ ComplexList # $list 列表页主模型
└─ ComplexInfo # $info 详情页模型
```
每一层只关心自己的关注点,子类通过 `_triggerCreateLife('ClassName', false/true, initOption)` 触发对应的 `beforeCreate`/`created` 生命周期。
每一层通过 `_triggerCreateLife('ClassName', false/true, initOption)` 触发 `beforeCreate`/`created` 生命周期。
## 模块系统
`ComplexList` 通过 `module` 挂载功能模块,每个模块独立管理一类能力:
`ComplexList` 通过 `module` 挂载功能模块:

@@ -115,41 +70,6 @@ | 模块 | 类 | 作用 | 必填 |

模块通过 `installData` / `uninstallData` 安装卸载,触发 `_install` / `_uninstall` 钩子。模块间通过 `ModuleData.getData(modName)` 互相查询。
## 快速开始:标准列表页
## 生命周期
下面示例提炼自真实业务页面 `src/pages/index/views/list/base/index.vue`。
`ComplexList` 内置 `Life` 系统,常用生命周期:
| 名称 | 触发时机 |
|---|---|
| `beforeCreate` / `created` | 实例构造前后 |
| `beforeLoad` | `loadData` 开始 |
| `loaded` | 加载成功 |
| `loadFail` | 加载失败 |
| `beforeReload` / `reloaded` / `reloadFail` | `reloadData` 各阶段 |
| `beforeSearch` | `setSearch` 校验前 |
| `searched` / `searchFail` | 检索成功/失败 |
| `beforeUpdate` / `updated` / `updateFail` | 更新数据各阶段 |
| `beforeReset` / `reseted` | `reset` 前后 |
| `beforeDestroy` / `destroyed` | `destroy` 前后 |
| `dataChange` | 业务方法成功后(buildData/changeData/deleteData/...)|
| `parentChange` | `$setParent` 时 |
监听方式:
```ts
listData.onLife('loaded', {
id: 'myHandler',
handler: (lifeValue, res) => {
console.log('加载完成', res)
}
})
listData.offLife('loaded', 'myHandler')
```
`onLoaded(handler)` 是 `loaded` 的快捷监听,若当前已加载完成会立即触发。
## 基础列表用法
下面示例提炼自真实业务页面 `src/pages/index/views/list/base/index.vue`,展示标准模式:页面创建 `ComplexList`,配置搜索、字典、分页和业务接口,然后传给 `ComplexQuickList` 渲染。
```vue

@@ -171,4 +91,4 @@ <template>

import { defineComponent } from 'vue'
import { ButtonEdit, ComplexList, SelectEdit, SelectValue, type fileDataType } from '@complex-suite/data'
import { ComplexQuickList, icon, type componentsProps, type tablePayload } from '@complex-suite/component-antd'
import { ButtonEdit, ComplexList, SelectEdit, SelectValue } from '@complex-suite/data'
import { ComplexQuickList, type componentsProps, type tablePayload } from '@complex-suite/component-antd'
import listApi from '@/api/listApi'

@@ -193,12 +113,13 @@

module: {
// 搜索区
// 搜索区:菜单 + 搜索字段
search: {
menu: {
list: [
'build', // 内置:新增按钮
'delete', // 内置:批量删除按钮
new ButtonEdit({ // 自定义按钮
'build', // 内置:新增按钮
'delete', // 内置:批量删除按钮
new ButtonEdit({ // 自定义按钮
prop: 'choice2',
type: 'button',
option: {
type: 'default',
name: () => '已选择2条时可用',

@@ -209,13 +130,13 @@ disabled(payload: any) {

click() {
return Promise.resolve({})
return new Promise((resolve) => setTimeout(() => resolve({}), 2000))
}
}
}),
new ButtonEdit({ // 文件上传按钮
new ButtonEdit({ // 文件上传按钮
prop: '$import',
type: 'button',
option: {
type: 'default',
name: () => '导入',
icon: () => icon.local('emptyImage', { size: 16 }),
upload: () => Promise.resolve({})
upload: () => new Promise((resolve) => setTimeout(() => resolve({}), 3000))
}

@@ -230,8 +151,3 @@ })

mod: {
search: {
$format: 'edit',
type: 'input',
collapse: 2,
option: { size: 20 }
}
search: { $format: 'edit', type: 'input', collapse: 2, option: { size: 20 } }
}

@@ -246,5 +162,5 @@ },

type: 'select',
cascader: undefined,
cascader: undefined, // 显式声明无级联
width: 100,
reload: true,
reload: true, // 值变化时重新加载选项
search: {},

@@ -267,3 +183,3 @@ // 远程加载选项

},
// 字典:字段定义(同时服务 list/info/edit/build/change)
// 字典:字段定义(同时服务 list/info/edit/build/change)
dictionary: {

@@ -274,8 +190,4 @@ propData: { id: 'id' },

{ prop: '$index', name: 'No', originFrom: 'local', mod: { list: { width: 60 } } },
{ prop: 'id', name: 'ID', mod: { list: { width: 80 }, info: {} } },
{
prop: 'id',
name: 'ID',
mod: { list: { width: 80 }, info: {} }
},
{
prop: 'input',

@@ -294,3 +206,3 @@ name: '输入框',

name: '开关',
assign(value) { return value === 1 ? true : false },
assign(value) { return value === 1 },
collect(value) { return value ? 1 : 0 },

@@ -332,13 +244,2 @@ mod: {

{
prop: 'multipleFile',
name: '多文件',
assign(value) { return value ? (value as string).split(',') : [] },
collect(value) { return value ? (value as string[]).join(',') : '' },
mod: {
edit: { type: 'file', multiple: true, required: true },
build: { $redirect: 'edit' },
change: { $redirect: 'edit' }
}
},
{
prop: 'timeRange',

@@ -370,3 +271,3 @@ name: '时间范围',

},
// 业务回调:均要求返回 Promise
// 业务回调:均要求返回 Promise,this 绑定到当前实例
getData(this: ComplexList) {

@@ -405,5 +306,3 @@ return new Promise((resolve, reject) => {

const index = this.$list.indexOf(targetData)
if (index > -1) {
this.$list.splice(index, 1)
}
if (index > -1) this.$list.splice(index, 1)
resolve({})

@@ -416,5 +315,3 @@ })

const index = this.$list.indexOf(item)
if (index > -1) {
this.$list.splice(index, 1)
}
if (index > -1) this.$list.splice(index, 1)
})

@@ -456,25 +353,23 @@ resolve({})

`module.dictionary.list` 是列表页的核心协议。一个字段可同时服务多个场景:
`module.dictionary.list` 是核心协议。一个字段可同时服务多个场景:
```ts
{
prop: 'name', // 字段标识
name: '名称', // 显示名
originFrom: 'local', // 数据来源:local / search / data
showProp: { // 不同场景下展示的字段
prop: 'name', // 字段标识
name: '名称', // 显示名
originFrom: 'local', // 数据来源:local / search / data
showProp: { // 不同场景下展示的字段
default: 'value',
list: 'label'
},
assign(value) { ... }, // 接口值进入展示/表单前的转换
collect(value) { ... }, // 表单值提交前的转换
dictionary: { // 嵌套字典(type: 'form'/'list' 时使用)
list: [...]
},
assign(value) { ... }, // 接口值 → 展示/表单值 转换
collect(value) { ... }, // 表单值 → 提交值 转换
dictionary: { list: [...] }, // 嵌套字典(type: 'form'/'list' 时使用)
mod: {
list: { width: 100 }, // 表格列配置
info: {}, // 详情字段配置
edit: { type: 'input', required: true }, // 通用编辑配置
build: { $redirect: 'edit' }, // 新增表单:复用 edit
change: { $redirect: 'edit' }, // 修改表单:复用 edit
search: { $format: 'edit', type: 'input' } // 搜索字段
list: { width: 100 }, // 表格列配置
info: {}, // 详情字段配置
edit: { type: 'input', required: true },// 通用编辑配置
build: { $redirect: 'edit' }, // 新增表单:复用 edit
change: { $redirect: 'edit' }, // 修改表单:复用 edit
search: { $format: 'edit', type: 'input' } // 搜索字段
}

@@ -496,3 +391,3 @@ }

| `file` | FileEdit | 文件上传 |
| `button` | ButtonEdit | 按钮(搜索区/自定义操作)|
| `button` | ButtonEdit | 按钮(搜索区/自定义操作) |
| `form` | FormEdit | 内嵌表单 |

@@ -505,4 +400,2 @@ | `list` | ListEdit | 内嵌列表 |

`module.search` 分为菜单和搜索字段两部分:
```ts

@@ -512,5 +405,5 @@ search: {

list: [
'build', // 内置:新增
'delete', // 内置:批量删除
new ButtonEdit({ ... }) // 自定义按钮
'build', // 内置:新增
'delete', // 内置:批量删除
new ButtonEdit({ ... }) // 自定义按钮
]

@@ -523,7 +416,3 @@ },

mod: {
search: {
$format: 'edit',
type: 'input',
collapse: 2 // 折叠阈值(字段数 > 2 时折叠)
}
search: { $format: 'edit', type: 'input', collapse: 2 }
}

@@ -535,111 +424,24 @@ }

`ComplexQuickList` 已内置处理这些菜单:
`ComplexQuickList` 内置菜单 prop:
| prop | 行为 |
|---|---|
| `$search` | 调用 `listData.setSearch()`,更新搜索条件并触发列表加载 |
| `$reset` | 调用 `listData.resetSearch()`,重置搜索条件 |
| `$search` | 调用 `listData.setSearch()` |
| `$reset` | 调用 `listData.resetSearch()` |
| `$refresh` | 调用 `listData.reloadData({ data: true, sync: true })` |
| `$build` | 打开新增弹窗 |
| `$delete` | 触发批量删除(基于 choice)|
| `$delete` | 触发批量删除(基于 choice) |
| `$info` | 打开详情弹窗 |
| `$export` | 调用 `listData.triggerMethod('exportData')` |
| `$import` / `$image` | 文件上传按钮(通过 `upload` 回调)|
| `$import` / `$image` | 文件上传按钮(通过 `upload` 回调) |
`ButtonEdit` 区分两种语义:
- `click()`:普通点击回调,返回 Promise
- `upload(file: File)`:文件上传专用回调,由组件触发文件选择 → 拿到 File → 调用 upload
`ButtonEdit` 区分两种语义:
- `click()`:普通点击回调
- `upload(file: File)`:文件上传专用回调
## 远程选择器
## 数据加载与业务回调
选择器可以使用静态 `SelectValue`,也可以在 `SelectEdit.getData` 中远程加载选项:
所有业务方法都要求返回 `Promise`,`this` 绑定到当前实例:
```ts
{
prop: 'search',
name: '检索框',
mod: {
search: {
$format: 'edit',
type: 'select',
cascader: undefined, // 显式声明无级联
width: 100,
reload: true, // 搜索框值变化时重新加载
search: { // SelectValue 配置
limit: 3, // 最多保留 3 个历史选项
limitContent: '限制3个'
},
getData(this: SelectEdit) {
return new Promise((resolve) => {
this.$select.setList([
{ value: this.$search?.value, label: this.$search?.value || '' },
{ value: 1, label: '1' },
{ value: 2, label: '2' }
])
resolve({})
})
}
}
}
}
```
如果远程接口有分页结果,可在 `getData` 中同时调用 `this.$pagination.setCount(total)`。
## 数据加载与提交
`ComplexList` 的业务方法都要求返回 `Promise`,这样状态模块才能正确维护 `load`、`operate` 等状态。
```ts
getData(this: ComplexList) {
return new Promise((resolve, reject) => {
const postData = {
...this.getSearch(), // 搜索条件
page: this.getPage(), // 当前页码
size: this.getPageSize() // 每页数量
}
api.list(postData).then(res => {
this.formatList(res.list, res.total) // 写入 $list 和分页总数
resolve(res)
}).catch(reject)
})
}
```
常用方法:
| 方法 | 作用 |
|---|---|
| `loadData(force?, ...args)` | 加载数据,force 控制是否强制 |
| `reloadData(force?, ...args)` | 重新加载,触发 beforeReload/reloaded/reloadFail |
| `formatList(list, total)` | 写入 $list,同步分页总数 |
| `getSearch()` | 获取搜索区当前值 |
| `getPage()` / `getPageSize()` | 获取当前分页参数 |
| `setPage(page)` / `setPageSize(size)` | 修改分页参数 |
| `triggerMethod(method, args, option)` | 通过状态机触发业务方法 |
| `triggerMethodWithOperateAndStatus(method, args, statusOption, operateOption)` | 触发并联动目标 status + operate |
| `setSort(prop, order)` | 设置排序 |
| `setChoice(data)` / `resetChoice()` | 多选操作 |
`force` 参数支持多种形式:
```ts
listData.loadData(true) // 强制重新加载
listData.loadData(false) // 仅在未加载或失败时加载
listData.loadData({ data: true, ing: true }) // 强制数据 + 强制进行中
listData.loadData({ // 完整配置
data: true, // 强制数据加载
ing: true, // 强制进行中状态
promise: true, // 强制触发 promise
sync: true, // 等待 promise 完成
trigger: { from: 'search', action: 'set' },
module: { pagination: true } // 同时重置某些模块
})
```
## 业务回调
`ComplexList` 支持在初始化配置中定义业务回调,`this` 绑定到当前实例:
```ts
interface ComplexDataInitOption {

@@ -660,33 +462,39 @@ module: ModuleDataInitOption

回调内可直接访问 `this.$list`、`this.reloadData()`、`this.getSearch()` 等。`buildData`/`changeData`/`deleteData` 等修改类方法成功后会自动触发 `dataChange` 生命周期。
回调内可直接访问 `this.$list`、`this.reloadData()`、`this.getSearch()` 等。修改类方法(buildData/changeData/deleteData)成功后会自动触发 `dataChange` 生命周期。
## 表格菜单
### loadData 的 force 参数
业务页面通常通过 `componentsProps.table.menu.menu` 配置表格行操作:
```ts
componentsProps: {
table: {
menu: {
menu: [
{ prop: '$change', name: () => '编辑', color: 'link' },
{ prop: '$delete', name: () => '删除', color: 'danger' },
{ prop: '$info', name: () => '详情', color: 'link' }
]
}
}
}
listData.loadData(true) // 强制重新加载
listData.loadData(false) // 仅在未加载或失败时加载
listData.loadData({ data: true, ing: true }) // 强制数据 + 强制进行中状态
listData.loadData({ // 完整配置
data: true, // 强制数据加载
ing: true, // 强制进行中状态
promise: true, // 强制触发 promise
sync: true, // 等待 promise 完成
trigger: { from: 'search', action: 'set' },
module: { pagination: true }
})
```
`ComplexQuickList` 默认行为:
### 常用方法
| prop | 行为 |
| 方法 | 作用 |
|---|---|
| `$change` | 打开编辑弹窗,使用当前行作为原始数据 |
| `$delete` | 确认后调用 `listData.triggerMethod('deleteData', [targetData])` |
| `$info` | 打开详情弹窗 |
| `loadData(force?, ...args)` | 加载数据 |
| `reloadData(force?, ...args)` | 重新加载,触发 beforeReload/reloaded/reloadFail |
| `formatList(list, total)` | 写入 $list,同步分页总数 |
| `getSearch()` | 获取搜索区当前值 |
| `getPage()` / `getPageSize()` | 获取当前分页参数 |
| `setPage(page)` / `setPageSize(size)` | 修改分页参数 |
| `setSearch(action?)` | 校验搜索区并触发 reload |
| `resetSearch(option?)` | 重置搜索条件并触发 setSearch('reset') |
| `triggerMethod(method, args, option)` | 通过状态机触发业务方法 |
| `setSort(prop, order)` | 设置排序 |
| `setChoice(data)` / `resetChoice()` | 多选操作 |
## 字段转换:assign 与 collect
## 字段转换:assign 与 collect
接口值与表单值往往存在差异,字典字段提供了两个转换点:
接口值与表单值往往存在差异,字典字段提供两个转换点:

@@ -696,34 +504,16 @@ ```ts

prop: 'switch',
// 接口值(number)→ 展示/表单值(boolean)
assign(value) {
return value === 1 ? true : false
},
// 表单值(boolean)→ 提交值(number)
collect(value) {
return value ? 1 : 0
}
// 接口值(number)→ 展示/表单值(boolean)
assign(value) { return value === 1 },
// 表单值(boolean)→ 提交值(number)
collect(value) { return value ? 1 : 0 }
}
```
- `assign`:在 `getData` 成功后、数据进入 `$list`/`$info`/表单前调用
- `collect`:在 `buildData`/`changeData` 提交前调用
- `assign`:在 `getData` 成功后、数据进入 `$list`/`$info`/表单前调用
- `collect`:在 `buildData`/`changeData` 提交前调用
对于多文件等场景,可结合 `isFile` 判断:
## 嵌套字典(FormEdit / ListEdit)
```ts
{
prop: 'multipleFile',
assign(value) { return value ? (value as string).split(',') : [] },
collect(value) {
return (value as any[])
.map(item => isFile(item) ? item.name : item)
.join(',')
}
}
```
字段可嵌套完整字典,用于内嵌表单/列表:
## 嵌套字典(FormEdit / ListEdit)
字段可嵌套完整字典,用于复杂场景(如内嵌表单/列表):
```ts

@@ -733,4 +523,2 @@ {

name: '内嵌表单',
assign(value) { return value || { name: 'a', date: '2019-05-18' } },
collect(value) { return value },
dictionary: {

@@ -742,4 +530,2 @@ list: [

mod: {
list: { width: 120 },
info: { $redirect: 'edit' },
edit: { type: 'input', required: true },

@@ -774,16 +560,75 @@ build: { $redirect: 'edit' },

## 级联列表
## 生命周期
级联列表通常使用 `ComplexQuickCascade`。主列表通过表格菜单暴露 `$sublist`,点击后打开子列表弹窗,并由子列表自己的 `ComplexList` 加载数据。
```ts
listData.onLife('loaded', {
id: 'myHandler',
handler: (lifeValue, res) => {
console.log('加载完成', res)
}
})
listData.offLife('loaded', 'myHandler')
推荐模式:
// onLoaded 是 loaded 的快捷监听,若当前已加载完成会立即触发
listData.onLoaded({
handler: (lifeValue) => { /* ... */ }
})
```
常用生命周期:
| 名称 | 触发时机 |
|---|---|
| `beforeCreate` / `created` | 实例构造前后 |
| `beforeLoad` | `loadData` 开始 |
| `loaded` | 加载成功 |
| `loadFail` | 加载失败 |
| `beforeReload` / `reloaded` / `reloadFail` | `reloadData` 各阶段 |
| `beforeSearch` | `setSearch` 校验前 |
| `searched` / `searchFail` | 检索成功/失败 |
| `beforeUpdate` / `updated` / `updateFail` | 更新数据各阶段 |
| `beforeReset` / `reseted` | `reset` 前后 |
| `beforeDestroy` / `destroyed` | `destroy` 前后 |
| `dataChange` | 业务方法成功后(buildData/changeData/deleteData/...) |
| `parentChange` | `$setParent` 时 |
## 远程选择器
选择器可使用静态 `SelectValue`,也可在 `SelectEdit.getData` 中远程加载:
```ts
{
prop: 'search',
mod: {
search: {
$format: 'edit',
type: 'select',
cascader: undefined,
reload: true,
search: { limit: 3, limitContent: '限制3个' },
getData(this: SelectEdit) {
return new Promise((resolve) => {
this.$select.setList([
{ value: this.$search?.value, label: this.$search?.value || '' },
{ value: 1, label: '1' }
])
resolve({})
})
}
}
}
}
```
远程接口有分页时,在 `getData` 中同步调用 `this.$pagination.setCount(total)`。
## 级联列表
主列表通过表格菜单暴露 `$sublist`,点击打开子列表弹窗,子列表用自己的 `ComplexList` 加载:
```ts
// 父列表
const parentData = new ComplexList({
prop: 'parentData',
module: {
dictionary: { list: [...] },
pagination: true
},
module: { dictionary: { list: [...] }, pagination: true },
getData(this: ComplexList) {

@@ -806,6 +651,3 @@ return new Promise((resolve, reject) => {

prop: 'childData',
module: {
dictionary: { list: [...] },
pagination: true
},
module: { dictionary: { list: [...] }, pagination: true },
getData(this: ComplexList) {

@@ -833,46 +675,6 @@ const parentId = this.getExtra('parentId')

`$extra` 是不参与字典/搜索/分页的额外数据容器,通过 `setExtra` / `getExtra` / `clearExtra` 操作。
`$extra` 是不参与字典/搜索/分页的额外数据容器,通过 `setExtra` / `getExtra` / `clearExtra` 操作。
## 全局配置与国际化
```ts
import { dataConfig, dataLocale } from '@complex-suite/data'
// 读取主题色
const primary = dataConfig.style.color.primary // '#1677ff'
// 国际化
dataLocale.use('en') // 切换语言
dataLocale.t('baseData.methodNotExist', { method: 'foo' }) // 翻译
```
可注入自定义消息:
```ts
import { dataLocale, type DataMessages } from '@complex-suite/data'
const customMessages: Partial<DataMessages> = {
zh: { baseData: { methodNotExist: '方法 {method} 不存在' } }
}
dataLocale.inject(customMessages)
```
## 重置与销毁
```ts
// 重置(不清除生命周期、storage)
listData.reset() // 全部重置
listData.reset({ status: false, pagination: true }) // 选择性重置
// 销毁(彻底清理)
listData.destroy() // 全部销毁
listData.destroy({ life: true, depend: true }) // 同时销毁生命周期和依赖
```
`resetOption` 通过 `parseResetOption(option, prop)` 解析:`true` / `false` / `undefined` / `{ [prop]: boolean }`。
## TypeScript 泛型
`ComplexList` 支持泛型约束列表项类型:
```ts

@@ -894,7 +696,4 @@ interface UserItem { id: number; name: string }

listData.$list // 类型为 UserItem[]
```
`ComplexInfo<D>` 类似,用于详情页:
```ts
// 详情页
const infoData = new ComplexInfo<UserVO>({

@@ -910,3 +709,2 @@ prop: 'userInfo',

})
infoData.$info // 类型为 Partial<UserVO>

@@ -923,7 +721,7 @@ ```

list: [{ value: 1, label: 'A' }, { value: 2, label: 'B' }],
equal: (a, b) => a.value === b.value, // 自定义相等判断
hidden: true // 启用隐藏能力
equal: (a, b) => a.value === b.value,
hidden: true
})
// 级联选择器(cascader 指定子字段名)
// 级联选择器(cascader 指定子字段名)
const cascader = new CascaderValue({

@@ -943,17 +741,50 @@ list: [

## 重置与销毁
```ts
// 重置(不清除生命周期、storage)
listData.reset() // 全部重置
listData.reset({ status: false, pagination: true }) // 选择性重置
// 销毁(彻底清理)
listData.destroy() // 全部销毁
listData.destroy({ life: true, depend: true }) // 同时销毁生命周期和依赖
```
`resetOption` 通过 `parseResetOption(option, prop)` 解析,支持 `true` / `false` / `undefined` / `{ [prop]: boolean }`。
## 全局配置与国际化
```ts
import { dataConfig, dataLocale, type DataMessages } from '@complex-suite/data'
// 读取主题色
const primary = dataConfig.style.color.primary // '#1677ff'
// 国际化
dataLocale.use('en')
dataLocale.t('baseData.methodNotExist', { method: 'foo' })
// 注入自定义消息
const customMessages: Partial<DataMessages> = {
zh: { baseData: { methodNotExist: '方法 {method} 不存在' } }
}
dataLocale.inject(customMessages)
```
## 推荐实践
- 列表页优先用 `ComplexList` 作为唯一业务状态入口,UI 组件只消费它,不重复维护列表、分页和搜索状态。
- 字段配置优先集中在 `module.dictionary.list`,通过 `list`、`info`、`edit`、`build`、`change` 复用同一字段语义。
- 所有业务回调都返回 `Promise`,包括 `getData`、`buildData`、`changeData`、`deleteData`、自定义按钮 `click` 和上传 `upload`。
- 接口返回值进入表单前使用 `assign`,提交前使用 `collect`,避免在 UI 组件中散落转换逻辑。
- 新增和修改表单大多可通过 `$redirect: 'edit'` 复用配置,只在字段行为确实不同的时候单独覆盖。
- 远程加载选择器时,在 `getData` 中同步调用 `this.$pagination.setCount(total)` 让分页模块拿到总数。
- 使用 `onLife` / `offLife` 监听关键生命周期(`loaded`、`dataChange`、`beforeReset`),避免在组件内用 setTimeout 等待异步。
- `force` 参数优先用对象形式(`{ data: true, ing: true }`),可读性优于布尔简写。
- 使用 `component-antd` 时,`data` 只负责无头数据协议,渲染细节交给 `ComplexQuickList`、`TableView`、`EditArea`、`InfoArea`。
- 列表页优先用 `ComplexList` 作为唯一业务状态入口,UI 组件只消费它
- 字段配置集中在 `module.dictionary.list`,通过 `list`/`info`/`edit`/`build`/`change` 复用同一字段语义
- 所有业务回调返回 `Promise`,包括 `getData`/`buildData`/`changeData`/`deleteData`/自定义 `click`/上传 `upload`
- 接口值进入表单前用 `assign`,提交前用 `collect`,避免转换逻辑散落在 UI 组件
- 新增和修改表单大多可通过 `$redirect: 'edit'` 复用配置
- 远程选择器在 `getData` 中同步调用 `this.$pagination.setCount(total)`
- 用 `onLife`/`offLife` 监听关键生命周期,避免 setTimeout 等待异步
- `force` 参数优先用对象形式(`{ data: true, ing: true }`),可读性优于布尔简写
- `data` 只负责无头数据协议,渲染细节交给 `component-antd` 的 `ComplexQuickList`/`TableView`/`EditArea`/`InfoArea`
## 依赖
- `@complex-suite/utils`:核心工具函数和基类(Life、Watcher、setProp 等)。
- `@complex-suite/plugin`:布局和通知系统。
- `@complex-suite/utils`:核心工具函数和基类(Life、Watcher、setProp 等)
- `@complex-suite/plugin`:布局和通知系统

@@ -57,7 +57,7 @@ import { isArray } from '@complex-suite/utils'

export type SimpleDateEditOption<R extends Boolean = false> = BaseSimpleDateEditOption & (R extends true ? RangeSimpleDateEditOption : {})
export type SimpleDateEditOption<R extends boolean = false> = BaseSimpleDateEditOption & (R extends true ? RangeSimpleDateEditOption : {})
export type PartialSimpleDateEditOption<R extends Boolean = false> = PartialBaseSimpleDateEditOption & (R extends true ? RangeSimpleDateEditOption : {})
export type PartialSimpleDateEditOption<R extends boolean = false> = PartialBaseSimpleDateEditOption & (R extends true ? RangeSimpleDateEditOption : {})
export interface SimpleDateEditInitOption<R extends Boolean = false> extends DefaultEditInitOption<false> {
export interface SimpleDateEditInitOption<R extends boolean = false> extends DefaultEditInitOption<false> {
option?: PartialSimpleDateEditOption<R>

@@ -107,3 +107,3 @@ }

class SimpleDateEdit<R extends Boolean = false> extends DefaultEdit<false> {
class SimpleDateEdit<R extends boolean = false> extends DefaultEdit<false> {
static $name = 'SimpleDateEdit'

@@ -110,0 +110,0 @@ static $width = undefined

+286
-129
# @complex-suite/data 开发待办
> 本文档记录 data 包的功能待办、设计决策与已放弃方案。
> 严重度:🔴 高 / 🟡 中 / 🟢 低

@@ -25,4 +26,6 @@ ## Abandon

## ToDo
## 长期方向
> 以下为无明确验收标准的长期优化方向,与下方有具体方案的问题清单区分。
- 优化全局类型

@@ -35,21 +38,79 @@ - 实现List表单

---
<!-- ===== 第二阶段:已识别但延后审核的待办项 ===== -->
## 延后审核(2026-07-06 识别)
## 已完成
### v5.0.14 已完成(2026-07-06)
| 编号 | 严重度 | 问题摘要 | 处理方式 |
| --- | --- | --- | --- |
| T5 | 🟡 中 | [SimpleDateEdit.ts#L57,59,61,106](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/SimpleDateEdit.ts#L57) `R extends Boolean`(大写包装类型)4 处 | 改为 `R extends boolean`。`Boolean` 是对象包装类型包含 `new Boolean(true)` 实例,`boolean` 是原始类型。TS 中 `extends Boolean` 虽功能上接受原始 boolean,但属代码异味 |
| - | 🟢 | README.md 重写 | 重写为快速使用文档,含实际业务示例 |
| - | 🟢 | ToDo.md 结构优化 | 新增 C1、R3 待审核项,合并重复区,调整严重度 |
### v5.0.10 修复(历史记录,已合并到 v5.0.14 发版)
### v5.0.9 已完成(2026-07-03)
| 编号 | 严重度 | 问题摘要 | 处理方式 |
| --- | --- | --- | --- |
| P1-1 | 🔴 | `_syncData` 空实现被 22 处调用 | 加 TSDoc 注释说明设计意图:Vue 环境空操作,非响应式环境需重写 |
| P1-2 | 🔴 | `dictionray` 拼写全局扩散 | DictionaryValue.ts 3 处 + list/base 1 处 + list/cascade 4 处 + README 全部改为 `dictionary` |
| P1-3 | 🔴 | ArrayValue 误导性「严重 Bug」注释 | 删除错误 `@warning` 注释 |
| P1-4 | 🔴 | `triggerMethodWithOperateAndStatus` 元调用 | 加注释说明集中化设计意图 + 反射复用原因 |
| P2-1 | 🟡 | `$assignData` / `$formatData` 80% 重复 | 抽取 `_buildDataValue`,6 个路径逐一验证与原始行为一致;保证性能(payload 仅 assign 时创建、originProp 仅调一次) |
| P2-3 | 🟢 | `destroy(force?)` 样板代码 | ResetData 加默认实现,ChoiceData/SortData 删除重复 |
| P3-2 | 🟢 | index.ts 混排导出 | 按 7 组分组重排 + 注释分区,导出项 100% 保留 |
| P4-1 | 🟢 | InterfaceValue 双重断言 | 调整判断顺序,用 typeof 类型守卫自动收窄 |
| P4-2 | 🟢 | LayoutParse 冗余断言 | `undefined as undefined | T` → `undefined` |
| P4-5 | 🟢 | StorageValue.init 返回值语义 | 补 TSDoc:true=缓存有效已加载 / false=缓存无效需重新加载 |
| P5-1 | 🟡 | list/base `dictionray` 拼写 | 随 P1-2 一并修改 |
| P5-2 | 🟡 | list/cascade `dictionray` 拼写 | 随 P1-2 一并修改 |
| P6-1 | 🟢 | README 旧示例错误 | 重写「快速开始」 |
| P6-2 | 🟢 | README 架构描述不准 | 重写「架构概览」 |
**版本记录**:所有子包升级至 v5.0.9(2026-07-03)
- `@complex-suite/data`: 5.0.8 → 5.0.9
- `@complex-suite/utils`: 3.0.8 → 3.0.9
- `@complex-suite/plugin`: 5.0.8 → 5.0.9
- `@complex-suite/component`: 5.0.8 → 5.0.9
- `@complex-suite/component-antd`: 5.0.8 → 5.0.9
- `@complex-suite/request`: 2.0.8 → 2.0.9
- `@complex-suite/request-axios`: 2.0.8 → 2.0.9
---
## 待审核
> 以下问题已识别但暂不修改,记录到此等待后续审核。
> 审阅方式:回复 `编号 + 处理意见`(如「D1 ✅ 删除」「R3 方案A」等),多条可批量回复。
### R1 — ComplexList/ComplexInfo/ComplexData `_triggerCreateLife` 前后夹击模式
### 死代码
- **位置**:[ComplexList.ts#L10-22](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/core/ComplexList.ts#L10)、[ComplexInfo.ts#L10-22](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/core/ComplexInfo.ts#L10)、[ComplexData.ts#L62,68](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/data/ComplexData.ts#L62)
- **问题**:`_triggerCreateLife('XXX', false, ...)` + 业务初始化 + `_triggerCreateLife('XXX', true, ...)` 前后夹击模式在继承链中重复 3 次。
- **建议方案**:在 BaseData 中封装 `_wrapCreateLife(name, initOption, fn)` 模板方法。
- **结果影响**:消除 3 处重复的 beforeCreate/created 生命周期间隔调用,统一初始化流程。
- **严重度**:🔴 高
#### D1 — OrderData 整文件死代码
- **位置**:[OrderData.ts](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/OrderData.ts) 全文件
- **问题**:`OrderData` 实现完整但**未注册**到 `ModuleData.ModuleMap`(仅含 update/pagination/dictionary/search/choice/sort),`ModuleDataKeys` 也不含 `order`。全代码库 Grep 确认:`OrderData` 仅在自身文件、`types/module.ts`(类型注释)、`ToDo.md` 出现,**无任何 import**。其 `_install` L47 检查 `force.module.order === true`,但 `ForceValue.module` 类型([ForceValue.ts#L9-14](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/ForceValue.ts#L9))中无 `order` 字段,即使被调用也永远为 falsy。
- **额外问题**:`OrderData` 中的 `orderType`(L9,含 `undefined`)与 `SortData` 中的 `orderType`(L4,不含 `undefined`)同名但含义不同,形成命名冲突。`index.ts` 导出的是 SortData 版本。
- **建议方案**:等待查找这个函数为什么存在。若确认废弃则删除整个文件;若需要则加入 ModuleMap。
- **结果影响**:🔴 高(死代码占用维护心智成本,且 `orderType` 命名冲突会误导维护者)。
- **状态**:⏳ 等待查找函数存在原因
---
### 重复代码
#### R1 — `_triggerCreateLife` 前后夹击模式
- **位置**:[ComplexList.ts#L10-22](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/core/ComplexList.ts#L10)、[ComplexInfo.ts#L10-22](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/core/ComplexInfo.ts#L10)、[ComplexData.ts#L62,68](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/data/ComplexData.ts#L62)、[BaseData.ts#L67,103](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/data/BaseData.ts#L67)、[DefaultData.ts#L31,44](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/data/DefaultData.ts#L31)、module 下 4 个文件(UpdateData/DictionaryData/SearchData/PaginationData)
- **问题**:`_triggerCreateLife('XXX', false, ...)` + 业务初始化 + `_triggerCreateLife('XXX', true, ...)` 前后夹击模式在继承链 + 模块系统中重复 10+ 处。且 `created` 阶段传参不一致(有的传 initOption,有的不传),消费方拿到的 args 长度不同。`env` 形参为 `string` 无类型约束,类名重构不会报错。
- **核实结论**:属生命周期框架固有模式(类似 Vue beforeCreate/created),重复是事实但非 bug。严重度从 🔴 高降为 🟡 中。
- **建议方案**:在 BaseData 中封装 `_wrapCreateLife(name, initOption, fn)` 模板方法;`env` 参数改为字面量联合类型。
- **结果影响**:消除 10+ 处重复调用,统一 created 阶段参数。
- **严重度**:🟡 中
- **状态**:⏳ 待审核
### R2 — ArrayValue hide/freeze、show/thaw、pop/shift 三组方法重复
#### R2 — ArrayValue hide/freeze、show/unfreeze、pop/shift 三组方法重复
- **位置**:[ArrayValue.ts#L174-181,200-207,186-195,212-221,96-107,112-123](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/ArrayValue.ts#L174)
- **问题**:`hide`/`freeze`、`show`/`unfreeze`、`pop`/`shift` 三组方法各对几乎完全相同,仅 `$hidden`/`$frozen`、`pop`/`shift` 不同。
- **位置**:[ArrayValue.ts#L174-221,96-123](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/ArrayValue.ts#L174)
- **问题**:`hide`/`freeze`、`show`/`unfreeze`、`pop`/`shift` 三组方法各对几乎完全相同,仅目标 Map(`$hidden`/`$frozen`)或取出方式不同。
- **建议方案**:抽取 `_toggleToMap(prop, targetMap, statusCheck)` 和 `_removeFromItem(value)` 公共方法。

@@ -60,21 +121,75 @@ - **结果影响**:ArrayValue 类可减少约 60 行重复代码,三组方法只需约 20 行委派代码。

### R3 — `destroy(option?)` 在 5 个类中完全重复
#### R3 — `destroy(option?)` 在 5 个模块类中完全重复
- **位置**:[ResetData.ts#L84-88](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/ResetData.ts#L84)、[PromiseData.ts#L86-90](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/PromiseData.ts#L86)、[StatusData.ts#L279-283](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/StatusData.ts#L279)、[UpdateData.ts#L227-231](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/UpdateData.ts#L227)、[PaginationData.ts#L194-198](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/PaginationData.ts#L194)
- **问题**:`destroy(option?) { if (option !== false) { this.reset(option) } }` 在 5 个类中完全重复,ResetData 已抽象 `destroy` 但子类仍各自实现。
- **建议方案**:让其他模块继承 ResetData 或抽 DestroyMixin。
- **结果影响**:消除 5 处重复,统一 destroy 行为。⚠️ 需要检查各模块的继承链是否能改为继承 ResetData。
- **位置**:
- [PromiseData.ts#L86-90](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/PromiseData.ts#L86)
- [StatusData.ts#L279-283](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/StatusData.ts#L279)
- [UpdateData.ts#L227-231](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/UpdateData.ts#L227)
- [PaginationData.ts#L194-198](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/PaginationData.ts#L194)
- [SearchData.ts#L272-279](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/SearchData.ts#L272)(含额外清理 watcher 逻辑)
- **问题**:5 个模块类各自实现了 `destroy(option?) { if (option !== false) { this.reset(option) } }`,逻辑与 `ResetData.destroy`([ResetData.ts#L98-102](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/ResetData.ts#L98))的默认实现完全相同。`ResetData` 已抽象出 `reset(force?) → _doReset(force)` 模板方法 + `destroy` 默认实现,但这 5 个类未继承 `ResetData`,各自重复实现。
- **继承链分析**:
```
ResetData extends Data(abstract,有 _doReset 抽象方法 + reset/destroy 模板方法 + $resetByForce + beforeReload 注册)
├─ ChoiceData extends ResetData ✅ 已复用 destroy
└─ SortData extends ResetData ✅ 已复用 destroy
DefaultData extends SimpleData extends Data
├─ DictionaryData extends DefaultData ❌ 未继承 ResetData
│ └─ SearchData extends DictionaryData ❌ 间接未继承,且重写 destroy 加了 watcher 清理
├─ UpdateData extends DefaultData ❌ 未继承 ResetData
└─ PaginationData extends DefaultData ❌ 未继承 ResetData
Data
├─ PromiseData extends Data ❌ 未继承 ResetData
└─ StatusData extends Data ❌ 未继承 ResetData
```
- **方案对比**(参考 ResetData 的 `_doReset` 模板方法模式):
- **方案A(推荐,最小改动):在 `Data` 基类加 `destroy` 默认实现**
```ts
// Data.ts
destroy(option?: boolean) {
if (option !== false && typeof this.reset === 'function') {
this.reset(option)
}
}
```
- 5 个类删除各自 `destroy` 重写
- SearchData 保留重写:`super.destroy(option)` 后清理 watcher
- ResetData.destroy 可删除(继承自 Data)
- ✅ 改动小、风险低、不改变继承链
- ⚠️ `Data` 基类需声明 `reset?: (option?: boolean) => void` 可选方法
- **方案B(模板方法,更规范):参考 `_doReset`,加 `destroy` 模板方法 + `_doDestroy` 钩子**
```ts
// Data.ts
destroy(option?: boolean) {
if (option !== false) {
this._doDestroy(option)
}
}
protected _doDestroy(option?: boolean) {
// 默认: 若有 reset 则调用
if (typeof this.reset === 'function') {
this.reset(option)
}
}
```
- 子类重写 `_doDestroy` 而非 `destroy`,与 `_doReset` 模式对称
- ✅ 更灵活,子类可在 `_doDestroy` 中加自定义逻辑而不需关心守卫
- ⚠️ 增加一层抽象
- **方案C(不推荐):让 5 个类继承 ResetData**
- 改动大,影响继承链。ResetData 是 abstract 且有 `$resetByForce`/`_parseForceTrigger`/`beforeReload` 注册等逻辑,这些可能是 5 个类不需要的
- ❌ 风险大,可能引入不需要的副作用
- **结果影响**:消除 5 处重复;统一 destroy 行为;SearchData 的额外清理逻辑通过 super 调用保留。方案A 改动约 20 行,方案B 改动约 30 行。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
- **状态**:⏳ 待审核(请选择方案 A / B / C)
### R5 — `$onCreatedLife('BaseDataCreated', ...)` 注册/注销模式重复
#### R5 — `$onCreatedLife('BaseDataCreated', ...)` beforeReload 注册模式重复
- **位置**:[ResetData.ts#L94-112](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/ResetData.ts#L94)、[OrderData.ts#L41-61](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/OrderData.ts#L41)、[PaginationData.ts#L203-245](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/PaginationData.ts#L203)
- **问题**:`target.$onCreatedLife('BaseDataCreated', () => target.onLife('beforeReload', { id: ... }))` + 注销模式在 3 个类中重复。
- **建议方案**:抽出 `BeforeReloadHook` 基类,封装注册/注销逻辑。
- **结果影响**:消除 3 处 BeforeReload 生命周期注册/注销样板代码。
- **严重度**:🔴 高
- **位置**:[ResetData.ts#L108-126](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/ResetData.ts#L108)、[PaginationData.ts#L203-245](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/PaginationData.ts#L203)
- **问题**:`target.$onCreatedLife('BaseDataCreated', () => target.onLife('beforeReload', { id: ... }))` + `_uninstall` 中 `offLife('beforeReload', ...)` 注销模式在 2 个类中重复。ChoiceData/SortData 通过继承 ResetData 复用,PaginationData 未继承而自行重复实现。
- **建议方案**:抽出 `BeforeReloadHook` 基类或在 ResetData 中提供可复用的注册方法;PaginationData 可考虑继承 ResetData(与 R3 方案关联)。
- **结果影响**:消除 PaginationData 的 beforeReload 注册样板代码。
- **严重度**:🟡 中
- **状态**:⏳ 待审核
### R6 — DictionaryData $assignData / $formatData 重复
#### R6 — DictionaryData `$assignData` / `$formatData` 重复

@@ -88,6 +203,6 @@ - **位置**:[DictionaryData.ts#L81-95,96-110](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/DictionaryData.ts#L81)

### R11 — `const $constructor = (this.constructor as typeof XxxClass)` 重复
#### R11 — `const $constructor = (this.constructor as typeof XxxClass)` 重复
- **位置**:DefaultMod.ts#L42、DefaultEdit.ts#L94,133、DefaultList.ts#L24、ButtonGroupEdit.ts#L25、InputEdit.ts#L24 等 dictionary 子类
- **问题**:`const $constructor = (this.constructor as typeof XxxClass)` 模式重复 10+ 处。
- **位置**:[DefaultMod.ts#L42](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/DefaultMod.ts#L42)、[DefaultEdit.ts#L94,133](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/DefaultEdit.ts#L94)、[DefaultList.ts#L24](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/DefaultList.ts#L24)、[ButtonGroupEdit.ts#L25](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/ButtonGroupEdit.ts#L25)、[InputEdit.ts#L24](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/InputEdit.ts#L24) 等 dictionary 子类
- **问题**:`const $constructor = (this.constructor as typeof XxxClass)` 模式重复 10+ 处,新增子类时极易漏写或写错类型断言。
- **建议方案**:在 DefaultMod 提供 `protected get $constructor()` 属性,子类共享。

@@ -98,6 +213,6 @@ - **结果影响**:消除 10+ 处硬编码的类型断言,统一通过 `this.$constructor` 访问静态属性。

### R13 — FormEdit / ListEdit `$runtime` 内联结构重复
#### R13 — FormEdit / ListEdit `$runtime` 内联结构重复
- **位置**:[FormEdit.ts#L24-31](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/FormEdit.ts#L24)、[ListEdit.ts#L37-44](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/ListEdit.ts#L37)
- **问题**:两个 `$runtime` 内联结构几乎相同,仅 `form?` vs `formList?` 不同。
- **问题**:两个 `$runtime` 内联结构几乎相同,仅 `form?` vs `formList?` 不同,且构造函数中 `this.$runtime = {}` 初始化方式也完全一致。
- **建议方案**:抽 `BaseEditRuntime` 接口,FormEdit/ListEdit 各自扩展。

@@ -108,101 +223,165 @@ - **结果影响**:运行时结构统一,便于后续新增编辑类型复用。

### T5 — BaseData._runMethod 字符串反射 + 类型逃逸
---
### 类型安全
#### P2-4 — `_runMethod` 字符串反射 + 类型逃逸
- **位置**:[BaseData.ts#L168-189](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/data/BaseData.ts#L168)
- **问题**:通过 `this[methodName as string]` 反射调用方法,3 处 `as unknown as any` 类型逃逸。传递的字符串仅能是该数据上的函数类型,且 args 自动匹配对应函数的参数。
- **建议方案**(类型增强方向):定义方法索引类型,限制 `methodName` 为实际函数名联合类型,配合 `Parameters<T[K]>` 自动推断 args 类型。
- **核实结论**:反射调用的类型安全是 JS 固有难点,`as unknown as any` 难以完全避免。严重度从 🔴 高降为 🟡 中。
- **建议方案**(类型增强方向):定义方法索引类型,限制 `methodName` 为实际函数名联合类型,配合 `Parameters<T[K]>` 自动推断 args 类型。或改用回调函数注册而非字符串。
- **结果影响**:🟡 中(类型安全)。会影响所有 `triggerMethod` 调用链,需慎重。
- **严重度**:🟡 中
- **状态**:⏳ 待审核
### D1 — OrderData 死代码
#### P2-6 — CascaderValue 泛型过复杂 + 类型逃逸
- **位置**:[OrderData.ts](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/OrderData.ts) 全文件
- **问题**:`OrderData` 实现完整但**未注册**到 `ModuleData.ModuleMap`(仅含 update/pagination/dictionary/search/choice/sort),`ModuleDataKeys` 也不含 `order`。git log 显示从 init commit 就存在但从未被实际使用。
- **建议方案**:等待查找这个函数为什么存在。若确认废弃则删除整个文件;若需要则加入 ModuleMap。
- **结果影响**:🔴 高(死代码占用维护心智成本)。
- **状态**:⏳ 等待查找函数存在原因
- **位置**:[CascaderValue.ts#L5,18,33,46,62,74](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/CascaderValue.ts#L5)
- **问题**:L5 类签名 5 层条件泛型 `<C extends PropertyKey | undefined, D extends (C extends PropertyKey ? CascaderValueType<C> : SelectValueType)>`;L18/33/46 `item[this.cascader!]` 非空断言;L62 `(currentItem as any)[this.cascader!]` 完全逃逸类型检查;L74 `getFilter(...)!` 非空断言。`cascader` 类型为 `C extends PropertyKey | undefined`,但访问处全部断言非空。
- **方案 A(拆分类,最优类型安全)**:将 `C extends PropertyKey | undefined` 拆为 `SelectValue`(无级联)+ `CascaderValue<C extends PropertyKey>`(强制级联),消除所有 `!` 断言。⚠️ 破坏性变更:`C = undefined` 的退化用法需改为直接用 SelectValue。
- **方案 B(getter 收窄,渐进式)**:在 CascaderValue 内加 `protected get _cascaderKey(): PropertyKey { return this.cascader as PropertyKey }`,级联方法内通过此访问,消除 `!` 断言。✅ 非破坏性,但仍有 1 处 `as` 断言(集中在 getter 内)。
- **结果影响**:🟢 低(仅类型层面,运行时无变化)。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
---
#### T6 — DefaultEdit `this.multiple = !!initOption.multiple as M` 类型谎言
## 问题清单
- **位置**:[DefaultEdit.ts#L101](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/DefaultEdit.ts#L101)
- **问题**:`!!initOption.multiple` 结果必为 `boolean`,但通过 `as M` 强转为泛型 `M`。当 `M=true` 且 `initOption.multiple` 为 falsy 时,运行时为 `false` 但类型标为 `true`,后续依赖 `M=true` 的逻辑类型与运行时不一致。
- **建议方案**:`this.multiple = !!initOption.multiple as M` 改为 `this.multiple = (initOption.multiple ?? false) as M`,或重新设计泛型使 `M` 能正确表达「是否为多选」。
- **结果影响**:修正类型与运行时不一致,但需检查所有依赖 `M` 的分支。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
> 以下为对 `packages/data` 及其消费者(`component`、`component-antd`、`pages/index/views/list`)的代码审查结果。
> 严重度:🔴 高 / 🟡 中 / 🟢 低
#### T7 — DefaultSelectEdit `as unknown as` 双重强转
---
- **位置**:[DefaultSelectEdit.ts#L36,42,44-46](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/DefaultSelectEdit.ts#L36)
- **问题**:多处 `as unknown as` 双重强转拟合泛型 `C`/`D`,L36 `(initOption.select as unknown as SelectData).loadData(...)`,说明泛型设计本身存在缺陷。
- **建议方案**:重新设计泛型,避免双重强转。
- **结果影响**:需调整 DefaultSelectEdit 泛型签名,可能影响子类。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
### ✅ 已完成(v5.0.9)
#### P4-6 — ForceValue `module` 类型定义松散
| 编号 | 严重度 | 问题摘要 | 处理方式 |
| --- | --- | --- | --- |
| P1-1 | 🔴 | `_syncData` 空实现被 22 处调用 | 加 TSDoc 注释说明设计意图:Vue 环境空操作,非响应式环境需重写 |
| P1-2 | 🔴 | `dictionray` 拼写全局扩散 | DictionaryValue.ts 3 处 + list/base 1 处 + list/cascade 4 处 + README 全部改为 `dictionary` |
| P1-3 | 🔴 | ArrayValue 误导性「严重 Bug」注释 | 删除错误 `@warning` 注释 |
| P1-4 | 🔴 | `triggerMethodWithOperateAndStatus` 元调用 | 加注释说明集中化设计意图 + 反射复用原因 |
| P2-1 | 🟡 | `$assignData` / `$formatData` 80% 重复 | 抽取 `_buildDataValue`,6 个路径逐一验证与原始行为一致;保证性能(payload 仅 assign 时创建、originProp 仅调一次) |
| P2-3 | 🟢 | `destroy(force?)` 样板代码 | ResetData 加默认实现,ChoiceData/SortData 删除重复 |
| P3-2 | 🟢 | index.ts 混排导出 | 按 7 组分组重排 + 注释分区,导出项 100% 保留 |
| P4-1 | 🟢 | InterfaceValue 双重断言 | 调整判断顺序,用 typeof 类型守卫自动收窄 |
| P4-2 | 🟢 | LayoutParse 冗余断言 | `undefined as undefined | T` → `undefined` |
| P4-5 | 🟢 | StorageValue.init 返回值语义 | 补 TSDoc:true=缓存有效已加载 / false=缓存无效需重新加载 |
| P5-1 | 🟡 | list/base `dictionray` 拼写 | 随 P1-2 一并修改 |
| P5-2 | 🟡 | list/cascade `dictionray` 拼写 | 随 P1-2 一并修改 |
| P6-1 | 🟢 | README 旧示例错误 | 重写「快速开始」 |
| P6-2 | 🟢 | README 架构描述不准 | 重写「架构概览」 |
- **位置**:[ForceValue.ts#L9-14](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/ForceValue.ts#L9)
- **问题**:`module` 类型定义松散,`[prop: string]` 索引签名允许任意值,应收窄为已知模块联合类型。
- **建议方案**:收窄索引签名为 `pagination | choice | sort | ...` 已知模块联合类型。
- **结果影响**:🟢 低。
- **严重度**:🟢 低
- **状态**:⏳ 待审核
**版本记录**:所有子包升级至 v5.0.9(2026-07-03)
- `@complex-suite/data`: 5.0.8 → 5.0.9
- `@complex-suite/utils`: 3.0.8 → 3.0.9
- `@complex-suite/plugin`: 5.0.8 → 5.0.9
- `@complex-suite/component`: 5.0.8 → 5.0.9
- `@complex-suite/component-antd`: 5.0.8 → 5.0.9
- `@complex-suite/request`: 2.0.8 → 2.0.9
- `@complex-suite/request-axios`: 2.0.8 → 2.0.9
---
### 📌 待审核(需你决策)
### 职责划分
#### P2-2 `nonEmptySetProp` 与 `nonEmptySetComplexProp` — 不合并,标注性能考量
#### C1 — DictionaryValue 单类承担过多职责(God Object)
- **问题**:[dataConfig.ts#L23-L34](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/dataConfig.ts#L23) 两个函数仅末尾 `setProp` vs `setComplexProp` 不同。
- **原方案**:合并为 `nonEmptySetProp(target, prop, value, useComplex = false)`
- **修正**:**不合并**。`setProp` 内部 O(1) 直接索引赋值,`setComplexProp` 需 split + 遍历解析 `'a.b.c'` 路径,分开是为了简单场景快速输出,性能优先。建议仅在注释中标注性能考量。
- **影响**:🟢 低
- **位置**:[DictionaryValue.ts](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/DictionaryValue.ts) 全类(478 行)
- **问题**:单类同时承担以下 8 类职责,严重违反单一职责原则(SRP):
1. **接口值管理**:`$interface`(InterfaceValue 实例)、`$getInterfaceData`/`$getInterfaceValue`/`$setInterfaceValue`
2. **来源判断**:`$originFrom`、`$isOriginFrom`、`$getOriginProp`
3. **模块管理**:`$mod`(各 mod 实例)、`$getMod`、`$triggerFunc`
4. **数据格式化**:`$formatData`、`$assignData`、`_buildDataValue`(含 `skipSameProp` 控制耦合)
5. **数据解析**:`$parseValue`、`parseValue`、`_setParseValue`
6. **数据收集**:`collectValue`
7. **模块初始化**:静态 `$initMod`/`$initEditMod`/`_initEditMod`
8. **生命周期触发**:`_triggerCreateLife('DictionaryValue', ...)`
- **影响**:
- 478 行单文件,维护者需通读全类才能理解单一功能
- 修改任一职责都可能影响其他职责(如改 `$formatData` 可能影响 `_setParseValue`)
- 测试困难:无法单独测试格式化逻辑而不实例化整个 DictionaryValue
- 多处非空断言(`mod.$runtime.dictionaryList!`/`this.dictionary!`/`index as unknown as string`)源于职责耦合
- **建议方案**:按职责拆分为多个类/模块:
- `DictionaryValueFormatter`:负责 `$formatData`/`$assignData`/`_buildDataValue`
- `DictionaryValueParser`:负责 `$parseValue`/`parseValue`/`_setParseValue`
- `DictionaryValueCollector`:负责 `collectValue`
- `DictionaryModFactory`:负责静态 `$initMod`/`$initEditMod`/`_initEditMod`
- `DictionaryValue`(核心):保留 `$interface`/`$originFrom`/`$mod`/生命周期,委派上述职责
- **结果影响**:
- 🔴 高(重构范围大,涉及 DictionaryValue 被大量引用,需逐个验证调用方)
- 但能显著降低维护成本,每个职责可独立测试
- 非空断言可随职责拆分自然消除
- 需评估是否值得投入,或仅做局部拆分(如仅拆出 Formatter/Parser)
- **严重度**:🔴 高
- **状态**:⏳ 待审核(需评估投入产出比,决定全量拆分还是局部拆分)
#### C2 — DependData.destroy 空实现导致内存泄漏
- **位置**:[DependData.ts#L180-184](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/DependData.ts#L180)
- **问题**:`destroy(option?)` 方法体仅有注释 `// 此处后续考虑数据的解绑操作`,实际为空实现。但 `$bindDependByLife`(L102-111)在 depend 对象上注册了 `loaded`/`loadFail`/`updated`/`updateFail` 生命周期监听,`destroy` 从未清理这些监听。若 depend 对象生命周期长于 target,监听泄漏,depend 上会持有已销毁 target 的引用。
- **额外问题**:`DependData` 不继承任何基类(L54 `class DependData {`),与其他模块设计不一致,缺少 `$exportMsg`/`_syncData`/`_getId`/`$life` 能力。
- **建议方案**:
1. 在 `destroy` 中实现解绑:遍历 `$bindDependByLife` 注册的 `lifeDict`,调用 `unbind()` 清理 depend 上的监听
2. 需在 `DependValue`/`$bindDepend` 中保存 `unbind` 函数引用,供 destroy 调用
- **结果影响**:消除内存泄漏风险。需改造 DependData 内部数据结构以保存 unbind 引用。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
#### P2-4 `_runMethod` 字符串反射 + 类型逃逸
#### C3 / P2-5 — ObserveList.reset() 破坏父类封装
- **问题**:[BaseData.ts#L168-L189](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/data/BaseData.ts#L168) 通过 `this[methodName as string]` 反射调用方法,3 处 `as unknown as any` 类型逃逸。
- **处理方案**:定义 `(this: BaseData, ...args: unknown[]) => unknown` 类型的方法索引,或改用回调函数注册而非字符串。
- **影响**:🟡 中(类型安全)。
- **备注**:会影响所有 `triggerMethod` 调用链,需慎重。
- **位置**:[ObserveList.ts#L85-95](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/ObserveList.ts#L85)
- **问题**:`reset()` 直接操作父类 ArrayValue 的内部状态 `$map.clear()`/`$hidden.clear()`/`$frozen.clear()`/`this.data = []`/`this.$prop = []`,绕过父类受控方法(`pop`/`shift`/`delete` 等)。父类数据结构变化时此处无编译期提示。
- **建议方案**:在 ArrayValue 暴露 `clearAll()` / `resetState()` 等受保护方法,ObserveList 调用父类 API。
- **结果影响**:🟡 中(封装性)。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
#### P2-5 ObserveList.reset() 破坏父类封装
---
- **问题**:[ObserveList.ts#L85-L95](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/ObserveList.ts#L85) 直接操作父类 ArrayValue 的内部状态 `$map` / `$hidden` / `$frozen` / `data` / `$prop`。
- **处理方案**:在 ArrayValue 暴露 `clearAll()` / `resetState()` 等受保护方法,ObserveList 调用父类 API。
- **影响**:🟡 中(封装性)。
### 封装性
#### P1 — ArrayValue 核心内部状态全部 public
- **位置**:[ArrayValue.ts#L4-8](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/ArrayValue.ts#L4)
- **问题**:`data`/`$prop`/`$map`/`$hidden`/`$frozen` 5 个维护一致性的核心状态全部 public,`$` 前缀暗示私有但 TS 未用 `private`/`protected`。外部可直接绕过所有维护逻辑(如 `this.$map.set(...)` 而不同步 `$prop`/`data`)。
- **关联问题**:C3(ObserveList.reset 破坏封装)正是 P1 的直接后果——正因为核心状态 public,ObserveList 才能直接操作。
- **建议方案**:将 `$prop`/`$map`/`$hidden`/`$frozen` 改为 `protected`,仅暴露必要的访问器。
- **结果影响**:需检查所有直接访问这些字段的子类(ObserveList 等)和外部代码。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
#### P2-6 CascaderValue 泛型过复杂
---
- **问题**:[CascaderValue.ts#L5](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/CascaderValue.ts#L5) 5 层泛型 + 条件类型,5 处 `this.cascader!` 非空断言,1 处 `(currentItem as any)[this.cascader!]` 类型逃逸。
- **方案 A(拆分类,最优类型安全)**:将 `C extends PropertyKey | undefined` 拆为 `SelectValue`(无级联)+ `CascaderValue<C extends PropertyKey>`(强制级联),消除所有 `!` 断言。⚠️ 破坏性变更:`C = undefined` 的退化用法需改为直接用 SelectValue。
- **方案 B(getter 收窄,渐进式)**:在 CascaderValue 内加 `protected get _cascaderKey(): PropertyKey { return this.cascader as PropertyKey }`,级联方法内通过此访问,消除 `!` 断言。✅ 非破坏性,但仍有 1 处 `as` 断言(集中在 getter 内)。
- **影响**:🟢 低(仅类型层面,运行时无变化)。
### 副作用
#### S1 — DefaultEdit 构造函数污染入参 `simple` 对象
- **位置**:[DefaultEdit.ts#L96-100](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/DefaultEdit.ts#L96)
- **问题**:L96 `this.simple = initOption.simple || {}` 直接引用原对象,L98-99 `this.simple.value ??= true` / `this.simple.rules ??= true` 向其写入字段。若调用方 `simple` 对象被多处引用会被污染。
- **建议方案**:`this.simple = { ...initOption.simple }` 浅拷贝,或 `this.simple = initOption.simple ? { ...initOption.simple } : {}`。
- **结果影响**:消除共享可变状态副作用。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
#### P3-3 `_install` / `_uninstall` 文档缺失
---
- **问题**:`_install(parent, from)` / `_uninstall(parent, from)` 无注释说明参数含义和调用时机。
- **处理方案**:在 ModuleData.ts 和各模块基类补 TSDoc 注释;README 已补充说明。
- **影响**:🟢 低。
### 默认实现 / 错误处理
#### P4-4 — FormValue 静态 validate / clearValidate 默认 console.error
- **位置**:[FormValue.ts#L6-10](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/FormValue.ts#L6)
- **问题**:静态默认实现是 `console.error` + `Promise.reject`,运行时若未注入实现会静默失败(被 `.catch` 吞掉)。生产环境污染控制台。
- **建议方案**:默认抛错或在生产环境 warn。
- **结果影响**:🟢 低。
- **严重度**:🟢 低
- **状态**:⏳ 待审核
#### P4-3 DefaultInfo / DefaultList 默认值管理风格不统一
---
- **问题**:[DefaultList.ts](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/DefaultList.ts) 用静态 `$option` + `||`/`??`,[DefaultInfo.ts](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/dictionary/DefaultInfo.ts) 用内联 `??`/`||`,两处 `if (initOption === true) { initOption = {} }` 重复。`createOption` 不接受 `true`,原方案有误。
- **方案:DefaultMod.parseModInitOption**
### 命名不一致
#### P4-3 — 默认值管理风格不统一
- **位置**:dictionary 目录多文件
- **问题**:默认值管理三套范式并存:
1. 静态 `$option` + `||`/`??`(DefaultList)
2. 静态 `$defaultOption` + `||`/`??`(InputEdit/TextAreaEdit/SelectEdit/SimpleDateEdit)
3. 行内硬编码字面量(DefaultInfo/ContentEdit/FormEdit/ButtonEdit)
且 `||` 与 `??` 混用。`SelectEdit` 对 boolean 字段 `hideArrow`/`hideClear`/`autoWidth` 用 `||` 是定时炸弹(用户传 `false` 会被默认值覆盖)。`InputEdit`/`TextAreaEdit` 对 `size: 0` 会被 `||` 吞掉。
- **建议方案**:
- 统一为「静态 `$defaultOption` + `parseModInitOption`」模式(见下)
- `||` 统一改为 `??`
```ts

@@ -220,36 +399,21 @@ // DefaultMod.ts

```
然后 DefaultList/DefaultInfo 统一用 `super(DefaultMod.parseModInitOption(initOption, Xxx.$option), parent, modName)`。
- **优点**:
- 消除 `if (initOption === true) { initOption = {} }` 重复(2 处 → 1 处)
- 默认值管理统一为「静态 $option + parseModInitOption」
- DefaultInfo 补上缺失的静态 $option,默认值声明集中
- **是否有必要**:改善有限(仅 2 个类),但如果后续新增 mod 类型可复用同一模式,减少出错。
- **性能**:与现状持平。`parseModInitOption` 是构造时一次性调用,且 `for...in` 遍历的是 $option 中少量默认值字段(2-3 个),开销可忽略。内联 `??`/`||` 看似更快,但构造时差异在纳秒级,无实际影响。
- **影响**:🟢 低
- **结果影响**:统一默认值管理,消除 `||` 对 `0`/`false`/`''` 的吞值风险。
- **严重度**:🟡 中
- **状态**:⏳ 待审核
#### P4-4 FormValue 静态 validate / clearValidate 默认 console.error
---
- **问题**:[FormValue.ts](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/FormValue.ts) 静态默认实现是 `console.error`,运行时若未注入实现会静默失败。
- **处理方案**:默认抛错或在生产环境 warn。
- **影响**:🟢 低。
- **状态**:⏳ 待审核
### 其他
#### P4-6 ForceValue 类型定义松散
#### P3-3 — `_install` / `_uninstall` 文档缺失
- **问题**:[ForceValue.ts](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/ForceValue.ts) `module` 类型定义松散,`[prop: string]` 索引签名允许任意值。
- **处理方案**:收窄索引签名为已知模块联合类型。
- **影响**:🟢 低。
- **问题**:`_install(parent, from)` / `_uninstall(parent, from)` 无注释说明参数含义和调用时机。
- **建议方案**:在 ModuleData.ts 和各模块基类补 TSDoc 注释;README 已补充说明。
- **结果影响**:🟢 低。
- **严重度**:🟢 低
- **状态**:⏳ 待审核
#### P5-3 `upload` 语义 — 不修改
- **原始判断**:`$import`/`$image` 用 `upload` 语义不清
- **修正**:`MenuValue` 有 `click`(普通点击),`ButtonValue extends MenuValue` 额外增加 `upload`(文件上传专用)+ `fileOption`。`upload` 语义清晰:该按钮是上传按钮,触发文件选择 → 拿到 File → 调用 `upload(file)`。list/base 中的 `$import`/`$image` 确实是文件上传按钮,使用 `upload` 正确。
- **结论**:撤销原建议,`upload` 与 `click` 各司其职。建议在 README 补充说明 `ButtonValue` 的 `click` vs `upload` 区别。
- **状态**:⏳ 待审核(确认是否需要在 README 补充说明)
---
### ✅ 已确认不修改
## 已确认不修改

@@ -260,9 +424,2 @@ | 编号 | 问题摘要 | 确认理由 |

| P5-3 | `upload` 语义 | `upload` 是文件上传专用字段,语义清晰,与 `click` 各司其职 |
---
### 审阅方式
- 对待审核项,请回复:`编号 + 处理意见`(如「P2-2 ✅ 仅加注释」「P2-6 方案 B」等)
- 多条可批量回复:`P2-2, P4-4, P4-6 ✅ 修改;P2-6 方案 B`
- 收到审阅意见后按批次执行
| P2-2 | `nonEmptySetProp` 不合并 | `setProp` O(1) 直接赋值 vs `setComplexProp` 需解析路径,分开是性能优先 |