🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@complex-suite/data

Package Overview
Dependencies
Maintainers
1
Versions
15
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.14
Version published
Weekly downloads
1.4K
2922.22%
Maintainers
1
Weekly downloads
 
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 06 Jul 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts