🎩 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.11
to
5.0.12
+24
-0
history.md

@@ -0,1 +1,25 @@

### `5.0.12`
- fix(dictionary): 修正 `SimpleDateEdit` 中 `option.format || option.time ? A : B` 因运算符优先级导致 `option.format` 从未被使用的 Bug(B1)。
- fix(core): `TrackData.countPercent` 和 `setPercent` 增加除零保护(B2)。
- fix(dictionary): `TextAreaEdit` 硬编码 `'textArea'` 改为使用 `initOption.type`(B6)。
- fix(dictionary): 删除 `DefaultMod.ts` 中失效的 eslint-disable 注释(D4)。
- fix(module): 删除 `DictionaryData.createData` 中无语义的占位注释(D5)。
- fix(dictionary): `DefaultEdit.$defaultRuleMessage` 冗余 `as string` 断言删除(S5)。
- fix(module): `PromiseData.ts` 类型标注中的双空格格式化为单空格(O4)。
- fix(module): `ResetData.ts` 导入路径 `'./../data/Data'` 统一为 `'../data/Data'`(O5)。
- fix(lib): `StorageValue`、`UpdateData`、`TrackData` 中 `setTimeout` 返回值类型从 `number` 改为 `ReturnType<typeof setTimeout>`,删除 `as unknown as number` 双重断言(T10/T11/T12)。
- refactor(module)!: `UpdateData` 中 `immerdiate` 拼写修正为 `immediate`,涉及字段名、方法名和判断条件,共 7 处。`ComplexData.updateImmerdiate` 同步重命名为 `updateImmediate`(N1)。
- refactor(lib): `ArrayValue.isHide` 重命名为 `isHidden`(N2)。
- refactor(lib)!: `ArrayValue.thaw` 重命名为 `unfreeze`(N3)。
- refactor(lib)!: `FileValue.truncation` 重命名为 `truncate`。消费者 `component-antd/MultipleImport` 同步修正(N4)。
- refactor(lib)!: `StorageValue.push` 重命名为 `register`,`SelectData`、`DefaultData` 调用处同步修正(N6)。
- refactor(lib)!: `AttrsValue.pushEvent` 参数 `type` 重命名为 `order`(N9)。
- refactor(module): `SearchData._uninstall` 补充注销 `inited` 中 `searchInited` 监听(L1)。
- refactor(module)!: `ResetData.reset(force?)` 改为模板方法,调用 `protected abstract _doReset(force?)`。`ChoiceData`、`SortData` 同步改为覆写 `_doReset`(R4)。
- refactor(lib)!: `FileValue` 命名导出改为默认导出(O1)。
- refactor(types): 新增 `DictionaryCtorArgs` 类型定义,收窄 dictionary 子模块构造函数签名(R10)。
- refactor(core): `TrackData.moveBackward`/`moveForward` 抽取共享逻辑到 `_moveByDirection` 保护方法(C6)。
- refactor(lib): `DictionaryValue.parseValue` 中 `DefaultLoadEdit` 分支消除 Promise 反模式(嵌套 `new Promise + finally + then + catch`),改为 `.catch(() => {}).then()` 链式调用(F4)。
- refactor(dictionary): 22 个 dictionary 文件 `import DictionaryValue` 改为 `import type DictionaryValue`(O2)。
### `5.0.11`

@@ -2,0 +26,0 @@ - fix(consumer): 消费者代码适配 `name` 类型变更,搜索区域按钮 `name` 从 `string` 改为 `() => string`。

+1
-1

