@complex-suite/data
Advanced tools
+24
-0
@@ -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' |
+3
-3
| { | ||
| "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] |
+17
-10
@@ -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 @@ |
357650
8.45%7540
0.27%928
83.4%