
Security News
/Research
Compromised Injective SDK npm Package Exfiltrates Wallet Keys and Mnemonics
Compromised Injective SDK npm version 1.20.21 exfiltrates wallet private keys and mnemonics through fake telemetry functionality.
@complex-suite/data
Advanced tools
@complex-suite/data 是 complex-suite 的核心数据层。它不渲染 UI,只负责描述业务页面的数据结构、搜索条件、字典字段、分页、选择、编辑、详情、异步加载和业务操作。
依赖链:utils → plugin → data → component → component-antd。业务页面通过 data 创建无头数据模型,再交给 component-antd 的 ComplexQuickList / 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.$list、this.reloadData()、this.getSearch() 等。修改类方法(buildData/changeData/deleteData)成功后会自动触发 dataChange 生命周期。
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() | 多选操作 |
接口值与表单值往往存在差异,字典字段提供两个转换点:
{
prop: 'switch',
// 接口值(number)→ 展示/表单值(boolean)
assign(value) { return value === 1 },
// 表单值(boolean)→ 提交值(number)
collect(value) { return value ? 1 : 0 }
}
assign:在 getData 成功后、数据进入 $list/$info/表单前调用collect:在 buildData/changeData 提交前调用字段可嵌套完整字典,用于内嵌表单/列表:
{
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 操作。
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>
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/上传 uploadassign,提交前用 collect,避免转换逻辑散落在 UI 组件$redirect: 'edit' 复用配置getData 中同步调用 this.$pagination.setCount(total)onLife/offLife 监听关键生命周期,避免 setTimeout 等待异步force 参数优先用对象形式({ data: true, ing: true }),可读性优于布尔简写data 只负责无头数据协议,渲染细节交给 component-antd 的 ComplexQuickList/TableView/EditArea/InfoArea@complex-suite/utils:核心工具函数和基类(Life、Watcher、setProp 等)@complex-suite/plugin:布局和通知系统FAQs
a complex data
The npm package @complex-suite/data receives a total of 1,019 weekly downloads. As such, @complex-suite/data popularity was classified as popular.
We found that @complex-suite/data demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Security News
/Research
Compromised Injective SDK npm version 1.20.21 exfiltrates wallet private keys and mnemonics through fake telemetry functionality.

Security News
npm v12 is generally available, turning install scripts off by default and beginning the deprecation of 2FA-bypass publishing tokens.

Research
/Security News
Socket tracks the activity as Operation “Muck and Load”: a threat actor uses commit-farming workflows, public dead drops, and protected archives to stage Windows RAT and infostealer malware.