New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

@complex-suite/data

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@complex-suite/data

a complex data

latest
Source
npmnpm
Version
5.0.22
Version published
Maintainers
1
Created
Source

@complex-suite/data 快速使用

@complex-suite/data 是 complex-suite 的核心数据层。它不渲染 UI,只负责描述业务页面的数据结构、搜索条件、字典字段、分页、选择、编辑、详情、异步加载和业务操作

依赖链:utils → plugin → data → component → component-antd。业务页面通过 data 创建无头数据模型,再交给 component-antdComplexQuickList / ComplexQuickCascade 渲染。

安装

pnpm add @complex-suite/data

核心导出

import {
  // 业务数据类
  ComplexList, ComplexInfo, SelectData, TrackData,
  // 数据类继承链
  Data, SimpleData, DefaultData, BaseData, ComplexData,
  // 模块
  ModuleData, DictionaryData, SearchData, PaginationData,
  ChoiceData, SortData, UpdateData, StatusData, DependData, PromiseData, ResetData,
  // 值对象
  DictionaryValue, ArrayValue, SelectValue, CascaderValue, FormValue,
  StorageValue, ForceValue, InterfaceValue, AttrsValue, TipValue, FileValue,
  GridParse, LayoutParse,
  // 字典项
  DefaultList, DefaultInfo, DefaultEdit, InputEdit, InputNumberEdit,
  TextAreaEdit, SelectEdit, SwitchEdit, DateEdit, DateRangeEdit, SimpleDateEdit,
  FileEdit, ButtonEdit, ButtonGroupEdit, ContentEdit, CustomEdit, CustomLoadEdit,
  FormEdit, ListEdit, ObserveList, DefaultMod, DefaultSimpleEdit, DefaultLoadEdit, DefaultSelectEdit,
  // 配置
  dataConfig, dataLocale
} from '@complex-suite/data'

数据类继承链

Data (utils._Data)
  └─ SimpleData          # $extra 额外数据容器
      └─ DefaultData     # $prop / $life 生命周期 / $storage
          └─ BaseData    # $status / $promise / $depend / $module / loadData / reset / destroy
              └─ ComplexData  # 业务回调(getData/buildData/changeData/...) + 模块代理方法
                  ├─ ComplexList  # $list 列表页主模型
                  └─ ComplexInfo  # $info 详情页模型

每一层通过 _triggerCreateLife('ClassName', false/true, initOption) 触发 beforeCreate/created 生命周期。

模块系统

ComplexList 通过 module 挂载功能模块:

模块作用必填
dictionaryDictionaryData字段字典管理
searchSearchData检索表单与菜单-
paginationPaginationData分页参数与总数-
choiceChoiceData多选状态-
sortSortData排序参数-
updateUpdateData定时刷新-
dependDependData依赖加载-
statusStatusData状态机内置
promisePromiseDataPromise 缓存内置

快速开始:标准列表页

下面示例提炼自真实业务页面 src/pages/index/views/list/base/index.vue

<template>
  <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>

