npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@complex-suite/data

v5.0.22

Published

a complex data

Readme

@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 挂载功能模块:

| 模块 | 类 | 作用 | 必填 | |---|---|---|---| | dictionary | DictionaryData | 字段字典管理 | ✅ | | search | SearchData | 检索表单与菜单 | - | | pagination | PaginationData | 分页参数与总数 | - | | choice | ChoiceData | 多选状态 | - | | sort | SortData | 排序参数 | - | | update | UpdateData | 定时刷新 | - | | depend | DependData | 依赖加载 | - | | status | StatusData | 状态机 | 内置 | | promise | PromiseData | Promise 缓存 | 内置 |

快速开始:标准列表页

下面示例提炼自真实业务页面 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 | 字典类 | 适用场景 | |---|---|---| | 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 | 自定义渲染 |

搜索区配置

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 | 实例构造前后 | | beforeLoad | loadData 开始 | | loaded | 加载成功 | | loadFail | 加载失败 | | beforeReload / reloaded / reloadFail | reloadData 各阶段 | | beforeSearch | setSearch 校验前 | | searched / searchFail | 检索成功/失败 | | beforeUpdate / updated / updateFail | 更新数据各阶段 | | beforeReset / reseted | reset 前后 | | beforeDestroy / destroyed | destroy 前后 | | 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:布局和通知系统