🎩 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.8
to
5.0.10
+14
-0
history.md

@@ -0,1 +1,15 @@

### `5.0.10`
- docs: 更新 Todo.md,补充 B/C/D 方案审核细节,新增 F(Capability Composer)重构方案及方案总览图。
- fix(test): vitest 配置显式设置包级 `root`,消除 monorepo 根目录执行时的 cwd 隐式依赖。
### `5.0.9`
- fix(dictionary): 全局修正 `dictionray` 拼写错误为 `dictionary`,涉及 `DictionaryValue.ts` 类型定义及消费者代码(`list/base`、`list/cascade`)。
- fix(dictionary): 删除 `ArrayValue.pushByIndex` 中错误的 `@warning [严重 Bug]` 注释。
- refactor(dictionary): `$assignData` / `$formatData` 抽取私有方法 `_buildDataValue`,消除 80% 重复代码,保证性能(payload 仅 assign 时创建,originProp 仅调一次)。
- refactor(module): `ResetData` 添加 `destroy` 默认实现,`ChoiceData` / `SortData` 删除重复的 `destroy`。
- refactor(types): `InterfaceValue.toString` 用 typeof 类型守卫替代双重断言;`LayoutParse` 删除冗余类型断言。
- refactor(build): `index.ts` 导出按 7 组(core/data/lib/module/dictionary/config/types)分组重排。
- docs: `_syncData` 添加 TSDoc 说明设计意图;`triggerMethod` / `triggerMethodWithOperateAndStatus` 添加集中化设计注释;`StorageValue.init` 添加返回值语义注释。
- docs: 重写 `README.md` 的「快速开始」和「架构概览」。
### `5.0.8`

@@ -2,0 +16,0 @@ - docs: 更新子包 README 安装命令,修正旧包名 `complex-*` 为 `@complex-suite/*`。

+27
-1

@@ -0,1 +1,15 @@

/**
* @complex-suite/data 包入口
*
* 导出按模块分组:
* 1. core/ - 业务数据类(ComplexList / ComplexInfo / SelectData / TrackData)
* 2. data/ - 数据类继承链(Data → SimpleData → DefaultData → BaseData → ComplexData)
* 3. lib/ - 工具类与值对象(DictionaryValue / ArrayValue / FormValue / GridParse 等)
* 4. module/ - 可复用功能模块(SearchData / PaginationData / ChoiceData / SortData 等)
* 5. dictionary/- 字典项实现(DefaultList / DefaultEdit / InputEdit / SelectEdit 等)
* 6. config/ - 全局配置与国际化(dataConfig / dataLocale)
* 7. types/ - 类型定义
*/
/* ============================== core/ 业务数据类 ============================== */
import ComplexInfo, { type ComplexInfoInitOption } from './src/core/ComplexInfo'

@@ -6,2 +20,3 @@ import ComplexList, { type ComplexListInitOption } from './src/core/ComplexList'

/* ============================== data/ 数据类继承链 ============================== */
import Data, { type BufferType } from './src/data/Data'

@@ -13,2 +28,3 @@ import SimpleData from './src/data/SimpleData'

/* ============================== lib/ 工具类与值对象 ============================== */
import ArrayValue from './src/lib/ArrayValue'

@@ -29,2 +45,3 @@ import AttrsValue, { type LocalValue, createLocalValue } from './src/lib/AttrsValue'

/* ============================== module/ 可复用功能模块 ============================== */
import ChoiceData from './src/module/ChoiceData'

@@ -41,2 +58,3 @@ import DependData, { type dependDataType, type dependBind, type dependBindType, DependValue, type DependValueInitOption, type DependDataInitOption } from './src/module/DependData'

/* ============================== dictionary/ 字典项实现 ============================== */
import DefaultList, { type DefaultListInitOption } from './src/dictionary/DefaultList'

@@ -67,2 +85,3 @@ import DefaultInfo, { type DefaultInfoInitOption } from './src/dictionary/DefaultInfo'

/* ============================== config/ 全局配置与国际化 ============================== */
import dataConfig from './dataConfig'

@@ -72,6 +91,8 @@ import { dataLocale } from './src/locale'

/* ============================== types/ 类型定义 ============================== */
export type * from './src/types'
/* ============================== 导出汇总 ============================== */
export {
/* --- core/ 业务数据类 --- */
ComplexInfo,

@@ -85,2 +106,3 @@ type ComplexInfoInitOption,

/* --- data/ 数据类继承链 --- */
Data,

@@ -99,2 +121,3 @@ type BufferType,

/* --- lib/ 工具类与值对象 --- */
ArrayValue,

@@ -132,2 +155,3 @@ AttrsValue,

/* --- module/ 可复用功能模块 --- */
ChoiceData,

@@ -173,2 +197,3 @@ DependData,

/* --- dictionary/ 字典项实现 --- */
DefaultList,

@@ -228,2 +253,3 @@ type DefaultListInitOption,

/* --- config/ 全局配置与国际化 --- */
dataConfig,

@@ -230,0 +256,0 @@ type DataMessages,

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

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

"dependencies": {
"@complex-suite/utils": "3.0.8",
"@complex-suite/plugin": "5.0.8"
"@complex-suite/utils": "3.0.10",
"@complex-suite/plugin": "5.0.10"
},

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

+477
-72

@@ -1,100 +0,505 @@

# Complex Data
# @complex-suite/data 快速使用
`@complex-suite/data` 是一个高度可组合、可扩展、由数据驱动的 UI 逻辑层框架。它旨在将复杂前端页面(尤其是表单、列表和数据表格)的状逻辑、数据流和交互行为,从具体的 UI 框架中解耦出来,通过一份结构化的数据(我们称之为“字典”)来进行定义和管理。
`@complex-suite/data` 是 complex-suite 的核心数据层。它不负责渲染 UI,而是负责描述业务页面的数据结构、搜索条件、字典字段、分页、选择、编辑、详情、异步加载和业务操作。
## 核心理念
在当前 monorepo 中,它位于依赖链中间:`utils → plugin → data → component → component-antd`。实际业务页面通常通过 `data` 创建无头数据模型,再交给 `component-antd` 的 `ComplexQuickList`、`ComplexQuickCascade` 等组件渲染。
- **字典驱动 (Dictionary-driven)**: UI 的结构、行为和状态不再通过手写组件模板来定义,而是通过一份可序列化的 JavaScript 对象(字典)来描述。
- **关注点分离 (Separation of Concerns)**: 框架本身是“无头 (Headless)”的,它只负责管理数据状态和业务逻辑,不关心最终的 UI 渲染。您可以将它与任何 UI 框架(如 Vue, React, Svelte 等)轻松集成。
- **组合与继承 (Composition & Inheritance)**: 通过设计精良的类继承链和灵活的功能模块组合,可以轻松构建出从简单输入框到复杂可编辑列表的任意组件。
- **内置响应式 (Built-in Reactivity)**: 框架内置了精简的响应式系统,能够自动处理组件之间的依赖关系、数据联动和状态更新。
## 安装
## 主要功能
```bash
pnpm add @complex-suite/data
```
- **丰富的组件定义**: 内置了对输入框、选择器、日期选择器、文件上传、嵌套表单、可编辑列表等多种常见业务组件的逻辑支持。
- **强大的数据加载与缓存**: 组件可以配置异步数据加载逻辑,并内置了对 `localStorage` 的缓存支持,能够轻松实现远程搜索、数据持久化等功能。
- **灵活的布局系统**: 通过 `GridParse` 栅格解析器,可以方便地定义组件的布局。
- **声明式 API**: 提供了大量便捷的声明式 API,如通过简单配置即可实现复杂的日期禁用逻辑。
- **高度可扩展**: 设计了强大的自定义组件工厂 (`createCustomEdit`),可以无缝集成任何非标准组件,并让其享受到框架的数据管理和响应式能力。
- **健壮的类型安全**: 项目由 TypeScript 编写,提供了严谨的类型定义,尤其在泛型应用上保证了复杂数据结构下的类型安全。
## 核心概念
## 安装
- `ComplexList`:列表页主数据模型,维护 `$list`、加载状态、搜索、分页、选择、字典和增删改查方法。
- `ComplexInfo`:详情页数据模型,维护 `$info`,常用于详情弹窗或详情页。
- `module.dictionary`:字段字典,统一描述表格列、详情字段、编辑表单、创建表单和搜索字段。
- `module.search`:搜索区配置,包含搜索字段和顶部菜单按钮。
- `module.pagination`:分页模块,提供 `getPage()`、`getPageSize()`、`setPageCount()` 等能力。
- `SelectValue` / `SelectEdit`:选择器数据源与选择器字典项,支持静态选项和远程加载。
- `ButtonEdit`:按钮字典项,常用于搜索区菜单、自定义操作和上传入口。
```bash
npm install @complex-suite/data
## 模块系统
`ComplexList` 通过 `module` 挂载功能模块,每个模块独立管理一类能力:
| 模块 | 类 | 作用 |
|---|---|---|
| `dictionary` | DictionaryData | 字典管理(必填) |
| `search` | SearchData | 检索表单与菜单 |
| `pagination` | PaginationData | 分页参数与总数 |
| `choice` | ChoiceData | 多选状态 |
| `sort` | SortData | 排序参数 |
| `update` | UpdateData | 定时刷新 |
模块通过 `installData` / `uninstallData` 安装卸载,触发 `_install` / `_uninstall` 钩子。
## 生命周期
`ComplexList` 内置 `Life` 系统,常用生命周期:
| 名称 | 触发时机 |
|---|---|
| `beforeLoad` | `loadData` 开始 |
| `loaded` | 加载成功 |
| `loadFail` | 加载失败 |
| `beforeSearch` | `setSearch` 校验前 |
| `searched` | 检索成功 |
| `beforeUpdate` | 字典更新前 |
| `reseted` | `reset` 后 |
监听方式:
```ts
listData.onLife('loaded', {
id: 'myHandler',
handler: (lifeValue, res) => {
console.log('加载完成', res)
}
})
listData.offLife('loaded', 'myHandler')
```
## 快速开始
## 基础列表用法
以下是一个定义简单登录表单的例子:
下面示例来自真实业务页面 `src/pages/index/views/list/base/index.vue` 的标准模式:页面创建 `ComplexList`,配置搜索、字典、分页和业务接口,然后传给 `ComplexQuickList` 渲染。
```typescript
import { ComplexData } from '@complex-suite/data';
```vue
<template>
<ComplexQuickList
:list-data="mainData"
:components="['spin', 'search', 'table', 'edit', 'info']"
:simple-table="false"
:components-props="componentsProps"
@search="onSearch"
@table="onTable"
/>
</template>
// 1. 定义表单字典
const loginDictionary = [
{
prop: 'username',
name: '用户名',
type: 'input',
required: true
<script lang="ts">
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 listApi from '@/api/listApi'
export default defineComponent({
name: 'RecordList',
components: {
ComplexQuickList
},
{
prop: 'password',
name: '密码',
type: 'input',
option: {
password: true
},
required: true
setup() {
const select = new SelectValue({
list: [
{ value: 0, label: '选项1' },
{ value: 1, label: '选项2' }
]
})
const mainData = new ComplexList({
prop: 'mainData',
module: {
search: {
menu: {
list: [
'build',
'delete',
new ButtonEdit({
prop: 'choice2',
type: 'button',
option: {
name: '已选择2条时可用',
disabled(payload: any) {
return payload.choice !== 2
},
click() {
return Promise.resolve({})
}
}
})
]
},
list: [
{
prop: 'keyword',
name: '关键字',
mod: {
search: {
$format: 'edit',
type: 'input'
}
}
},
{
prop: 'status',
name: '状态',
mod: {
search: {
$format: 'edit',
type: 'select',
select: select
}
}
}
]
},
dictionary: {
list: [
{
prop: 'id',
name: 'ID',
mod: {
list: { width: 80 },
info: {}
}
},
{
prop: 'name',
name: '名称',
mod: {
list: {},
info: {},
edit: { type: 'input', required: true },
build: { $redirect: 'edit' },
change: { $redirect: 'edit' }
}
},
{
prop: 'status',
name: '状态',
mod: {
list: {},
info: {},
edit: { type: 'select', select: select },
build: { $redirect: 'edit' },
change: { $redirect: 'edit' }
}
}
]
},
pagination: true
},
getData(this: ComplexList) {
return new Promise((resolve, reject) => {
const postData = {
...this.getSearch(),
page: this.getPage(),
size: this.getPageSize()
}
listApi.baseList.require(postData).then(res => {
this.formatList(res.data.data.list, res.data.data.num)
resolve(res)
}).catch(err => {
reject(err)
})
})
},
buildData(this: ComplexList, targetData) {
return new Promise((resolve, reject) => {
listApi.baseBuild.require(targetData).then(res => {
this.reloadData(true)
resolve(res)
}).catch(err => {
reject(err)
})
})
},
changeData(this: ComplexList, targetData, originData) {
return new Promise((resolve, reject) => {
targetData.id = originData.id
listApi.baseChange.require(targetData).then(res => {
this.reloadData(true)
resolve(res)
}).catch(err => {
reject(err)
})
})
},
deleteData(this: ComplexList, targetData) {
return new Promise((resolve) => {
const index = this.$list.indexOf(targetData)
if (index > -1) {
this.$list.splice(index, 1)
}
resolve({})
})
}
})
const onSearch = function(_prop: string) {}
const onTable = function(_prop: string, _payload: tablePayload) {}
return {
mainData: mainData,
onSearch: onSearch,
onTable: onTable,
componentsProps: {
table: {
menu: {
menu: [
{ prop: '$change', name: '编辑', color: 'link' },
{ prop: '$delete', name: '删除', color: 'danger' },
{ prop: '$info', name: '详情', color: 'link' }
]
}
},
edit: {}
} as componentsProps
}
},
{
prop: 'loginBtn',
name: '登录',
type: 'button',
option: {
type: 'primary',
onClick: (payload) => {
// payload.form 是表单实例
payload.form.validate().then(data => {
console.log('表单数据:', data);
// 在这里提交数据...
});
mounted() {
this.mainData.loadData(true)
}
})
</script>
```
## 字典字段配置
`module.dictionary.list` 是列表页的核心协议。一个字段可以同时服务多个场景:
```ts
{
prop: 'name',
name: '名称',
mod: {
list: {},
info: {},
edit: { type: 'input', required: true },
build: { $redirect: 'edit' },
change: { $redirect: 'edit' }
}
}
```
- `list`:表格列配置,供 `TableView` 渲染列。
- `info`:详情字段配置,供详情区域展示。
- `edit`:通用编辑配置。
- `build`:新增表单配置,可通过 `$redirect: 'edit'` 复用编辑配置。
- `change`:修改表单配置,可通过 `$redirect: 'edit'` 复用编辑配置。
- `assign`:后端值进入展示/表单前的转换函数。
- `collect`:表单值提交接口前的转换函数。
## 搜索区配置
`module.search` 分为菜单和搜索字段两部分。
```ts
search: {
menu: {
list: [
'build',
'delete',
new ButtonEdit({
prop: 'custom',
type: 'button',
option: {
name: '自定义按钮',
click() {
return Promise.resolve({})
}
}
})
]
},
list: [
{
prop: 'keyword',
name: '关键字',
mod: {
search: {
$format: 'edit',
type: 'input'
}
}
}
]
}
```
`ComplexQuickList` 已内置处理这些菜单:
- `$search`:调用 `listData.setSearch()`,更新搜索条件并触发列表加载。
- `$reset`:调用 `listData.resetSearch()`,重置搜索条件。
- `$refresh`:调用 `listData.reloadData({ data: true, sync: true })`。
- `$build`:打开新增弹窗。
- `$delete`:触发批量删除。
- `$info`:打开详情弹窗。
- `$export`:调用 `listData.triggerMethod('exportData')`。
## 远程选择器
选择器可以使用静态 `SelectValue`,也可以在 `SelectEdit.getData` 中远程加载选项。
```ts
{
prop: 'search',
name: '检索框',
mod: {
search: {
$format: 'edit',
type: 'select',
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({})
})
}
}
}
];
}
```
// 2. 创建 ComplexData 实例
const loginForm = new ComplexData({
dictionary: loginDictionary
});
如果远程接口有分页结果,可在 `getData` 中同时调用 `this.$pagination.setCount(total)`。
// 3. 在你的 UI 框架中渲染
// - 遍历 loginForm.getPageList() 获取渲染列表
// - 将每个组件的属性 (item.name, item.prop, item.$option 等) 传递给你的 UI 组件
// - 通过 item.setValue(newValue) 更新数据
// - 通过 item.getValue() 获取数据
// - 监听 item.$on('change', ...) 来响应数据变化
## 数据加载与提交
`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)
resolve(res)
}).catch(err => {
reject(err)
})
})
}
```
## 架构概览
常用方法:
- `src/data/`: 定义了项目的核心数据模型基类,如 `Data`, `SimpleData`, `ComplexData`。
- `src/module/`: 封装了可复用的功能模块,如 `SearchData`, `PaginationData`, `StatusData` 等。
- `src/core/`: 包含核心的业务逻辑,如 `ComplexInfo` (单条数据) 和 `ComplexList` (数据列表)。
- `src/lib/`: 提供了一系列工具类和值对象,如 `FormValue`, `SelectValue`, `GridParse` 等。
- `src/dictionary/`: 包含了所有内置组件的定义,是整个字典系统的核心实现。
- `loadData(true)`:首次或强制加载数据。
- `reloadData(true)`:重新加载数据,通常在新增、修改后调用。
- `formatList(list, total)`:把接口列表写入 `$list`,并同步分页总数。
- `getSearch()`:获取搜索区当前值。
- `getPage()` / `getPageSize()`:获取当前分页参数。
- `triggerMethod(method, args, option)`:通过状态机触发业务方法。
## 依赖
## 业务回调
- [`@complex-suite/utils`](https://github.com/MarAngle/@complex-suite/utils): 提供核心的工具函数和基类。
- [`@complex-suite/plugin`](https://github.com/MarAngle/@complex-suite/plugin): 提供布局和通知系统。
`ComplexList` 支持在初始化配置中定义业务回调,`this` 绑定到当前实例:
## 贡献
```ts
interface ComplexDataInitOption {
module: ModuleDataInitOption
getData?: (this: ComplexList) => Promise<any>
buildData?: (this: ComplexList, targetData) => Promise<any>
changeData?: (this: ComplexList, targetData, originData) => Promise<any>
deleteData?: (this: ComplexList, targetData) => Promise<any>
multipleDeleteData?: (this: ComplexList, choiceList) => Promise<any>
exportData?: (this: ComplexList) => Promise<any>
importData?: (this: ComplexList, file: File) => Promise<any>
}
```
欢迎提交问题 (issues) 和合并请求 (pull requests)。
回调内可直接访问 `this.$list`、`this.reloadData()`、`this.getSearch()` 等。
## 许可证
## 表格菜单
[MIT](https://opensource.org/licenses/MIT)
业务页面通常通过 `componentsProps.table.menu.menu` 配置表格行操作。
```ts
componentsProps: {
table: {
menu: {
menu: [
{ prop: '$change', name: '编辑', color: 'link' },
{ prop: '$delete', name: '删除', color: 'danger' },
{ prop: '$info', name: '详情', color: 'link' }
]
}
}
}
```
`ComplexQuickList` 默认行为:
- `$change`:打开编辑弹窗,并使用当前行作为原始数据。
- `$delete`:确认后调用 `listData.triggerMethod('deleteData', [targetData])`。
- `$info`:打开详情弹窗。
## 级联列表
级联列表通常使用 `ComplexQuickCascade`。主列表通过表格菜单暴露 `$sublist`,点击后打开子列表弹窗,并由子列表自己的 `ComplexList` 加载数据。
推荐模式:
```ts
const parentData = new ComplexList({
prop: 'parentData',
module: {
dictionary: { list: [] },
pagination: true
},
getData(this: ComplexList) {
return Promise.resolve({})
}
})
const childData = new ComplexList({
prop: 'childData',
module: {
dictionary: { list: [] },
pagination: true
},
getData(this: ComplexList) {
const parentId = this.getExtra('parentId')
return Promise.resolve(parentId)
}
})
const showSubList = function(targetData: any) {
childData.setExtra('parentId', targetData.id)
childData.reloadData(true)
}
```
## 推荐实践
- 列表页优先用 `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`。
## TypeScript 泛型
`ComplexList` 支持泛型约束列表项类型:
```ts
interface UserItem { id: number; name: string }
interface UserVO { id: number; name: string; deptName: string }
const listData = new ComplexList<UserItem, UserVO>({
prop: 'userList',
module: { dictionary: { list: [] }, pagination: true },
getData(this: ComplexList<UserItem, UserVO>) {
return fetch('/api/users').then(r => r.json()).then(res => {
this.formatList(res.list, res.total)
return res
})
}
})
listData.$list // 类型为 UserItem[]
```
## 依赖
- `@complex-suite/utils`:核心工具函数和基类(Life、Watcher)。
- `@complex-suite/plugin`:布局和通知系统。

@@ -245,3 +245,4 @@ import { getComplexProp, isPromise } from '@complex-suite/utils'

}
// 触发函数联动operate
// 触发函数联动operate:通过强制注入 status='operate',保证所有业务操作自动联动 operate 状态机
// 所有业务回调(buildData/changeData/deleteData 等)都应通过此方法触发,以确保 operate 状态集中管理
triggerMethod(method: string, args: any[] = [], option: triggerMethodOption = {}) {

@@ -252,2 +253,3 @@ (option as triggerMethodWithStatusOption).status = 'operate'

// 触发函数并联动目标status,再联动operate
// 通过反射调用 triggerMethod('$triggerMethodWithStatus', ...) 复用 operate 注入逻辑,避免分散
triggerMethodWithOperateAndStatus(method: string, args: any[] = [], option: triggerMethodWithStatusOption, operateOption: triggerMethodOption = {}) {

@@ -254,0 +256,0 @@ return this.triggerMethod('$triggerMethodWithStatus', [method, args, option] as Parameters<BaseData['$triggerMethodWithStatus']>, operateOption)

@@ -52,2 +52,9 @@ import { _Data } from '@complex-suite/utils'

}
/**
* 数据同步钩子:响应式环境(如 Vue)下为空操作,数据变更自动触发 UI 更新;
* 非响应式环境(如 React、微信小程序)需在子类重写,通过 setData / forceUpdate 等机制同步数据到 UI
* @param _self 当前数据是否为刷新发起者(true=自身刷新成功不冒泡;false=需向上递归到顶层数据进行判断)
* @param _act 触发动作标识(如 '$setInterfaceValue'、'formatList')
* @param _args 附加参数
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars

@@ -54,0 +61,0 @@ _syncData(_self: boolean, _act: string, ..._args: unknown[]) {

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

_getProp() {
return `${this._getConstructorName}-${this.$prop}`
return `${this._getConstructorName()}-${this.$prop}`
}

@@ -136,0 +136,0 @@ _getName(): string {

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

* @param {number} index - 要插入的索引位置
* @warning [严重 Bug] 此方法存在严重错误,splice的第三个参数应为 value.$prop,而不是数字 1。
*/

@@ -159,0 +158,0 @@ pushByIndex(value: D, index: number) {

@@ -131,3 +131,3 @@ import { isExist, exportMsg, getComplexProp, trimData, isArray } from '@complex-suite/utils'

mod?: DictionaryModDataInitOption
dictionray?: DictionaryDataInitOption | DictionaryData
dictionary?: DictionaryDataInitOption | DictionaryData
}

@@ -259,4 +259,4 @@

}
if (initOption.dictionray) {
this.dictionary = initOption.dictionray instanceof DictionaryData ? initOption.dictionray : new DictionaryData(initOption.dictionray)
if (initOption.dictionary) {
this.dictionary = initOption.dictionary instanceof DictionaryData ? initOption.dictionary : new DictionaryData(initOption.dictionary)
}

@@ -301,62 +301,62 @@ this._triggerCreateLife('DictionaryValue', true, initOption)

$formatData(targetData: Record<PropertyKey, any>, originFrom: string, useSetData?: boolean, depth = 0) {
if (this.$isOriginFrom(originFrom)) {
// 仅存在assign函数或者originProp !== this.$prop需要进行格式化操作
if (!this.$complex.assignProp) {
const originProp = this.$getOriginProp(originFrom)
if (this.assign) {
dataConfig.nonEmptySetProp(targetData, this.$prop, this.$triggerFunc('assign', targetData[originProp], {
targetData: targetData,
originData: targetData,
type: originFrom,
depth
}), useSetData)
} else if (originProp !== this.$prop) {
// 不存在赋值函数则在prop不同时重新赋值
// 不应对原字段进行操作,原因如标题处
dataConfig.nonEmptySetProp(targetData, this.$prop, targetData[originProp], useSetData)
}
} else {
const originProp = this.$getOriginProp(originFrom)
if (this.assign) {
dataConfig.nonEmptySetComplexProp(targetData, this.$prop, this.$triggerFunc('assign', getComplexProp(targetData, originProp), {
targetData: targetData,
originData: targetData,
type: originFrom,
depth
}), useSetData)
} else if (originProp !== this.$prop) {
// 不存在赋值函数则在prop不同时重新赋值
// 不应对原字段进行操作,原因如标题处
dataConfig.nonEmptySetComplexProp(targetData, this.$prop, targetData[originProp], useSetData)
}
}
}
// format 模式:originData 即 targetData 自身;无 assign 且 prop 相同时跳过(值已存在于 targetData)
this._buildDataValue(targetData, targetData, originFrom, useSetData, depth, true)
}
// 赋值
$assignData(targetData: Record<PropertyKey, any>, originData: Record<PropertyKey, any>, originFrom: string, useSetData?: boolean, depth = 0) {
if (this.$isOriginFrom(originFrom)) {
const originProp = this.$getOriginProp(originFrom)
if (!this.$complex.assignProp) {
let targetValue = originData[originProp]
if (this.assign) {
targetValue = this.$triggerFunc('assign', targetValue, {
targetData: targetData,
originData: originData,
type: originFrom,
depth
})
}
dataConfig.nonEmptySetProp(targetData, this.$prop, targetValue, useSetData)
// assign 模式:从独立 originData 赋值到 targetData;无 assign 时也需赋值(数据源不同)
this._buildDataValue(targetData, originData, originFrom, useSetData, depth, false)
}
/**
* 数据构建内部方法($formatData / $assignData 共用,仅一处实现,避免重复代码)
* 性能考量:payload 对象仅在存在 assign 函数时创建;originProp 仅调用一次;分支内联避免函数调用栈
* @param targetData 目标数据
* @param originData 源数据(format 模式下等于 targetData)
* @param originFrom 来源标识
* @param useSetData 是否使用 setData 写入(小程序等环境)
* @param depth 嵌套深度
* @param skipSameProp 无 assign 时同 prop 是否跳过:format=true(值已在目标),assign=false(数据源不同需赋值)
*/
protected _buildDataValue(targetData: Record<PropertyKey, any>, originData: Record<PropertyKey, any>, originFrom: string, useSetData: boolean | undefined, depth: number, skipSameProp: boolean) {
if (!this.$isOriginFrom(originFrom)) {
return
}
const originProp = this.$getOriginProp(originFrom)
if (!this.$complex.assignProp) {
// 简单 prop 模式
let targetValue = originData[originProp]
if (this.assign) {
targetValue = this.$triggerFunc('assign', targetValue, {
targetData: targetData,
originData: originData,
type: originFrom,
depth: depth
})
} else if (skipSameProp && originProp === this.$prop) {
// format 模式下同 prop 跳过:值已在 targetData 中,无需重新赋值
return
}
dataConfig.nonEmptySetProp(targetData, this.$prop, targetValue, useSetData)
} else {
// 复杂 prop 模式(如 'a.b.c')
// 注意:$assignData 无 assign 时使用 getComplexProp 解析嵌套路径取值
// $formatData 无 assign 时使用 originData[originProp] 直接索引取值,保持与原始实现一致
let targetValue: unknown
if (this.assign) {
targetValue = this.$triggerFunc('assign', getComplexProp(originData, originProp), {
targetData: targetData,
originData: originData,
type: originFrom,
depth: depth
})
} else if (skipSameProp && originProp === this.$prop) {
return
} else if (skipSameProp) {
// $formatData 无 assign:直接索引取值(与原始 $formatData 一致)
targetValue = originData[originProp]
} else {
let targetValue = getComplexProp(originData, originProp)
if (this.assign) {
targetValue = this.$triggerFunc('assign', targetValue, {
targetData: targetData,
originData: originData,
type: originFrom,
depth
})
}
dataConfig.nonEmptySetComplexProp(targetData, this.$prop, targetValue, useSetData)
// $assignData 无 assign:通过 getComplexProp 解析嵌套路径(与原始 $assignData 一致)
targetValue = getComplexProp(originData, originProp)
}
dataConfig.nonEmptySetComplexProp(targetData, this.$prop, targetValue, useSetData)
}

@@ -363,0 +363,0 @@ }

@@ -50,9 +50,9 @@ import { getType, setProp } from '@complex-suite/utils'

const value = this.getValue('default')
const type = typeof value
if (type === 'object' || type === 'function') {
// 通过 typeof 类型守卫让 TS 自动收窄泛型 D,避免双重断言
if (typeof value === 'string') {
return value
} else if (typeof value === 'object' || typeof value === 'function') {
return (value as object).toString()
} else if (type !== 'string') {
} else {
return String(value)
} else {
return (value as unknown as string)
}

@@ -59,0 +59,0 @@ }

@@ -11,3 +11,3 @@ import GridParse from "./GridParse"

initOption.grid = {
default: undefined as undefined | GridParseInitOption
default: undefined
}

@@ -14,0 +14,0 @@ }

@@ -88,2 +88,10 @@ import { exportMsg, getRandomNum, storage } from '@complex-suite/utils'

}
/**
* 初始化本地缓存读取
* @param {DataWithLife} parent 持有该 StorageValue 的父级数据对象
* @returns {boolean}
* - true: 缓存有效(版本匹配 + 加权进度 < 100),已加载缓存数据;
* 若加权进度 > 50,还会设置定时器在 offset * (1 + offsetRate/50) 毫秒后触发 reloadStorage
* - false: 缓存无效(无缓存 / 版本不匹配 / 加权进度 >= 100),需调用方重新加载远程数据
*/
init(parent: DataWithLife) {

@@ -90,0 +98,0 @@ const storageData = storage.getData(this.prop) as undefined | storageDataType

@@ -57,7 +57,2 @@ import BaseData from '../data/BaseData'

}
destroy(force?: boolean) {
if (force !== false) {
this.reset(force)
}
}
/**

@@ -64,0 +59,0 @@ * 模块加载

@@ -80,2 +80,13 @@ import Data from './../data/Data'

/**
* 销毁操作:默认实现为在 force !== false 时执行 reset
* 子类如无特殊销毁逻辑(如清除定时器、解绑全局事件),无需重写此方法
* @param {boolean} force 销毁判断值,false 时跳过
*/
destroy(force?: boolean) {
if (force !== false) {
this.reset(force)
}
}
/**
* 模块加载

@@ -82,0 +93,0 @@ * @param {object} target 加载到的目标

@@ -59,9 +59,4 @@ import ResetData from './ResetData'

}
destroy(force?: boolean) {
if (force !== false) {
this.reset(force)
}
}
}
export default SortData
+143
-0

@@ -32,1 +32,144 @@ # @complex-suite/data 开发待办

- 优化代码结构
---
## 问题清单
> 以下为对 `packages/data` 及其消费者(`component`、`component-antd`、`pages/index/views/list`)的代码审查结果。
> 严重度:🔴 高 / 🟡 中 / 🟢 低
---
### ✅ 已完成(v5.0.9)
| 编号 | 严重度 | 问题摘要 | 处理方式 |
| --- | --- | --- | --- |
| 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
---
### 📌 待审核(需你决策)
#### P2-2 `nonEmptySetProp` 与 `nonEmptySetComplexProp` — 不合并,标注性能考量
- **问题**:[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'` 路径,分开是为了简单场景快速输出,性能优先。建议仅在注释中标注性能考量。
- **影响**:🟢 低
- **状态**:⏳ 待审核
#### P2-4 `_runMethod` 字符串反射 + 类型逃逸
- **问题**:[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` 调用链,需慎重。
- **状态**:⏳ 待审核
#### 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。
- **影响**:🟡 中(封装性)。
- **状态**:⏳ 待审核
#### 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 内)。
- **影响**:🟢 低(仅类型层面,运行时无变化)。
- **状态**:⏳ 待审核
#### P3-3 `_install` / `_uninstall` 文档缺失
- **问题**:`_install(parent, from)` / `_uninstall(parent, from)` 无注释说明参数含义和调用时机。
- **处理方案**:在 ModuleData.ts 和各模块基类补 TSDoc 注释;README 已补充说明。
- **影响**:🟢 低。
- **状态**:⏳ 待审核
#### 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**
```ts
// DefaultMod.ts
static parseModInitOption<T extends object>(initOption: T | true, defaultOption?: Partial<T>): T {
const option = initOption === true ? {} as T : initOption
if (defaultOption) {
for (const prop in defaultOption) {
if (option[prop] === undefined) { option[prop] = defaultOption[prop]! }
}
}
return option
}
```
然后 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 个),开销可忽略。内联 `??`/`||` 看似更快,但构造时差异在纳秒级,无实际影响。
- **影响**:🟢 低
- **状态**:⏳ 待审核
#### 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 类型定义松散
- **问题**:[ForceValue.ts](file:///e:/PROJECT/VITE/A-DEVELOP/suite-container/src/complex-suite/packages/data/src/lib/ForceValue.ts) `module` 类型定义松散,`[prop: string]` 索引签名允许任意值。
- **处理方案**:收窄索引签名为已知模块联合类型。
- **影响**:🟢 低。
- **状态**:⏳ 待审核
#### 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 补充说明)
---
### ✅ 已确认不修改
| 编号 | 问题摘要 | 确认理由 |
| --- | --- | --- |
| P3-1 | TrackData 与 data 包核心无关 | 保留在 data 包但标记为独立(README 已落地) |
| P5-3 | `upload` 语义 | `upload` 是文件上传专用字段,语义清晰,与 `click` 各司其职 |
---
### 审阅方式
- 对待审核项,请回复:`编号 + 处理意见`(如「P2-2 ✅ 仅加注释」「P2-6 方案 B」等)
- 多条可批量回复:`P2-2, P4-4, P4-6 ✅ 修改;P2-6 方案 B`
- 收到审阅意见后按批次执行