<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() {
    // 1) 静态选项数据源
    const select = new SelectValue({
      list: [
        { value: 0, label: '选项1' },
        { value: 1, label: '选项2' },
        { value: 2, label: '选项3' }
      ]
    })

    // 2) 创建列表数据模型
    const mainData = new ComplexList({
      prop: 'mainData',
      module: {
        // 搜索区:菜单 + 搜索字段
        search: {
          menu: {
            list: [
              'build',                        // 内置:新增按钮
              'delete',                       // 内置:批量删除按钮
              new ButtonEdit({                // 自定义按钮
                prop: 'choice2',
                type: 'button',
                option: {
                  type: 'default',
                  name: () => '已选择2条时可用',
                  disabled(payload: any) {
                    return payload.choice !== 2
                  },
                  click() {
                    return new Promise((resolve) => setTimeout(() => resolve({}), 2000))
                  }
                }
              }),
              new ButtonEdit({                // 文件上传按钮
                prop: '$import',
                type: 'button',
                option: {
                  type: 'default',
                  name: () => '导入',
                  upload: () => new Promise((resolve) => setTimeout(() => resolve({}), 3000))
                }
              })
            ]
          },
          list: [
            {
              prop: 'input',
              name: '输入框',
              mod: {
                search: { $format: 'edit', type: 'input', collapse: 2, option: { size: 20 } }
              }
            },
            {
              prop: 'search',
              name: '检索框',
              mod: {
                search: {
                  $format: 'edit',
                  type: '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({})
                    })
                  }
                }
              }
            }
          ]
        },
        // 字典:字段定义(同时服务 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: 100 },
                info: {},
                edit: { type: 'input', required: true, option: { size: 20 } },
                build: { $redirect: 'edit' },
                change: { $redirect: 'edit' }
              }
            },
            {
              prop: 'switch',
              name: '开关',
              assign(value) { return value === 1 },
              collect(value) { return value ? 1 : 0 },
              mod: {
                list: { width: 70 },
                edit: { type: 'switch', required: true },
                build: { $redirect: 'edit' },
                change: { $redirect: 'edit' }
              }
            },
            {
              prop: 'select',
              name: '选择器',
              showProp: { default: 'value', list: 'label' },
              assign(value) { return select.getItem(value) },
              mod: {
                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: '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' }
              }
            }
          ]
        },
        pagination: true
      },
      // 业务回调:均要求返回 Promise,this 绑定到当前实例
      getData(this: ComplexList) {
        return new Promise((resolve, reject) => {
          const postData = {
            ...this.getSearch(),
            page: this.getPage(),
            size: this.getPageSize()
          } as any
          listApi.baseList.require(postData).then(res => {
            this.formatList(res.data.data.list, res.data.data.num)
            resolve(res)
          }).catch(reject)
        })
      },
      buildData(this: ComplexList, targetData) {
        return new Promise((resolve, reject) => {
          listApi.baseBuild.require(targetData).then(res => {
            this.reloadData(true)
            resolve(res)
          }).catch(reject)
        })
      },
      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(reject)
        })
      },
      deleteData(this: ComplexList, targetData) {
        return new Promise((resolve) => {
          const index = this.$list.indexOf(targetData)
          if (index > -1) this.$list.splice(index, 1)
          resolve({})
        })
      },
      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({})
        })
      }
    })

    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: '名称',                  // 显示名
  originFrom: 'local',           // 数据来源:local / search / data
  showProp: {                    // 不同场景下展示的字段
    default: 'value',
    list: 'label'
  },
  assign(value) { ... },         // 接口值 → 展示/表单值 转换
  collect(value) { ... },        // 表单值 → 提交值 转换
  dictionary: { list: [...] },   // 嵌套字典(type: 'form'/'list' 时使用)
  mod: {
    list:   { width: 100 },                  // 表格列配置
    info:   {},                               // 详情字段配置
    edit:   { type: 'input', required: true },// 通用编辑配置
    build:  { $redirect: 'edit' },            // 新增表单:复用 edit
    change: { $redirect: 'edit' },            // 修改表单:复用 edit
    search: { $format: 'edit', type: 'input' } // 搜索字段
  }
}

编辑类型对照

type字典类适用场景
inputInputEdit文本输入
textAreaTextAreaEdit多行文本
inputNumberInputNumberEdit数字输入
selectSelectEdit / DefaultSelectEdit选择器
switchSwitchEdit开关
dateDateEdit / SimpleDateEdit日期
dateRangeDateRangeEdit日期范围
fileFileEdit文件上传
buttonButtonEdit按钮(搜索区/自定义操作)
formFormEdit内嵌表单
listListEdit内嵌列表
contentContentEdit纯展示
customCustomEdit / CustomLoadEdit自定义渲染

搜索区配置

search: {
  menu: {
    list: [
      'build',                 // 内置:新增
      'delete',                // 内置:批量删除
      new ButtonEdit({ ... })  // 自定义按钮
    ]
  },
  list: [
    {
      prop: 'keyword',
      name: '关键字',
      mod: {
        search: { $format: 'edit', type: 'input', collapse: 2 }
      }
    }
  ]
}

ComplexQuickList 内置菜单 prop:

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():普通点击回调
  • upload(file: File):文件上传专用回调

数据加载与业务回调

所有业务方法都要求返回 Promise,this 绑定到当前实例:

interface ComplexDataInitOption {
  module: ModuleDataInitOption
  getData?: (this: ComplexList) => Promise<any>
  buildData?: (this: ComplexList, targetData) => Promise<any>
  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>
  importData?: (this: ComplexList, file: File) => Promise<any>
}

回调内可直接访问 this.$listthis.reloadData()this.getSearch() 等。修改类方法(buildData/changeData/deleteData)成功后会自动触发 dataChange 生命周期。

loadData 的 force 参数

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 }
})

常用方法

