@hz_yujin/vue-smart-table
v0.1.2
Published
Vue 3 ProSmartTable:基于 el-table 全量透传,列 schema 驱动查询 / 表单 / 列管理抽屉
Maintainers
Readme
@hz_yujin/vue-smart-table
本包位于 monorepo
packages/smart-table。仓库根目录见 README。
基于 Element Plus el-table 的 Smart Table:一份列 schema 同时驱动表格列、查询条件与新增/编辑表单,并内置列管理抽屉、分页、骨架屏。
与 @hz_yujin/vue-base-table(VXE)互补,互不影响。
目录
安装
npm install @hz_yujin/vue-smart-table element-plus @element-plus/icons-vueelement-plus、@element-plus/icons-vue、vue 为 peerDependencies,需由宿主项目安装。
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import '@hz_yujin/vue-smart-table/style.css'
import { ProSmartTable } from '@hz_yujin/vue-smart-table'
// 或全局注册
import VueSmartTable from '@hz_yujin/vue-smart-table'
const app = createApp(App)
app.use(ElementPlus, { locale: zhCn })
app.use(VueSmartTable) // 可选
app.mount('#app')快速开始
<script setup lang="ts">
import { ref } from 'vue'
import {
ProSmartTable,
type SmartColumnSchema,
} from '@hz_yujin/vue-smart-table'
import '@hz_yujin/vue-smart-table/style.css'
const columns = ref<SmartColumnSchema[]>([
{
id: 'name',
prop: 'name',
label: '名称',
minWidth: 160,
searchable: true,
formable: true,
required: true,
formType: 'input',
source: 'fixed',
},
{
id: 'status',
prop: 'status',
label: '状态',
minWidth: 120,
searchable: true,
formable: true,
formType: 'select',
formOptions: [
{ label: '启用', value: '启用' },
{ label: '禁用', value: '禁用' },
],
source: 'fixed',
},
])
const data = ref([
{ name: '示例 A', status: '启用' },
{ name: '示例 B', status: '禁用' },
])
function onSearch(model: Record<string, unknown>) {
console.log('search', model)
}
</script>
<template>
<ProSmartTable
v-model:columns="columns"
:data="data"
persist-key="demo-smart-table"
form-title="记录"
border
stripe
@search="onSearch"
@create="(row) => data.push(row)"
@update="(row) => console.log('update', row)"
/>
</template>未在 ProSmartTable 上声明的属性(如 border、stripe、height、row-key)会透传给底层 el-table。
设计思路
| 区域 | 由谁驱动 |
|------|----------|
| 表格列 | columns 中 visible !== false 且有 prop 的项 |
| 查询区 | searchable: true 的列 |
| 新增/编辑表单 | formable: true 的列 |
| 控件类型 | formType + formOptions / formProps |
业务侧通常只需维护一份 SmartColumnSchema[],再监听 search / create / update / page-change 对接接口。
能力一览
| 能力 | 说明 |
|------|------|
| el-table 透传 | 未声明的 attrs / 默认插槽全部落到 el-table |
| 查询 | 可收起/展开;默认收起展示前 3 项 |
| 新增/编辑 | 内置弹框 + 必填校验;可用插槽自定义字段 |
| 列管理 | 显隐、拖拽排序、增删列、查询/表单开关、固定列 |
| 持久化 | persistKey → localStorage;也可自行存服务端 |
| 分页 | 默认开启;local 前端切片 / remote 服务端分页 |
| 排序 | 支持本地 / 远程(sortable: 'custom' 或 remote 分页自动提升) |
| 骨架屏 | 默认开启;无数据时骨架,有数据刷新转圈 |
| 序号 / 多选 | showSeq / showSelection |
SmartColumnSchema
interface SmartColumnSchema {
/** 稳定唯一 id(拖拽 / 删除 / 持久化必填) */
id: string
/** 对应 el-table-column 的 prop / 数据字段 */
prop: string
/** 列标题 */
label: string
minWidth?: number
width?: number | string
/** 是否可排序;远程分页下 true 会自动提升为 'custom' */
sortable?: boolean | 'custom'
/** 是否作为查询条件 */
searchable?: boolean
/** 是否出现在新增/编辑表单 */
formable?: boolean
/** 表单是否必填(仅 formable 时有效) */
required?: boolean
/** 表单 / 查询控件类型 */
formType?: SmartFormControlType
/** select / radio / cascader / tree-select 等选项 */
formOptions?: SmartSelectOption[]
/** 查询占位符 */
searchPlaceholder?: string
/** 固定列 */
fixed?: boolean | 'left' | 'right'
/** 是否在表格中显示,默认 true */
visible?: boolean
/** fixed:业务预置;custom:列管理新增 */
source?: 'fixed' | 'custom'
/** 是否允许删除(fixed 默认不可删,custom 默认可删) */
deletable?: boolean
/** 透传给 el-table-column */
columnProps?: Record<string, unknown>
/** 透传给表单控件 */
formProps?: Record<string, unknown>
}formType 一览
| formType | 说明 |
|----------|------|
| input | 输入框(默认) |
| password | 密码 |
| textarea | 多行文本 |
| number | 数字 |
| select / select-multiple | 下拉单选 / 多选 |
| radio | 单选 |
| checkbox / checkbox-group | 复选 / 复选组 |
| switch | 开关 |
| date / datetime | 日期 / 日期时间 |
| daterange / datetimerange | 日期范围 / 日期时间范围(亦支持 date-range / datetime-range 别名) |
| time / timerange | 时间 / 时间范围(亦支持 time-range) |
| month / year / week | 月 / 年 / 周 |
| cascader | 级联 |
| tree-select | 树选择 |
| autocomplete | 自动完成 |
| rate / slider / color | 评分 / 滑块 / 颜色 |
选项结构:
interface SmartSelectOption {
label: string
value: string | number | boolean
disabled?: boolean
children?: SmartSelectOption[] // cascader / tree-select
}列配置示例
const columns: SmartColumnSchema[] = [
{
id: 'workshop',
prop: 'workshop',
label: '车间',
minWidth: 120,
searchable: true,
formable: true,
required: true,
formType: 'tree-select',
formOptions: [
{
label: '东区',
value: 'east',
children: [
{ label: '一车间', value: '一车间' },
{ label: '二车间', value: '二车间' },
],
},
],
formProps: { checkStrictly: false, filterable: true },
source: 'fixed',
deletable: false,
},
{
id: 'duration',
prop: 'duration',
label: '持续时长(分)',
minWidth: 120,
sortable: 'custom',
searchable: true,
formable: true,
formType: 'number',
formProps: { min: 0, max: 999, controlsPosition: 'right' },
source: 'fixed',
},
]Props
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| data | Record<string, unknown>[] | [] | 表格数据 |
| columns | SmartColumnSchema[] | [] | 列 schema;支持 v-model:columns |
| showToolbar | boolean | true | 是否展示工具栏 |
| showColumnEditor | boolean | true | 是否展示「编辑列」按钮 |
| showRefresh | boolean | true | 是否展示刷新按钮 |
| showCreate | boolean | true | 是否展示默认「新增」按钮 |
| showSeq | boolean | true | 是否展示序号列 |
| seqWidth | number \| string | 60 | 序号列宽度 |
| showSelection | boolean | false | 是否展示多选列 |
| showSearch | boolean | true | 是否展示查询区 |
| searchCollapsible | boolean | true | 查询区是否可收起展开 |
| searchCollapseVisibleCount | number | 3 | 收起时展示的字段数 |
| searchDefaultExpanded | boolean | false | 查询区默认是否展开 |
| persistKey | string | — | 有值时列配置写入 localStorage |
| formTitle | string | '' | 弹框标题前缀,如「告警」→「新增告警」 |
| formWidth | number \| string | '45%' | 弹框宽度 |
| loading | boolean | false | 加载中(骨架 / 转圈) |
| skeleton | boolean \| ProSmartTableSkeletonConfig | true | 骨架屏;false 关闭 |
| pagination | false \| ProSmartTablePagination | {} | 分页;false 关闭 |
skeleton 配置
interface ProSmartTableSkeletonConfig {
rows?: number // 骨架行数,默认取 pageSize(上限 10),否则 5
animated?: boolean // 默认 true
onlyEmpty?: boolean // 默认 true:仅无数据时用骨架,有数据刷新仍用转圈
}pagination 配置
interface ProSmartTablePagination {
currentPage?: number
pageSize?: number
total?: number
pageSizes?: number[]
layout?: string // 默认 total, sizes, prev, pager, next, jumper
background?: boolean
size?: 'large' | 'default' | 'small' // 默认 small
pagerCount?: number
/** local:前端切片;remote:data 为当前页,需传 total */
mode?: 'local' | 'remote'
}未显式传 mode 时:有 total 走 remote,否则 local。
Events
| 事件 | 参数 | 说明 |
|------|------|------|
| update:columns | SmartColumnSchema[] | 列变更(配合 v-model:columns) |
| columns-change | SmartColumnSchema[] | 列管理保存成功 |
| columns-reset | — | 列管理「恢复默认」;业务侧应清空服务端配置 |
| search | model | 点击查询,或重置后也会再抛一次 search |
| reset | — | 点击重置 |
| create | model | 新增弹框确认(已过必填校验) |
| update | model | 编辑弹框确认(已过必填校验) |
| page-change | { currentPage, pageSize } | 页码 / 每页条数变化 |
| update:pagination | ProSmartTablePagination | 分页受控同步 |
| refresh | — | 点击刷新 |
| sort-change | { column, prop, order } | 透传 el-table;远程排序请监听此事件 |
reset 后组件会清空查询模型并发出 search(带空条件),远程列表可统一在 @search 里拉数。
插槽
| 插槽 | 作用域 | 说明 |
|------|--------|------|
| toolbar-left | — | 工具栏左侧;覆盖默认「新增」时可配合 show-create={false} |
| toolbar-right | — | 工具栏右侧(刷新 / 编辑列按钮之前) |
| col-{prop} | el-table 单元格 scope | 自定义列渲染,如 #col-status |
| (默认插槽) | — | 追加额外 el-table-column(如操作列) |
| form-{prop} | { model, column, value, mode } | 覆盖某个表单字段;mode: create | edit |
| form-extra | { model, mode, visible } | 写在弹框内部,用于嵌套二级弹框 |
| pagination | — | 完全自定义分页 |
| skeleton | — | 自定义骨架屏 |
自定义列 + 操作列
<ProSmartTable :data="data" :columns="columns" ...>
<template #col-status="{ row }">
<el-tag :type="row.status === '启用' ? 'success' : 'info'">
{{ row.status }}
</el-tag>
</template>
<el-table-column label="操作" width="140" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="tableRef?.openEdit(row)">
编辑
</el-button>
</template>
</el-table-column>
</ProSmartTable>方法(expose)
interface ProSmartTableExpose {
tableRef: TableInstance | null // 底层 el-table
openColumnEditor: () => void
openCreate: () => void
openEdit: (row: Record<string, unknown>) => void
getSearchModel: () => Record<string, unknown>
getColumns: () => SmartColumnSchema[]
}<script setup lang="ts">
import { ref } from 'vue'
import type { ProSmartTableExpose } from '@hz_yujin/vue-smart-table'
const tableRef = ref<ProSmartTableExpose>()
function onEdit(row: Record<string, unknown>) {
tableRef.value?.openEdit(row)
}
</script>
<template>
<ProSmartTable ref="tableRef" ... />
</template>分页
本地分页(默认)
不传 total 时,组件对 data 做前端切片:
<ProSmartTable :data="allRows" :columns="columns" />远程分页
<script setup lang="ts">
import { ref } from 'vue'
import type { ProSmartTablePagination } from '@hz_yujin/vue-smart-table'
const pagination = ref<ProSmartTablePagination>({
currentPage: 1,
pageSize: 10,
total: 0,
mode: 'remote',
pageSizes: [10, 20, 50],
})
async function onPageChange({ currentPage, pageSize }) {
pagination.value = { ...pagination.value, currentPage, pageSize }
await fetchList()
}
</script>
<template>
<ProSmartTable
:data="list"
:columns="columns"
v-model:pagination="pagination"
:loading="loading"
@page-change="onPageChange"
@search="onSearch"
/>
</template>关闭分页::pagination="false"。
远程列表(推荐写法)
与 playground SmartTableDemo 一致的模式:
v-model:pagination+mode: 'remote'@search/@reset(重置也会走search)回到第 1 页并拉数@page-change更新页码后拉数@create/@update调接口后刷新- 自定义「新增」时设
:show-create="false",用#toolbar-left+openCreate()
const lastSearchModel = ref<Record<string, unknown>>({})
const pagination = ref<ProSmartTablePagination>({
currentPage: 1,
pageSize: 10,
total: 0,
mode: 'remote',
})
async function onSearch(model: Record<string, unknown>) {
lastSearchModel.value = { ...model }
pagination.value = { ...pagination.value, currentPage: 1 }
await fetchList()
}
async function onPageChange(payload: { currentPage: number; pageSize: number }) {
pagination.value = { ...pagination.value, ...payload }
await fetchList()
}
async function fetchList() {
loading.value = true
try {
const res = await api.list({
...lastSearchModel.value,
currentPage: pagination.value.currentPage,
pageSize: pagination.value.pageSize,
})
tableData.value = res.list
pagination.value = {
...pagination.value,
total: res.total,
currentPage: res.currentPage,
pageSize: res.pageSize,
}
} finally {
loading.value = false
}
}查询 / 重置时组件会 silent 把页码置 1(不额外抛 page-change),避免连打两次接口;业务只需在 @search 里请求即可。
排序
- 本地:
sortable: true,el-table 本地排序即可。 - 远程:设
sortable: 'custom',或分页为remote时sortable: true会自动提升为custom,监听@sort-change:
function onSortChange(payload: {
prop: string
order: 'ascending' | 'descending' | null
}) {
sortState.value = !payload.prop || !payload.order
? {}
: {
sortBy: payload.prop,
sortOrder: payload.order === 'ascending' ? 'asc' : 'desc',
}
pagination.value = { ...pagination.value, currentPage: 1 }
void fetchList()
}
sort-change由组件显式转发,不会再落入 attrs 重复绑定到el-table。
列管理与持久化
右上角「编辑列」打开 ProColumnEditor,可:
- 显隐、拖拽排序
- 改 field / 标题 / 最小宽
- 开关:排序 / 查询 / 表单
- 改表单类型、左右固定
- 新增自定义列、删除(受
deletable控制) - 「恢复默认」
本地持久化
<ProSmartTable persist-key="alarm-list" v-model:columns="columns" ... />有 persistKey 时,保存列配置写入 localStorage;恢复默认会清除该 key。
服务端持久化
<ProSmartTable
v-model:columns="columns"
@columns-change="(cols) => saveTableColumns(cols)"
@columns-reset="() => saveTableColumns([])"
/>「恢复默认」后组件会应用内置默认列(首次传入的 columns 快照)并发出 columns-reset。业务侧建议把服务端配置存成 [],下次读取空数组时回落默认列。
表单自定义 / 嵌套弹框
<ProSmartTable ...>
<!-- 覆盖某个表单字段 -->
<template #form-deviceName="{ model }">
<el-input :model-value="model.deviceName" readonly placeholder="请选择设备">
<template #append>
<el-button @click="pickerVisible = true">选择</el-button>
</template>
</el-input>
</template>
<!-- 二级弹框必须写在 form-extra 内(父级弹框插槽) -->
<template #form-extra="{ model }">
<ProDialog v-model="pickerVisible" nested title="选择设备" show-footer>
<!-- 选中后写回 model.deviceName / model.deviceCode -->
</ProDialog>
</template>
</ProSmartTable>#form-{prop}:model/column/value/mode(create|edit)#form-extra:model/mode/visible
必填:required: true 时,null / undefined / 空串 / 纯空格 / 空数组视为未填;0 / false 合法。
骨架屏与加载
| 场景 | 表现 |
|------|------|
| loading=true 且无数据 | 默认骨架屏(onlyEmpty: true) |
| loading=true 且已有数据 | 表格上转圈 |
| 点击刷新 | 先发 refresh,并等到外部 loading 结束再停动画 |
<!-- 关闭骨架 -->
<ProSmartTable :skeleton="false" :loading="loading" />
<!-- 自定义 -->
<ProSmartTable :skeleton="{ rows: 8, animated: true, onlyEmpty: true }" />全局注册
import VueSmartTable from '@hz_yujin/vue-smart-table'
app.use(VueSmartTable)注册后可直接使用 <ProSmartTable> / <ProColumnEditor>。
也可按需引入:
import {
ProSmartTable,
ProColumnEditor,
SMART_FORM_CONTROL_OPTIONS,
createColumnId,
normalizeColumn,
cloneColumns,
createEmptyColumn,
resolveFormControlType,
} from '@hz_yujin/vue-smart-table'工具函数
| 函数 | 说明 |
|------|------|
| createColumnId(prefix?) | 生成列 id |
| normalizeColumn(col) | 补齐默认值(visible / formType / deletable 等) |
| cloneColumns(cols) | 深拷贝并 normalize |
| createEmptyColumn(partial?) | 新建 custom 列 |
| resolveFormControlType(type) | 统一别名(如 date-range → daterange) |
| SMART_FORM_CONTROL_OPTIONS | 列管理里表单类型下拉选项 |
本地开发
# 在 monorepo 根目录
pnpm --filter @hz_yujin/vue-smart-table build
# playground 演示页:SmartTableDemo
pnpm --filter playground devLicense
MIT
