@complex-suite/data 快速使用
@complex-suite/data 是 complex-suite 的核心数据层。它不负责渲染 UI,而是负责描述业务页面的数据结构、搜索条件、字典字段、分页、选择、编辑、详情、异步加载和业务操作。
在当前 monorepo 中,它位于依赖链中间:utils → plugin → data → component → component-antd。实际业务页面通常通过 data 创建无头数据模型,再交给 component-antd 的 ComplexQuickList、ComplexQuickCascade 等组件渲染。
安装
pnpm add @complex-suite/data
核心概念
ComplexList:列表页主数据模型,维护 $list、加载状态、搜索、分页、选择、字典和增删改查方法。
ComplexInfo:详情页数据模型,维护 $info,常用于详情弹窗或详情页。
module.dictionary:字段字典,统一描述表格列、详情字段、编辑表单、创建表单和搜索字段。
module.search:搜索区配置,包含搜索字段和顶部菜单按钮。
module.pagination:分页模块,提供 getPage()、getPageSize()、setPageCount() 等能力。
SelectValue / SelectEdit:选择器数据源与选择器字典项,支持静态选项和远程加载。
ButtonEdit:按钮字典项,常用于搜索区菜单、自定义操作和上传入口。
模块系统
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 后 |
监听方式:
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 渲染。
<template>
<ComplexQuickList
:list-data="mainData"
:components="['spin', 'search', 'table', 'edit', 'info']"
:simple-table="false"
:components-props="componentsProps"
@search="onSearch"
@table="onTable"
/>
</template>
<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
},
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
}
},
mounted() {
this.mainData.loadData(true)
}
})
</script>
字典字段配置
module.dictionary.list 是列表页的核心协议。一个字段可以同时服务多个场景:
{
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 分为菜单和搜索字段两部分。
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 中远程加载选项。
{
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({})
})
}
}
}
}
如果远程接口有分页结果,可在 getData 中同时调用 this.$pagination.setCount(total)。
数据加载与提交
ComplexList 的业务方法都要求返回 Promise,这样状态模块才能正确维护 load、operate 等状态。
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)
})
})
}
常用方法:
loadData(true):首次或强制加载数据。
reloadData(true):重新加载数据,通常在新增、修改后调用。
formatList(list, total):把接口列表写入 $list,并同步分页总数。
getSearch():获取搜索区当前值。
getPage() / getPageSize():获取当前分页参数。
triggerMethod(method, args, option):通过状态机触发业务方法。
业务回调
ComplexList 支持在初始化配置中定义业务回调,this 绑定到当前实例:
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>
}
回调内可直接访问 this.$list、this.reloadData()、this.getSearch() 等。
表格菜单
业务页面通常通过 componentsProps.table.menu.menu 配置表格行操作。
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 加载数据。
推荐模式:
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 支持泛型约束列表项类型:
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
依赖
@complex-suite/utils:核心工具函数和基类(Life、Watcher)。
@complex-suite/plugin:布局和通知系统。