方法作用
loadData(force?, ...args)加载数据
reloadData(force?, ...args)重新加载,触发 beforeReload/reloaded/reloadFail
formatList(list, total)写入 $list,同步分页总数
getSearch()获取搜索区当前值
getPage() / getPageSize()获取当前分页参数
setPage(page) / setPageSize(size)修改分页参数
setSearch(action?)校验搜索区并触发 reload
resetSearch(option?)重置搜索条件并触发 setSearch('reset')
triggerMethod(method, args, option)通过状态机触发业务方法
setSort(prop, order)设置排序
setChoice(data) / resetChoice()多选操作

字段转换:assign 与 collect

接口值与表单值往往存在差异,字典字段提供两个转换点:

{
  prop: 'switch',
  // 接口值(number)→ 展示/表单值(boolean)
  assign(value) { return value === 1 },
  // 表单值(boolean)→ 提交值(number)
  collect(value) { return value ? 1 : 0 }
}
  • assign:在 getData 成功后、数据进入 $list/$info/表单前调用
  • collect:在 buildData/changeData 提交前调用

嵌套字典(FormEdit / ListEdit)

字段可嵌套完整字典,用于内嵌表单/列表:

{
  prop: 'form',
  name: '内嵌表单',
  dictionary: {
    list: [
      {
        prop: 'name',
        name: '名称',
        mod: {
          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' }
  }
}

生命周期

listData.onLife('loaded', {
  id: 'myHandler',
  handler: (lifeValue, res) => {
    console.log('加载完成', res)
  }
})
listData.offLife('loaded', 'myHandler')

// onLoaded 是 loaded 的快捷监听,若当前已加载完成会立即触发
listData.onLoaded({
  handler: (lifeValue) => { /* ... */ }
})

常用生命周期:

名称触发时机
beforeCreate / created实例构造前后
beforeLoadloadData 开始
loaded加载成功
loadFail加载失败
beforeReload / reloaded / reloadFailreloadData 各阶段
beforeSearchsetSearch 校验前
searched / searchFail检索成功/失败
beforeUpdate / updated / updateFail更新数据各阶段
beforeReset / resetedreset 前后
beforeDestroy / destroyeddestroy 前后
dataChange业务方法成功后(buildData/changeData/deleteData/...)
parentChange$setParent

远程选择器

选择器可使用静态 SelectValue,也可在 SelectEdit.getData 中远程加载:

{
  prop: 'search',
  mod: {
    search: {
      $format: 'edit',
      type: 'select',
      cascader: undefined,
      reload: true,
      search: { limit: 3, limitContent: '限制3个' },
      getData(this: SelectEdit) {
        return new Promise((resolve) => {
          this.$select.setList([
            { value: this.$search?.value, label: this.$search?.value || '' },
            { value: 1, label: '1' }
          ])
          resolve({})
        })
      }
    }
  }
}

远程接口有分页时,在 getData 中同步调用 this.$pagination.setCount(total)

级联列表

主列表通过表格菜单暴露 $sublist,点击打开子列表弹窗,子列表用自己的 ComplexList 加载:

// 父列表
const parentData = new ComplexList({
  prop: 'parentData',
  module: { dictionary: { list: [...] }, pagination: true },
  getData(this: ComplexList) {
    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: [...] }, pagination: true },
  getData(this: ComplexList) {
    const parentId = this.getExtra('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) {
  childData.setExtra('parentId', targetData.id)
  childData.reloadData(true)
}

$extra 是不参与字典/搜索/分页的额外数据容器,通过 setExtra / getExtra / clearExtra 操作。

TypeScript 泛型

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[]

// 详情页
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 与级联

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'
})

重置与销毁

// 重置(不清除生命周期、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 }

全局配置与国际化

import { dataConfig, dataLocale, type DataMessages } from '@complex-suite/data'

// 读取主题色
const primary = dataConfig.style.color.primary  // '#1677ff'

// 国际化
dataLocale.use('en')
dataLocale.t('baseData.methodNotExist', { method: 'foo' })

// 注入自定义消息
const customMessages: Partial<DataMessages> = {
  zh: { baseData: { methodNotExist: '方法 {method} 不存在' } }
}
dataLocale.inject(customMessages)

推荐实践

  • 列表页优先用 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 监听关键生命周期,避免 setTimeout 等待异步
  • force 参数优先用对象形式({ data: true, ing: true }),可读性优于布尔简写
  • data 只负责无头数据协议,渲染细节交给 component-antdComplexQuickList/TableView/EditArea/InfoArea

依赖

  • @complex-suite/utils:核心工具函数和基类(Life、Watcher、setProp 等)
  • @complex-suite/plugin:布局和通知系统

Keywords

complex

FAQs

Package last updated on 09 Sep 2026

Related posts