@@ -37,3 +37,3 @@ /**

import TipValue from './src/lib/TipValue'
import { FileValue, FileMultipleValue } from './src/lib/FileValue'
import FileValue, { FileMultipleValue } from './src/lib/FileValue'
import GridParse, { type GridOption, createGridOption } from './src/lib/GridParse'

@@ -40,0 +40,0 @@ import LayoutParse from './src/lib/LayoutParse'

{
"name": "@complex-suite/data",
"version": "5.0.11",
"version": "5.0.12",
"description": "a complex data",

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

"dependencies": {
"@complex-suite/utils": "3.0.11",
"@complex-suite/plugin": "5.0.11"
"@complex-suite/plugin": "5.0.11",
"@complex-suite/utils": "3.0.11"
},

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

+558
-136

@@ -13,2 +13,62 @@ # @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
```
## 核心概念

@@ -24,2 +84,16 @@

## 数据类继承链
```
Data (utils._Data)
└─ SimpleData # $extra
└─ DefaultData # $prop / $life / $storage
└─ BaseData # $status / $promise / $depend / $module / loadData
└─ ComplexData # 业务回调(getData/buildData/...)+ 模块代理方法
├─ ComplexList # $list
└─ ComplexInfo # $info
```
每一层只关心自己的关注点,子类通过 `_triggerCreateLife('ClassName', false/true, initOption)` 触发对应的 `beforeCreate`/`created` 生命周期。
## 模块系统

@@ -29,12 +103,15 @@

| 模块 | 类 | 作用 |
|---|---|---|
| `dictionary` | DictionaryData | 字典管理(必填) |
| `search` | SearchData | 检索表单与菜单 |
| `pagination` | PaginationData | 分页参数与总数 |
| `choice` | ChoiceData | 多选状态 |
| `sort` | SortData | 排序参数 |
| `update` | UpdateData | 定时刷新 |
| 模块 | 类 | 作用 | 必填 |
|---|---|---|---|
| `dictionary` | DictionaryData | 字段字典管理 | ✅ |
| `search` | SearchData | 检索表单与菜单 | - |
| `pagination` | PaginationData | 分页参数与总数 | - |
| `choice` | ChoiceData | 多选状态 | - |
| `sort` | SortData | 排序参数 | - |
| `update` | UpdateData | 定时刷新 | - |
| `depend` | DependData | 依赖加载 | - |
| `status` | StatusData | 状态机 | 内置 |
| `promise` | PromiseData | Promise 缓存 | 内置 |
模块通过 `installData` / `uninstallData` 安装卸载,触发 `_install` / `_uninstall` 钩子。
模块通过 `installData` / `uninstallData` 安装卸载,触发 `_install` / `_uninstall` 钩子。模块间通过 `ModuleData.getData(modName)` 互相查询。

@@ -47,9 +124,14 @@ ## 生命周期

|---|---|
| `beforeCreate` / `created` | 实例构造前后 |
| `beforeLoad` | `loadData` 开始 |
| `loaded` | 加载成功 |
| `loadFail` | 加载失败 |
| `beforeReload` / `reloaded` / `reloadFail` | `reloadData` 各阶段 |
| `beforeSearch` | `setSearch` 校验前 |
| `searched` | 检索成功 |
| `beforeUpdate` | 字典更新前 |
| `reseted` | `reset` 后 |
| `searched` / `searchFail` | 检索成功/失败 |
| `beforeUpdate` / `updated` / `updateFail` | 更新数据各阶段 |
| `beforeReset` / `reseted` | `reset` 前后 |
| `beforeDestroy` / `destroyed` | `destroy` 前后 |
| `dataChange` | 业务方法成功后(buildData/changeData/deleteData/...)|
| `parentChange` | `$setParent` 时 |

@@ -68,16 +150,20 @@ 监听方式:

`onLoaded(handler)` 是 `loaded` 的快捷监听,若当前已加载完成会立即触发。
## 基础列表用法
下面示例来自真实业务页面 `src/pages/index/views/list/base/index.vue` 的标准模式:页面创建 `ComplexList`,配置搜索、字典、分页和业务接口,然后传给 `ComplexQuickList` 渲染。
下面示例提炼自真实业务页面 `src/pages/index/views/list/base/index.vue`,展示标准模式:页面创建 `ComplexList`,配置搜索、字典、分页和业务接口,然后传给 `ComplexQuickList` 渲染。
```vue
<template>
<ComplexQuickList
:list-data="mainData"
:components="['spin', 'search', 'table', 'edit', 'info']"
:simple-table="false"
:components-props="componentsProps"
@search="onSearch"
@table="onTable"
/>
<div class="local-page">
<ComplexQuickList
:list-data="mainData"
:components="['spin', 'search', 'table', 'edit', 'info']"
:simple-table="false"
:components-props="componentsProps"
@search="onSearch"
@table="onTable"
/>
</div>
</template>

@@ -87,4 +173,4 @@

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

@@ -94,26 +180,28 @@

name: 'RecordList',
components: {
ComplexQuickList
},
components: { ComplexQuickList },
setup() {
// 1) 静态选项数据源
const select = new SelectValue({
list: [
{ value: 0, label: '选项1' },
{ value: 1, label: '选项2' }
{ value: 1, label: '选项2' },
{ value: 2, label: '选项3' }
]
})
// 2) 创建列表数据模型
const mainData = new ComplexList({
prop: 'mainData',
module: {
// 搜索区
search: {
menu: {
list: [
'build',
'delete',
new ButtonEdit({
'build', // 内置:新增按钮
'delete', // 内置:批量删除按钮
new ButtonEdit({ // 自定义按钮
prop: 'choice2',
type: 'button',
option: {
name: '已选择2条时可用',
name: () => '已选择2条时可用',
disabled(payload: any) {

@@ -126,2 +214,11 @@ return payload.choice !== 2

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

@@ -132,8 +229,10 @@ ]

{
prop: 'keyword',
name: '关键字',
prop: 'input',
name: '输入框',
mod: {
search: {
$format: 'edit',
type: 'input'
type: 'input',
collapse: 2,
option: { size: 20 }
}

@@ -143,4 +242,4 @@ }

{
prop: 'status',
name: '状态',
prop: 'search',
name: '检索框',
mod: {

@@ -150,3 +249,17 @@ search: {

type: 'select',
select: select
cascader: undefined,
width: 100,
reload: true,
search: {},
// 远程加载选项
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({})
})
}
}

@@ -157,19 +270,32 @@ }

},
// 字典:字段定义(同时服务 list/info/edit/build/change)
dictionary: {
propData: { id: 'id' },
list: [
{ prop: 'menu', name: '操作', originFrom: 'local', mod: { list: { width: 140 } } },
{ prop: '$index', name: 'No', originFrom: 'local', mod: { list: { width: 60 } } },
{
prop: 'id',
name: 'ID',
mod: { list: { width: 80 }, info: {} }
},
{
prop: 'input',
name: '输入框',
mod: {
list: { width: 80 },
info: {}
list: { width: 100 },
info: {},
edit: { type: 'input', required: true, option: { size: 20 } },
build: { $redirect: 'edit' },
change: { $redirect: 'edit' }
}
},
{
prop: 'name',
name: '名称',
prop: 'switch',
name: '开关',
assign(value) { return value === 1 ? true : false },
collect(value) { return value ? 1 : 0 },
mod: {
list: {},
info: {},
edit: { type: 'input', required: true },
list: { width: 70 },
edit: { type: 'switch', required: true },
build: { $redirect: 'edit' },

@@ -180,11 +306,61 @@ change: { $redirect: 'edit' }

{
prop: 'status',
name: '状态',
prop: 'select',
name: '选择器',
showProp: { default: 'value', list: 'label' },
assign(value) { return select.getItem(value) },
mod: {
list: {},
info: {},
edit: { type: 'select', select: select },
list: { width: 100 },
edit: {
type: 'select',
cascader: undefined,
required: true,
select: select,
pagination: {},
getData(this: any) {
return new Promise((resolve) => {
setTimeout(() => {
this.$select.setList(select.getList())
this.$pagination.setCount(100)
resolve({})
}, 200)
})
}
},
build: { $redirect: 'edit' },
change: { $redirect: 'edit' }
}
},
{
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',
name: '时间范围',
assign(value) { return (value as string).split(',') },
collect(value) { return (value as string[]).join(',') },
mod: {
list: { width: 300 },
edit: {
type: 'dateRange',
required: true,
option: {
time: {},
rangeLimit: { value: 5 * 24 * 60 * 60 },
disabledDate: {
start: { value: 'today', eq: true },
end: { value: 'tomorrow', eq: true }
}
}
},
build: { $redirect: 'edit' },
change: { $redirect: 'edit' }
}
}

@@ -195,2 +371,3 @@ ]

},
// 业务回调:均要求返回 Promise
getData(this: ComplexList) {

@@ -202,9 +379,7 @@ return new Promise((resolve, reject) => {

size: this.getPageSize()
}
} as any
listApi.baseList.require(postData).then(res => {
this.formatList(res.data.data.list, res.data.data.num)
resolve(res)
}).catch(err => {
reject(err)
})
}).catch(reject)
})

@@ -217,5 +392,3 @@ },

resolve(res)
}).catch(err => {
reject(err)
})
}).catch(reject)
})

@@ -229,5 +402,3 @@ },

resolve(res)
}).catch(err => {
reject(err)
})
}).catch(reject)
})

@@ -243,2 +414,13 @@ },

})
},
multipleDeleteData(this: ComplexList, choiceList) {
return new Promise((resolve) => {
choiceList.forEach(item => {
const index = this.$list.indexOf(item)
if (index > -1) {
this.$list.splice(index, 1)
}
})
resolve({})
})
}

@@ -258,5 +440,5 @@ })

menu: [
{ prop: '$change', name: '编辑', color: 'link' },
{ prop: '$delete', name: '删除', color: 'danger' },
{ prop: '$info', name: '详情', color: 'link' }
{ prop: '$change', name: () => '编辑', color: 'link' },
{ prop: '$delete', name: () => '删除', color: 'danger' },
{ prop: '$info', name: () => '详情', color: 'link' }
]

@@ -278,14 +460,25 @@ }

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

@@ -295,13 +488,23 @@ }

- `list`:表格列配置,供 `TableView` 渲染列。
- `info`:详情字段配置,供详情区域展示。
- `edit`:通用编辑配置。
- `build`:新增表单配置,可通过 `$redirect: 'edit'` 复用编辑配置。
- `change`:修改表单配置,可通过 `$redirect: 'edit'` 复用编辑配置。
- `assign`:后端值进入展示/表单前的转换函数。
- `collect`:表单值提交接口前的转换函数。
### 编辑类型对照
| type | 字典类 | 适用场景 |
|---|---|---|
| `input` | InputEdit | 文本输入 |
| `textArea` | TextAreaEdit | 多行文本 |
| `inputNumber` | InputNumberEdit | 数字输入 |
| `select` | SelectEdit / DefaultSelectEdit | 选择器 |
| `switch` | SwitchEdit | 开关 |
| `date` | DateEdit / SimpleDateEdit | 日期 |
| `dateRange` | DateRangeEdit | 日期范围 |
| `file` | FileEdit | 文件上传 |
| `button` | ButtonEdit | 按钮(搜索区/自定义操作)|
| `form` | FormEdit | 内嵌表单 |
| `list` | ListEdit | 内嵌列表 |
| `content` | ContentEdit | 纯展示 |
| `custom` | CustomEdit / CustomLoadEdit | 自定义渲染 |
## 搜索区配置
`module.search` 分为菜单和搜索字段两部分。
`module.search` 分为菜单和搜索字段两部分:

@@ -312,14 +515,5 @@ ```ts

list: [
'build',
'delete',
new ButtonEdit({
prop: 'custom',
type: 'button',
option: {
name: '自定义按钮',
click() {
return Promise.resolve({})
}
}
})
'build', // 内置:新增
'delete', // 内置:批量删除
new ButtonEdit({ ... }) // 自定义按钮
]

@@ -334,3 +528,4 @@ },

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

@@ -345,13 +540,20 @@ }

- `$search`:调用 `listData.setSearch()`,更新搜索条件并触发列表加载。
- `$reset`:调用 `listData.resetSearch()`,重置搜索条件。
- `$refresh`:调用 `listData.reloadData({ data: true, sync: true })`。
- `$build`:打开新增弹窗。
- `$delete`:触发批量删除。
- `$info`:打开详情弹窗。
- `$export`:调用 `listData.triggerMethod('exportData')`。
| prop | 行为 |
|---|---|
| `$search` | 调用 `listData.setSearch()`,更新搜索条件并触发列表加载 |
| `$reset` | 调用 `listData.resetSearch()`,重置搜索条件 |
| `$refresh` | 调用 `listData.reloadData({ data: true, sync: true })` |
| `$build` | 打开新增弹窗 |
| `$delete` | 触发批量删除(基于 choice)|
| `$info` | 打开详情弹窗 |
| `$export` | 调用 `listData.triggerMethod('exportData')` |
| `$import` / `$image` | 文件上传按钮(通过 `upload` 回调)|
`ButtonEdit` 区分两种语义:
- `click()`:普通点击回调,返回 Promise
- `upload(file: File)`:文件上传专用回调,由组件触发文件选择 → 拿到 File → 调用 upload
## 远程选择器
选择器可以使用静态 `SelectValue`,也可以在 `SelectEdit.getData` 中远程加载选项。
选择器可以使用静态 `SelectValue`,也可以在 `SelectEdit.getData` 中远程加载选项:

@@ -366,4 +568,9 @@ ```ts

type: 'select',
reload: true,
search: {},
cascader: undefined, // 显式声明无级联
width: 100,
reload: true, // 搜索框值变化时重新加载
search: { // SelectValue 配置
limit: 3, // 最多保留 3 个历史选项
limitContent: '限制3个'
},
getData(this: SelectEdit) {

@@ -373,4 +580,4 @@ return new Promise((resolve) => {

{ value: this.$search?.value, label: this.$search?.value || '' },
{ value: 1, label: '选项1' },
{ value: 2, label: '选项2' }
{ value: 1, label: '1' },
{ value: 2, label: '2' }
])

@@ -395,12 +602,10 @@ resolve({})

const postData = {
...this.getSearch(),
page: this.getPage(),
size: this.getPageSize()
...this.getSearch(), // 搜索条件
page: this.getPage(), // 当前页码
size: this.getPageSize() // 每页数量
}
api.list(postData).then(res => {
this.formatList(res.list, res.total)
this.formatList(res.list, res.total) // 写入 $list 和分页总数
resolve(res)
}).catch(err => {
reject(err)
})
}).catch(reject)
})

@@ -412,9 +617,31 @@ }

- `loadData(true)`:首次或强制加载数据。
- `reloadData(true)`:重新加载数据,通常在新增、修改后调用。
- `formatList(list, total)`:把接口列表写入 `$list`,并同步分页总数。
- `getSearch()`:获取搜索区当前值。
- `getPage()` / `getPageSize()`:获取当前分页参数。
- `triggerMethod(method, args, option)`:通过状态机触发业务方法。
| 方法 | 作用 |
|---|---|
| `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 } // 同时重置某些模块
})
```
## 业务回调

@@ -430,4 +657,7 @@

changeData?: (this: ComplexList, targetData, originData) => Promise<any>
editData?: (this: ComplexList, targetData, originData) => Promise<any>
deleteData?: (this: ComplexList, targetData) => Promise<any>
multipleDeleteData?: (this: ComplexList, choiceList) => Promise<any>
refreshData?: (this: ComplexList, targetData) => Promise<any>
updateData?: (this: ComplexList) => Promise<any>
exportData?: (this: ComplexList) => Promise<any>

@@ -438,7 +668,7 @@ importData?: (this: ComplexList, file: File) => Promise<any>

回调内可直接访问 `this.$list`、`this.reloadData()`、`this.getSearch()` 等。
回调内可直接访问 `this.$list`、`this.reloadData()`、`this.getSearch()` 等。`buildData`/`changeData`/`deleteData` 等修改类方法成功后会自动触发 `dataChange` 生命周期。
## 表格菜单
业务页面通常通过 `componentsProps.table.menu.menu` 配置表格行操作。
业务页面通常通过 `componentsProps.table.menu.menu` 配置表格行操作:

@@ -450,5 +680,5 @@ ```ts

menu: [
{ prop: '$change', name: '编辑', color: 'link' },
{ prop: '$delete', name: '删除', color: 'danger' },
{ prop: '$info', name: '详情', color: 'link' }
{ prop: '$change', name: () => '编辑', color: 'link' },
{ prop: '$delete', name: () => '删除', color: 'danger' },
{ prop: '$info', name: () => '详情', color: 'link' }
]

@@ -462,6 +692,90 @@ }

- `$change`:打开编辑弹窗,并使用当前行作为原始数据。
- `$delete`:确认后调用 `listData.triggerMethod('deleteData', [targetData])`。
- `$info`:打开详情弹窗。
| prop | 行为 |
|---|---|
| `$change` | 打开编辑弹窗,使用当前行作为原始数据 |
| `$delete` | 确认后调用 `listData.triggerMethod('deleteData', [targetData])` |
| `$info` | 打开详情弹窗 |
## 字段转换:assign 与 collect
接口值与表单值往往存在差异,字典字段提供了两个转换点:
```ts
{
prop: 'switch',
// 接口值(number)→ 展示/表单值(boolean)
assign(value) {
return value === 1 ? true : false
},
// 表单值(boolean)→ 提交值(number)
collect(value) {
return value ? 1 : 0
}
}
```
- `assign`:在 `getData` 成功后、数据进入 `$list`/`$info`/表单前调用
- `collect`:在 `buildData`/`changeData` 提交前调用
对于多文件等场景,可结合 `isFile` 判断:
```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
{
prop: 'form',
name: '内嵌表单',
assign(value) { return value || { name: 'a', date: '2019-05-18' } },
collect(value) { return value },
dictionary: {
list: [
{
prop: 'name',
name: '名称',
mod: {
list: { width: 120 },
info: { $redirect: 'edit' },
edit: { type: 'input', required: true },
build: { $redirect: 'edit' },
change: { $redirect: 'edit' }
}
}
]
},
mod: {
list: { width: 300 },
info: { $redirect: 'edit' },
edit: {
grid: { // 栅格布局自定义
line: 1,
custom(data, position, _gridParse) {
if (position === 'label') return { span: 0 }
if (position === 'content') return { span: 24 }
return data
}
},
type: 'form',
required: true,
option: {}
},
build: { $redirect: 'edit' },
change: { $redirect: 'edit' }
}
}
```
## 级联列表

@@ -474,17 +788,28 @@

```ts
// 父列表
const parentData = new ComplexList({
prop: 'parentData',
module: {
dictionary: { list: [] },
dictionary: { list: [...] },
pagination: true
},
getData(this: ComplexList) {
return Promise.resolve({})
return new Promise((resolve, reject) => {
listApi.parentList.require({
...this.getSearch(),
page: this.getPage(),
size: this.getPageSize()
}).then(res => {
this.formatList(res.data.data.list, res.data.data.num)
resolve(res)
}).catch(reject)
})
}
})
// 子列表
const childData = new ComplexList({
prop: 'childData',
module: {
dictionary: { list: [] },
dictionary: { list: [...] },
pagination: true

@@ -494,6 +819,16 @@ },

const parentId = this.getExtra('parentId')
return Promise.resolve(parentId)
return new Promise((resolve, reject) => {
listApi.childList.require({
parentId,
page: this.getPage(),
size: this.getPageSize()
}).then(res => {
this.formatList(res.data.data.list, res.data.data.num)
resolve(res)
}).catch(reject)
})
}
})
// 触发子列表加载
const showSubList = function(targetData: any) {

@@ -505,11 +840,42 @@ childData.setExtra('parentId', targetData.id)

## 推荐实践
`$extra` 是不参与字典/搜索/分页的额外数据容器,通过 `setExtra` / `getExtra` / `clearExtra` 操作。
- 列表页优先用 `ComplexList` 作为唯一业务状态入口,UI 组件只消费它,不重复维护列表、分页和搜索状态。
- 字段配置优先集中在 `module.dictionary.list`,通过 `list`、`info`、`edit`、`build`、`change` 复用同一字段语义。
- 所有业务回调都返回 `Promise`,包括 `getData`、`buildData`、`changeData`、`deleteData`、自定义按钮 `click` 和上传 `upload`。
- 接口返回值进入表单前使用 `assign` / `parse`,提交前使用 `collect`,避免在 UI 组件中散落转换逻辑。
- 新增和修改表单大多可通过 `$redirect: 'edit'` 复用配置,只在字段行为确实不同的时候单独覆盖。
- 使用 `component-antd` 时,`data` 只负责无头数据协议,渲染细节交给 `ComplexQuickList`、`TableView`、`EditArea`、`InfoArea`。
## 全局配置与国际化
```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 泛型

@@ -537,5 +903,61 @@

`ComplexInfo<D>` 类似,用于详情页:
```ts
const infoData = new ComplexInfo<UserVO>({
prop: 'userInfo',
module: { dictionary: { list: [...] } },
getData(this: ComplexInfo<UserVO>) {
return fetch(`/api/users/${this.$extra.id}`).then(r => r.json()).then(res => {
this.formatInfo(res.data)
return res
})
}
})
infoData.$info // 类型为 Partial<UserVO>
```
## SelectValue 与级联
```ts
import { SelectValue, CascaderValue } from '@complex-suite/data'
// 静态选择器
const select = new SelectValue({
list: [{ value: 1, label: 'A' }, { value: 2, label: 'B' }],
equal: (a, b) => a.value === b.value, // 自定义相等判断
hidden: true // 启用隐藏能力
})
// 级联选择器(cascader 指定子字段名)
const cascader = new CascaderValue({
list: [
{
value: 'zhejiang', label: '浙江',
children: [
{ value: 'hangzhou', label: '杭州' },
{ value: 'ningbo', label: '宁波' }
]
}
],
cascader: 'children'
})
```
## 推荐实践
- 列表页优先用 `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`。
## 依赖
- `@complex-suite/utils`:核心工具函数和基类(Life、Watcher)。
- `@complex-suite/utils`:核心工具函数和基类(Life、Watcher、setProp 等)。
- `@complex-suite/plugin`:布局和通知系统。

@@ -43,3 +43,3 @@ import { Life } from '@complex-suite/utils'

this.$storage = new StorageValue(initOption.storage, this._getConstructorName())
this.$storage.push('list', {
this.$storage.register('list', {
init: (value) => {

@@ -46,0 +46,0 @@ this.list = [...value]

@@ -86,3 +86,3 @@ import { getNum } from "@complex-suite/utils"

}
nextTimer?: number
nextTimer?: ReturnType<typeof setTimeout>
percent: number

@@ -403,13 +403,13 @@ data: {

// drag end ---
moveBackward() {
protected _moveByDirection(direction: directionProp) {
const num = 1000 / this.speed.current
const offset = num * 5
this.setCurrentByOffset('backward', offset)
this.setCurrentByOffset(direction, offset)
this.$start()
}
moveBackward() {
this._moveByDirection('backward')
}
moveForward() {
const num = 1000 / this.speed.current
const offset = num * 5
this.setCurrentByOffset('forward', offset)
this.$start()
this._moveByDirection('forward')
}

@@ -428,3 +428,3 @@ setCurrentByOffset (direction: directionProp, offset: number) {

this.$next()
}, this.getSpeed()) as unknown as number
}, this.getSpeed())
}

@@ -537,7 +537,14 @@ }

countPercent() {
this.percent = getNum(this.$index.current.data * 100 / ( this.data.maxIndex), 'round', 0)
if (this.data.maxIndex <= 0) {
this.percent = 0
return
}
this.percent = getNum(this.$index.current.data * 100 / this.data.maxIndex, 'round', 0)
}
setPercent(data: number) {
this.percent = data
const index = getNum(this.percent / 100 * ( this.data.maxIndex), 'round', 0)
if (this.data.maxIndex <= 0) {
return
}
const index = getNum(this.percent / 100 * this.data.maxIndex, 'round', 0)
this.$setIndex(index)

@@ -544,0 +551,0 @@ }

@@ -145,4 +145,4 @@ import BaseData from "./BaseData"

}
updateImmerdiate(...args: Parameters<UpdateData['immerdiate']>) {
return this.$module.update?.immerdiate(...args)
updateImmediate(...args: Parameters<UpdateData['immediate']>) {
return this.$module.update?.immediate(...args)
}

@@ -149,0 +149,0 @@ clearUpdate(...args: Parameters<UpdateData['clear']>) {

@@ -34,3 +34,3 @@ import { Life, upperCaseFirstChar } from '@complex-suite/utils'

this.$storage = new StorageValue(initOption.storage, this._getProp())
this.$storage.push('extra', {
this.$storage.register('extra', {
init: (value) => {

@@ -37,0 +37,0 @@ this.$extra = { ...value, ...this.$extra }

import DefaultSimpleEdit from "./DefaultSimpleEdit"
import type { DefaultSimpleEditInitOption } from "./DefaultSimpleEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { payloadType, ButtonValue } from "../types"

@@ -5,0 +5,0 @@

import DefaultSimpleEdit from "./DefaultSimpleEdit"
import type { DefaultSimpleEditInitOption } from "./DefaultSimpleEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { ButtonEditOption } from "./ButtonEdit"

@@ -5,0 +5,0 @@ import dataConfig from "../../dataConfig"

import DefaultSimpleEdit from "./DefaultSimpleEdit"
import type { DefaultSimpleEditInitOption } from "./DefaultSimpleEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { ContentEditOption } from "../types"

@@ -5,0 +5,0 @@

import { upperCaseFirstChar } from "@complex-suite/utils"
import DefaultEdit from "./DefaultEdit"
import type { DefaultEditInitOption } from "./DefaultEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { customModelType, DefaultCustomEditInitOption } from "../types"

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

import SimpleDateEdit from "./SimpleDateEdit"
import type { SimpleDateEditInitOption } from "./SimpleDateEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"

@@ -5,0 +5,0 @@ export interface DateEditInitOption extends SimpleDateEditInitOption {

import SimpleDateEdit from "./SimpleDateEdit"
import type { SimpleDateEditInitOption } from "./SimpleDateEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import { dataLocale } from '../locale'

@@ -5,0 +5,0 @@

import { deepCloneData, hasProp, isArray, isComplex } from "@complex-suite/utils"
import DefaultSimpleEdit from "./DefaultSimpleEdit"
import type { DefaultSimpleEditInitOption } from "./DefaultSimpleEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { ruleOption } from "../types"

@@ -41,3 +41,3 @@ import { dataLocale } from '../locale'

static $defaultPlaceholder = (name: string) => dataLocale.t('placeholder.input', { name })
static $defaultRuleMessage = () => '' as string
static $defaultRuleMessage = () => ''
static $parseRuleList = function($constructor: typeof DefaultEdit<boolean>, target: DefaultEdit<boolean>, formData: Record<PropertyKey, any>, _type?: string) {

@@ -44,0 +44,0 @@ const ruleMessage = target.ruleMessage?.() || target.placeholder?.()

import DefaultMod from "./DefaultMod"
import type { DefaultModInitOption } from "./DefaultMod"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { GridOption } from "../lib/GridParse"

@@ -5,0 +5,0 @@ import { createGridOption } from "../lib/GridParse"

import DefaultMod from "./DefaultMod"
import type { DefaultModInitOption } from "./DefaultMod"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { alignType } from "../types"

@@ -5,0 +5,0 @@

import type { loadFunctionType } from "../types"
import DefaultEdit from "./DefaultEdit"
import type { DefaultEditInitOption } from "./DefaultEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import { StatusItem, type DataWithSimpleLoad, StatusValue } from "../module/StatusData"

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

import SimpleData from "../data/SimpleData"
import type { SimpleDataInitOption, renderType, ArrayValueDataType, LocalValueInitOption, collapseType, DefaultModOrder, functionType, TipValueInitOption } from "../types"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import TipValue from "../lib/TipValue"

@@ -39,3 +39,2 @@ import type { LocalValue } from "../lib/AttrsValue"

$order?: DefaultModOrder
// eslint-disable-next-line @typescript-eslint/no-unused-vars
constructor(initOption: DefaultModInitOption, parent?: DictionaryValue, modName?: string) {

@@ -42,0 +41,0 @@ super(initOption)

import PaginationData from "../module/PaginationData"
import DefaultLoadEdit from "./DefaultLoadEdit"
import type { DefaultLoadEditInitOption } from "./DefaultLoadEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import SelectValue from "../lib/SelectValue"

@@ -6,0 +6,0 @@ import CascaderValue from "../lib/CascaderValue"

import DefaultInfo from "./DefaultInfo"
import type { DefaultInfoInitOption } from "./DefaultInfo"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { functionType } from "../types"

@@ -5,0 +5,0 @@ import InterfaceValue from "../lib/InterfaceValue"

import DefaultEdit from "./DefaultEdit"
import type { DefaultEditInitOption } from "./DefaultEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { fileOption, multipleFileOption } from "../types"

@@ -5,0 +5,0 @@ import { dataLocale } from '../locale'

import DefaultEdit from "./DefaultEdit"
import type { DefaultEditInitOption } from "./DefaultEdit"
import DefaultInfo from "./DefaultInfo"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import GridParse from "../lib/GridParse"

@@ -6,0 +6,0 @@ import ObserveList from "./ObserveList"

import DefaultEdit from "./DefaultEdit"
import type { DefaultEditInitOption } from "./DefaultEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { InputEditOption } from "../types"

@@ -5,0 +5,0 @@

import DefaultEdit from "./DefaultEdit"
import type { DefaultEditInitOption } from "./DefaultEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { InputNumberEditOption } from "../types"

@@ -5,0 +5,0 @@

import DefaultEdit from "./DefaultEdit"
import type { DefaultEditInitOption } from "./DefaultEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { ListEditOption } from "../types"

@@ -5,0 +5,0 @@ import ObserveList from "./ObserveList"

import DefaultSelectEdit from "./DefaultSelectEdit"
import type { DefaultSelectEditInitOption } from "./DefaultSelectEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { SelectValueType, CascaderValueType, SelectEditOption, searchInitOption, searchOption } from '../types'

@@ -5,0 +5,0 @@

@@ -5,3 +5,3 @@ import { isArray } from '@complex-suite/utils'

import type { DefaultEditInitOption } from "./DefaultEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { functionType } from "../types"

@@ -198,3 +198,3 @@ import { dataLocale } from '../locale'

const $defaultOption = $constructor.$defaultOption
const format = option.format || option.time ? $defaultOption.formatWithTime : $defaultOption.format
const format = option.format || (option.time ? $defaultOption.formatWithTime : $defaultOption.format)
this.$option = {

@@ -201,0 +201,0 @@ format: format,

import DefaultEdit from "./DefaultEdit"
import type { DefaultEditInitOption } from "./DefaultEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"

@@ -5,0 +5,0 @@ export interface SwitchEditInitOption extends DefaultEditInitOption<false> {

import DefaultEdit from "./DefaultEdit"
import type { DefaultEditInitOption } from "./DefaultEdit"
import DictionaryValue from "../lib/DictionaryValue"
import type DictionaryValue from "../lib/DictionaryValue"
import type { TextAreaEditOption } from "../types"

@@ -22,3 +22,3 @@

super(initOption, parent, modName)
this.type = 'textArea'
this.type = initOption.type
const option = initOption.option || {}

@@ -25,0 +25,0 @@ const $defaultOption = (this.constructor as typeof TextAreaEdit).$defaultOption

@@ -47,3 +47,3 @@ import type { ArrayValueDataType } from '../types'

*/
isHide(prop: PropertyKey) {
isHidden(prop: PropertyKey) {
return this.$hidden.has(prop)

@@ -65,3 +65,3 @@ }

getStatus(prop: PropertyKey) {
if (this.isHide(prop)) {
if (this.isHidden(prop)) {
return 'hide'

@@ -189,3 +189,3 @@ } else if (this.isFrozen(prop)) {

show(prop: PropertyKey) {
if (this.isHide(prop)) {
if (this.isHidden(prop)) {
const value = this.$hidden.get(prop)

@@ -215,3 +215,3 @@ if (value) {

*/
thaw(prop: PropertyKey) {
unfreeze(prop: PropertyKey) {
if (this.isFrozen(prop)) {

@@ -218,0 +218,0 @@ const value = this.$frozen.get(prop)

@@ -65,7 +65,7 @@

}
pushEvent(prop: string, event?: (...args: any[]) => any, type: 'before' | 'after' = 'after') {
pushEvent(prop: string, event?: (...args: any[]) => any, order: 'before' | 'after' = 'after') {
if (event) {
if (this.on[prop]) {
const lastEvent = this.on[prop]
this.on[prop] = type === 'after' ? function(...args) {
this.on[prop] = order === 'after' ? function(...args) {
lastEvent(...args)

@@ -72,0 +72,0 @@ return event(...args)

@@ -426,11 +426,4 @@ import { isExist, exportMsg, getComplexProp, trimData, isArray } from '@complex-suite/utils'

if (mod instanceof DefaultLoadEdit) {
return new Promise((resolve, reject) => {
mod.loadData().finally(() => {
this._setParseValue(mod, payload).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
})
// loadData 的结果不影响解析流程,无论加载成功/失败都继续执行 _setParseValue
return mod.loadData().catch(() => {}).then(() => this._setParseValue(mod, payload))
} else {

@@ -437,0 +430,0 @@ return this._setParseValue(mod, payload)

import { isFile } from "@complex-suite/utils"
import type { fileDataType, fileValueType } from "../types"
export class FileValue {
class FileValue {
value!: fileDataType['value']

@@ -66,3 +66,3 @@ name!: fileDataType['name']

}
truncation(size: number) {
truncate(size: number) {
if (this.value.length > size) {

@@ -78,1 +78,3 @@ const deletes = this.value.splice(size, this.value.length - size)

}
export default FileValue

@@ -46,3 +46,3 @@ import { exportMsg, getRandomNum, storage } from '@complex-suite/utils'

control: Record<string, controlType>
timer: undefined | number // 定时器
timer: undefined | ReturnType<typeof setTimeout> // 定时器
constructor(initOption: StorageValueInitOption, prop: string) {

@@ -61,3 +61,3 @@ super()

}
push(prop: string, data: controlType, replace?: boolean) {
register(prop: string, data: controlType, replace?: boolean) {
if (!this.control[prop] || replace) {

@@ -119,3 +119,3 @@ this.control[prop] = data

parent.triggerLife('reloadStorage', parent)
}, this.offset * (1 + offsetRate / 50)) as unknown as number
}, this.offset * (1 + offsetRate / 50))
}

@@ -122,0 +122,0 @@ return true

@@ -49,9 +49,7 @@ import BaseData from '../data/BaseData'

/**
* 重置操作
* @param {boolean} force 重置判断值
* 实际重置逻辑
* @param {boolean} _force 重置判断值
*/
reset(force?: boolean) {
if (force !== false) {
this.setData([], [])
}
protected _doReset(_force?: boolean) {
this.setData([], [])
}

@@ -58,0 +56,0 @@ /**

@@ -288,3 +288,2 @@ import { Life } from "@complex-suite/utils"

createData(originData: Record<PropertyKey, any>, originFrom = 'list', useSetData = false, depth = 0) {
// --------
return !this.$complex.assign ? DictionaryData.$formatData(this, originData, originFrom, useSetData, depth) : DictionaryData.$assignData(this, {}, originData, originFrom, useSetData, depth)

@@ -291,0 +290,0 @@ }

@@ -10,3 +10,3 @@ import Data from '../data/Data'

static $name = 'PromiseData'
data: PromiseDataValueType
data: PromiseDataValueType
constructor(initOption?: PromiseDataInitData) {

@@ -23,3 +23,3 @@ super()

*/
$initData(data: PromiseDataValueType = {}) {
$initData(data: PromiseDataValueType = {}) {
for (const n in data) {

@@ -26,0 +26,0 @@ this.setData(n, data[n])

@@ -1,2 +0,2 @@

import Data from './../data/Data'
import Data from '../data/Data'
import BaseData from '../data/BaseData'

@@ -77,3 +77,17 @@ import type { LocalValue } from "../lib/AttrsValue"

}
abstract reset(force?: boolean): void
/**
* 重置操作:force !== false 时执行实际重置逻辑
* 子类应实现 _doReset 而非重写此方法
* @param {boolean} force 重置判断值,false 时跳过
*/
reset(force?: boolean) {
if (force !== false) {
this._doReset(force)
}
}
/**
* 实际重置逻辑,由子类实现
* @param {boolean} force 重置判断值
*/
protected abstract _doReset(force?: boolean): void

@@ -80,0 +94,0 @@ /**

@@ -304,2 +304,3 @@ import { deepCloneData, getType } from "@complex-suite/utils"

this.offLife('updated', target._getId('searchUpdated'))
this.offLife('inited', target._getId('searchInited'))
}

@@ -306,0 +307,0 @@ }

@@ -51,9 +51,7 @@ import ResetData from './ResetData'

/**
* 重置操作
* @param {boolean} force 重置判断值
* 实际重置逻辑
* @param {boolean} _force 重置判断值
*/
reset(force?: boolean) {
if (force !== false) {
this.setData(undefined, undefined)
}
protected _doReset(_force?: boolean) {
this.setData(undefined, undefined)
}

@@ -60,0 +58,0 @@ }

@@ -24,7 +24,7 @@ import { isPromise } from '@complex-suite/utils'

operate: boolean
immerdiate: boolean
immediate: boolean
}
index: number
offset: number
timer?: number
timer?: ReturnType<typeof setTimeout>
trigger?: triggerType

@@ -37,3 +37,3 @@ constructor(initOption: UpdateDataInitOption) {

operate: false, // 触发操作判断值,true说明trigger正在进行中
immerdiate: false // 立即同步操作判断值,true说明正在进行强制同步操作
immediate: false // 立即同步操作判断值,true说明正在进行强制同步操作
}

@@ -146,3 +146,3 @@ this.index = 0

this.clear(true)
this.load.immerdiate = false
this.load.immediate = false
if (offset == undefined) {

@@ -156,3 +156,3 @@ offset = this.offset

this._trigger(this.next, this.getIndex())
}, this._countOffset(offset)) as unknown as number
}, this._countOffset(offset))
}

@@ -167,3 +167,3 @@ /**

this.triggerLife('triggered', this, offset)
if (this.load.update && !this.load.immerdiate) {
if (this.load.update && !this.load.immediate) {
if (offset !== false) {

@@ -190,3 +190,3 @@ const checkRes = this.check(this.getIndex())

}
} else if (this.load.immerdiate) {
} else if (this.load.immediate) {
this.$start(0)

@@ -198,6 +198,6 @@ }

*/
immerdiate() {
immediate() {
// 正在更新中的在更新完成直接触发下一次更新
if (this.load.operate) {
this.load.immerdiate = true
this.load.immediate = true
} else {

@@ -204,0 +204,0 @@ // 不在更新中直接触发下一次更新

// dictionary 领域类型集中管理
import type { SimpleType } from '@complex-suite/utils'
import type { MenuValue } from './common'
import type DictionaryValue from '../lib/DictionaryValue'
/**
* Dictionary 子模块构造函数通用参数列表
* 每个模块的构造函数都遵循:(initOption, parent?, modName?) 签名
*/
export type DictionaryCtorArgs<InitOption = any> = [
initOption: InitOption,
parent?: DictionaryValue,
modName?: string
]
// 来自 DefaultMod.ts

@@ -6,0 +17,0 @@ export type collapseType = 0 | 1 | 2 // 折叠判断值,默认 0 不展示 1 推荐展示 2必须展示

+86
-0

@@ -34,3 +34,89 @@ # @complex-suite/data 开发待办

---
<!-- ===== 第二阶段:已识别但延后审核的待办项 ===== -->
## 延后审核(2026-07-06 识别)
> 以下问题已识别但暂不修改,记录到此等待后续审核。
### 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 生命周期间隔调用,统一初始化流程。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
### R2 — ArrayValue hide/freeze、show/thaw、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` 不同。
- **建议方案**:抽取 `_toggleToMap(prop, targetMap, statusCheck)` 和 `_removeFromItem(value)` 公共方法。
- **结果影响**:ArrayValue 类可减少约 60 行重复代码,三组方法只需约 20 行委派代码。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
### 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。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
### R5 — `$onCreatedLife('BaseDataCreated', ...)` 注册/注销模式重复
- **位置**:[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 生命周期注册/注销样板代码。
- **严重度**:🔴 高
- **状态**:⏳ 待审核
### R6 — DictionaryData $assignData / $formatData 重复
- **位置**:[DictionaryData.ts#L81-95,96-110](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/module/DictionaryData.ts#L81)
- **问题**:静态方法 `$assignData` 和 `$formatData` 结构几乎完全相同,仅调用方法名和参数个数不同。
- **建议方案**:抽公共 `$processData(..., mode: 'assign' | 'format')`。
- **结果影响**:两个 ~15 行的方法合并为一个带 mode 参数的方法,减少维护成本。
- **严重度**:🟡 中
- **状态**:⏳ 待审核
### 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 提供 `protected get $constructor()` 属性,子类共享。
- **结果影响**:消除 10+ 处硬编码的类型断言,统一通过 `this.$constructor` 访问静态属性。
- **严重度**:🟡 中
- **状态**:⏳ 待审核
### 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?` 不同。
- **建议方案**:抽 `BaseEditRuntime` 接口,FormEdit/ListEdit 各自扩展。
- **结果影响**:运行时结构统一,便于后续新增编辑类型复用。
- **严重度**:🟢 低
- **状态**:⏳ 待审核
### T5 — BaseData._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 类型。
- **结果影响**:🟡 中(类型安全)。会影响所有 `triggerMethod` 调用链,需慎重。
- **状态**:⏳ 待审核
### 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`。git log 显示从 init commit 就存在但从未被实际使用。
- **建议方案**:等待查找这个函数为什么存在。若确认废弃则删除整个文件;若需要则加入 ModuleMap。
- **结果影响**:🔴 高(死代码占用维护心智成本)。
- **状态**:⏳ 等待查找函数存在原因
---
## 问题清单

@@ -37,0 +123,0 @@