@complex-suite/data
Advanced tools
+14
-0
@@ -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) |
+7
-0
@@ -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` | ||
| - 收到审阅意见后按批次执行 |
329647
8.07%7520
0.56%506
400.99%+ Added
+ Added
- Removed
- Removed
Updated
Updated