Appearance
BizTable 业务表格
基于 el-table 二次封装的业务表格:schema 配置化渲染列、多选、序号列、分页封装、默认自适应父级高度,可选工具栏(刷新 / 列显隐 / 密度 / 全屏)与远程数据源。原生 props、事件、插槽通过 v-bind="$attrs" 完整透传给 el-table。
基础用法
本地数据 + schema 列配置。type: 'selection' 开启多选,type: 'index' 开启序号列,enums 将枚举值映射为 el-tag,slot 自定义单元格。组件默认自适应父级高度(父容器设置确定高度即可),表头固定、内容滚动、分页常驻底部。
共 37 条
- 1
- 2
- 3
- 4
页
本地数据自动分页(传入
data 时组件内部按页切片),勾选行后点击「获取选中行」查看 expose 方法效果。<script setup lang="ts">
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn, BizTableInstance } from 'vue3-ep-biz-lib'
type UserRow = {
id: number
name: string
age: number
status: 0 | 1
address: string
}
const data = ref<UserRow[]>(
Array.from({ length: 37 }, (_, i) => ({
id: i + 1,
name: `用户${i + 1}`,
age: 20 + (i % 15),
status: (i % 3 === 0 ? 1 : 0) as 0 | 1,
address: `杭州市西湖区文一西路 ${i + 1} 号`,
}))
)
const columns: BizTableColumn<UserRow>[] = [
{ type: 'selection', width: 42, fixed: 'left' },
{ type: 'index', label: '序号', width: 56, fixed: 'left' },
{ prop: 'name', label: '姓名', minWidth: 90 },
{ prop: 'age', label: '年龄', width: 80, sortable: true },
{
prop: 'status',
label: '状态',
width: 84,
enums: [
{ value: 1, label: '启用', tag: 'success' },
{ value: 0, label: '停用', tag: 'danger' },
],
},
{ prop: 'address', label: '地址', minWidth: 130, showOverflowTooltip: true },
{ label: '操作', width: 116, fixed: 'right', slot: 'action' },
]
const tableRef = ref<BizTableInstance<UserRow>>()
const onSelectChange = (rows: UserRow[]): void => {
if (rows.length > 0) ElMessage.info(`已选 ${rows.length} 行`)
}
const onEdit = (row: UserRow): void => {
ElMessage.success(`编辑:${row.name}`)
}
const onRemove = (row: UserRow): void => {
data.value = data.value.filter((item) => item.id !== row.id)
ElMessage.success(`删除:${row.name}`)
}
const showSelection = (): void => {
const rows = tableRef.value?.getSelectionRows() ?? []
ElMessage.info(`当前选中 ${rows.length} 行`)
}
</script>
<template>
<!-- 自适应父级高度:父容器给确定高度即可 -->
<div style="height: 360px">
<BizTable
ref="tableRef"
:columns="columns"
:data="data"
row-key="id"
stripe
border
@selection-change="onSelectChange"
>
<template #action="{ row }">
<ElButton link type="primary" @click="onEdit(row)">编辑</ElButton>
<ElButton link type="danger" @click="onRemove(row)">删除</ElButton>
</template>
</BizTable>
</div>
<!-- 高度容器之外的操作区,不会被表格高度挤占 -->
<ElButton style="margin-top: 12px" @click="showSelection">获取选中行</ElButton>
</template>
显示代码
远程数据源
传入 dataSource 函数即切换为远程模式:组件自动管理 loading、分页、sortable: 'custom' 排序联动刷新;params 变化后自动回到第一页重新请求(常配合搜索表单使用)。组件内不耦合任何接口实现,mock 仅在 demo 中。
部门列表
暂无数据
搜索触发
params 变化 → 自动回页并重新请求;点击「成立时间 / ID」表头体验远程排序;工具栏默认全量开启。<script setup lang="ts">
import { ref } from 'vue'
import { ElButton, ElInput } from 'element-plus'
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn, BizTableDataSource } from 'vue3-ep-biz-lib'
type DeptRow = {
id: number
name: string
leader: string
createdAt: string
status: 0 | 1 | 2
}
/** mock 数据源:业务侧替换为真实接口即可(组件内不耦合任何请求实现) */
const dataSource: BizTableDataSource<DeptRow> = async ({ currentPage, pageSize, sort, params }) => {
await new Promise((resolve) => setTimeout(resolve, 400))
const keyword = String(params.keyword ?? '')
const all: DeptRow[] = Array.from({ length: 53 }, (_, i) => ({
id: i + 1,
name: `研发部-${i + 1} 组`,
leader: `负责人${(i % 5) + 1}`,
createdAt: `2026-0${(i % 8) + 1}-1${i % 9}`,
status: (i % 3) as 0 | 1 | 2,
}))
let list = keyword === '' ? all : all.filter((row) => row.name.includes(keyword))
// 演示 sortable: 'custom' 远程排序
const prop = sort.prop as keyof DeptRow | undefined
if (prop !== undefined && sort.order !== null && sort.order !== undefined) {
list = [...list].sort((a, b) => {
const result = String(a[prop]).localeCompare(String(b[prop]), 'zh-CN')
return sort.order === 'ascending' ? result : -result
})
}
const start = (currentPage - 1) * pageSize
return { list: list.slice(start, start + pageSize), total: list.length }
}
const columns: BizTableColumn<DeptRow>[] = [
{ prop: 'id', label: 'ID', width: 80, sortable: 'custom' },
{ prop: 'name', label: '部门名称', minWidth: 140 },
{ prop: 'leader', label: '负责人', width: 110 },
{
prop: 'status',
label: '状态',
width: 100,
enums: [
{ value: 0, label: '筹备中', tag: 'info' },
{ value: 1, label: '运转中', tag: 'success' },
{ value: 2, label: '已归档', tag: 'warning' },
],
},
{ prop: 'createdAt', label: '成立时间', width: 130, sortable: 'custom' },
]
/** 业务查询参数:变化后自动回到第一页并重新请求 */
const params = ref<Record<string, unknown>>({})
const keyword = ref('')
const onSearch = (): void => {
params.value = { ...params.value, keyword: keyword.value }
}
const onReset = (): void => {
keyword.value = ''
params.value = {}
}
</script>
<template>
<!-- 搜索区在高度容器之外,autoHeight 只作用于表格自身 -->
<div style="display: flex; gap: 8px; padding-bottom: 12px">
<ElInput
v-model="keyword"
placeholder="按部门名称搜索"
style="width: 220px"
clearable
@keyup.enter="onSearch"
/>
<ElButton type="primary" @click="onSearch">搜索</ElButton>
<ElButton @click="onReset">重置</ElButton>
</div>
<div style="height: 340px">
<BizTable
:columns="columns"
:data-source="dataSource"
:params="params"
:toolbar="{ title: '部门列表' }"
/>
</div>
</template>
显示代码
工具栏
toolbar 传 true 全量开启,或传对象细项控制 title / refresh / columnSetting / density / fullscreen;toolbar-left、toolbar-right 插槽可放置业务内容。
订单流水
实时同步
刷新、列显隐、密度切换(宽松 / 默认 / 紧凑)、全屏(Esc 退出)。
<script setup lang="ts">
import { ref } from 'vue'
import { ElButton, ElMessage, ElTag } from 'element-plus'
import { Download } from '@element-plus/icons-vue'
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn, BizTableInstance, BizTableSize } from 'vue3-ep-biz-lib'
type OrderRow = {
id: number
orderNo: string
amount: number
channel: string
}
const data = ref<OrderRow[]>(
Array.from({ length: 23 }, (_, i) => ({
id: i + 1,
orderNo: `SO2026${String(i + 1).padStart(6, '0')}`,
amount: ((i + 1) * 137) % 5000,
channel: ['线上', '门店', '渠道'][i % 3] ?? '线上',
}))
)
const columns: BizTableColumn<OrderRow>[] = [
{ type: 'index', label: '序号', width: 64 },
{ prop: 'orderNo', label: '订单号', minWidth: 150 },
{ prop: 'channel', label: '渠道', width: 100 },
{ prop: 'amount', label: '金额(元)', width: 120, align: 'right' },
]
const tableRef = ref<BizTableInstance<OrderRow>>()
const onDensityChange = (density: BizTableSize): void => {
ElMessage.info(`当前密度:${density}`)
}
const onExport = (): void => {
ElMessage.success(`导出 ${tableRef.value?.getSelectionRows().length ?? 0} 条记录`)
}
</script>
<template>
<div style="height: 340px">
<BizTable
ref="tableRef"
:columns="columns"
:data="data"
:pagination="false"
:toolbar="{ title: '订单流水' }"
@density-change="onDensityChange"
>
<template #toolbar-right>
<ElTag type="info" effect="plain" style="margin-right: 8px">实时同步</ElTag>
<ElButton type="primary" :icon="Download" size="small" @click="onExport">导出</ElButton>
</template>
</BizTable>
</div>
</template>
显示代码
列设置增强(拖拽排序 + 固定列)
工具栏「列设置」面板能力:勾选控制列显隐;拖动行调整列顺序——功能性列(多选 / 序号 / 展开)始终保持在最前,拖拽之后新增的列自动追加到末尾;点「左 / 右」固定列,再点同侧取消固定(也可取消列配置自带的 fixed)。设置随 remember-settings 持久化到 localStorage(列配置变更后失效 key 自动清理),column-setting-change 事件携带完整设置快照 { hidden, order, fixed },其中 fixed 值为 'left' | 'right' | false(false 表示显式取消固定)。
打开工具栏「列设置」:拖动行调整列顺序、点「左 / 右」固定列(再点取消)、勾选控制显隐; remember-settings 持久化后刷新页面仍在。当前设置快照: 未调整
拖动行调整列顺序、点「左 / 右」固定列(再点取消)、勾选控制显隐;remember-settings 持久化后刷新页面仍在。
<script setup lang="ts">
/** 场景:列设置增强——拖拽排序 + 左/右固定 + 显隐,设置可持久化(remember-settings) */
import { ref } from 'vue'
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumnSettingSnapshot } from 'vue3-ep-biz-lib'
type Row = { id: number; orderNo: string; customer: string; channel: string; amount: number; status: string; owner: string; createdAt: string }
const rows: Row[] = Array.from({ length: 8 }, (_, i) => ({
id: i + 1,
orderNo: `SO-2026${1200 + i}`,
customer: `客户${i + 1}号`,
channel: i % 2 === 0 ? '线上' : '线下',
amount: 2000 + i * 350,
status: i % 3 === 0 ? '已支付' : '待支付',
owner: ['张三', '李四', '王五'][i % 3],
createdAt: `2026-10-${String(10 + i).padStart(2, '0')}`,
}))
const lastSetting = ref<BizTableColumnSettingSnapshot | null>(null)
const columns = [
{ prop: 'orderNo', label: '订单号', width: 120 },
{ prop: 'customer', label: '客户', width: 110 },
{ prop: 'channel', label: '渠道', width: 80 },
{ prop: 'amount', label: '金额', width: 100 },
{ prop: 'status', label: '状态', width: 90 },
{ prop: 'owner', label: '跟进人', width: 90 },
{ prop: 'createdAt', label: '创建时间', width: 110 },
]
</script>
<template>
<div>
<BizTable
:columns="columns"
:data="rows"
:pagination="false"
toolbar
remember-settings="demo-column-setting-plus"
@column-setting-change="(_hidden, settings) => (lastSetting = settings)"
/>
<p style="margin-top: 10px; font-size: 13px; color: var(--el-text-color-secondary)">
打开工具栏「列设置」:拖动行调整列顺序、点「左 / 右」固定列(再点取消)、勾选控制显隐;
remember-settings 持久化后刷新页面仍在。当前设置快照:
<code v-if="lastSetting">order={{ lastSetting.order.slice(0, 3).join('→') }}…,fixed={{ JSON.stringify(lastSetting.fixed) }}</code>
<span v-else>未调整</span>
</p>
</div>
</template>
显示代码
自定义渲染
多级表头(children 嵌套)、自定义表头(headerSlot)、自定义单元格(slot)、展开行(type: 'expand' 配合 #expand 插槽)。
优先级:slot > enums > formatter(el-table 原生)。
<script setup lang="ts">
import { ref } from 'vue'
import {
ElDescriptions,
ElDescriptionsItem,
ElIcon,
ElTag,
ElTooltip,
} from 'element-plus'
import { QuestionFilled } from '@element-plus/icons-vue'
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn } from 'vue3-ep-biz-lib'
type SkuRow = {
id: number
sku: string
price: number
stock: number
city: string
zip: string
remark: string
}
const data = ref<SkuRow[]>(
Array.from({ length: 6 }, (_, i) => ({
id: i + 1,
sku: `SKU-${String(1000 + i)}`,
price: 99 + i * 10,
stock: 50 + i * 30,
city: `杭州${i + 1} 号仓`,
zip: `3100${i}`,
remark: `备注信息 ${i + 1}:多级表头 / 自定义表头 / 展开行演示`,
}))
)
const columns: BizTableColumn<SkuRow>[] = [
{ type: 'expand', width: 48 },
{ prop: 'sku', label: 'SKU 编码', minWidth: 120 },
{
label: '库存分布',
headerAlign: 'center',
children: [
{ prop: 'city', label: '仓库城市', minWidth: 120, align: 'center' },
{ prop: 'zip', label: '邮编', width: 100, align: 'center' },
],
},
{ prop: 'price', label: '单价', width: 100, align: 'right', headerSlot: 'price-header' },
{ prop: 'stock', label: '库存', width: 110, align: 'right', slot: 'stock' },
]
</script>
<template>
<!-- 无固定高度场景:关闭 autoHeight,表格随内容自然撑开 -->
<BizTable :columns="columns" :data="data" :pagination="false" :auto-height="false">
<!-- 自定义表头 -->
<template #price-header>
<span style="display: inline-flex; align-items: center; gap: 4px">
单价
<ElTooltip content="含税价格,含 13% 增值税" placement="top">
<ElIcon style="color: var(--el-text-color-secondary)"><QuestionFilled /></ElIcon>
</ElTooltip>
</span>
</template>
<!-- 自定义单元格:库存低于 100 预警 -->
<template #stock="{ row }">
<ElTag :type="row.stock < 100 ? 'danger' : 'success'" disable-transitions>
{{ row.stock < 100 ? '偏低' : '充足' }}({{ row.stock }})
</ElTag>
</template>
<!-- 展开行 -->
<template #expand="{ row }">
<ElDescriptions :column="3" border size="small" style="padding: 4px 48px">
<ElDescriptionsItem label="SKU">{{ row.sku }}</ElDescriptionsItem>
<ElDescriptionsItem label="仓库">{{ row.city }}({{ row.zip }})</ElDescriptionsItem>
<ElDescriptionsItem label="备注">{{ row.remark }}</ElDescriptionsItem>
</ElDescriptions>
</template>
</BizTable>
</template>
显示代码
高度说明
- 默认
auto-height:容器display: flex纵向布局,表格区域撑满剩余高度,需父级有确定高度; - 直接透传 el-table 原生
height/max-height属性时,以透传值为准; auto-height: false时表格按内容自然撑高,随页面滚动。
区域-设备树形表
共 2 条
- 1
页
树形场景:children 层级数据 + tree-props / default-expand-all 经 attrs 透传 el-table,三层展开。
<script setup lang="ts">
/** 场景:区域-设备树形表——children 层级展开(tree-props 经 attrs 透传) */
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn } from 'vue3-ep-biz-lib'
type RegionRow = {
id: number
name: string
type: string
deviceCount: number
status?: string
children?: RegionRow[]
}
const data: RegionRow[] = [
{
id: 1,
name: '华东园区',
type: '区域',
deviceCount: 128,
children: [
{ id: 11, name: '一号车间', type: '车间', deviceCount: 66, status: '在线' },
{
id: 12,
name: '二号车间',
type: '车间',
deviceCount: 62,
status: '部分离线',
children: [
{ id: 121, name: '温湿度探头 A 组', type: '设备组', deviceCount: 30, status: '在线' },
{ id: 122, name: '电表 B 组', type: '设备组', deviceCount: 32, status: '部分离线' },
],
},
],
},
{ id: 2, name: '华北园区', type: '区域', deviceCount: 56, children: [{ id: 21, name: '总装车间', type: '车间', deviceCount: 56, status: '在线' }] },
]
const columns: BizTableColumn<RegionRow>[] = [
{ prop: 'name', label: '名称', minWidth: 200 },
{ prop: 'type', label: '层级', width: 90 },
{ prop: 'deviceCount', label: '设备数', width: 90, align: 'right' },
{ prop: 'status', label: '状态', width: 110, slot: 'status' },
]
</script>
<template>
<BizTable
:columns="columns"
:data="data"
row-key="id"
:default-expand-all="true"
:tree-props="{ children: 'children' }"
:toolbar="false"
>
<template #status="{ row }">
<span :style="{ color: row.status === '在线' ? 'var(--el-color-success)' : row.status === '部分离线' ? 'var(--el-color-warning)' : 'var(--el-text-color-placeholder)' }">
{{ row.status ?? '—' }}
</span>
</template>
</BizTable>
</template>
显示代码
发货汇总表(合计行)
共 4 条
- 1
页
汇总场景:show-summary + 自定义 summaryMethod 千分位合计(attrs 透传 el-table),formatter 格式化金额列。
<script setup lang="ts">
/** 场景:发货汇总表——show-summary 合计行 + 千分位汇总(attrs 透传) */
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn } from 'vue3-ep-biz-lib'
type ShipRow = {
id: number
region: string
orders: number
boxes: number
weightKg: number
freight: number
}
const data: ShipRow[] = [
{ id: 1, region: '华东', orders: 1284, boxes: 5620, weightKg: 18240, freight: 48210 },
{ id: 2, region: '华北', orders: 968, boxes: 4130, weightKg: 14020, freight: 36180 },
{ id: 3, region: '华南', orders: 1502, boxes: 6480, weightKg: 21030, freight: 52940 },
{ id: 4, region: '西南', orders: 503, boxes: 2210, weightKg: 7960, freight: 21870 },
]
const columns: BizTableColumn<ShipRow>[] = [
{ prop: 'region', label: '区域', width: 110 },
{ prop: 'orders', label: '订单数', align: 'right' },
{ prop: 'boxes', label: '箱数', align: 'right' },
{ prop: 'weightKg', label: '重量(kg)', align: 'right', formatter: (row) => row.weightKg.toLocaleString('zh-CN') },
{ prop: 'freight', label: '运费(元)', align: 'right', formatter: (row) => row.freight.toLocaleString('zh-CN') },
]
const summary = (param: { columns: Array<{ property: string }>; data: ShipRow[] }): Array<string> => {
const { columns: cols, data: rows } = param
return cols.map((col, index) => {
if (index === 0) return '合计'
if (['orders', 'boxes', 'weightKg', 'freight'].includes(col.property)) {
const total = rows.reduce((sum, row) => sum + Number(row[col.property as keyof ShipRow] ?? 0), 0)
return total.toLocaleString('zh-CN')
}
return ''
})
}
</script>
<template>
<BizTable :columns="columns" :data="data" show-summary :summary-method="summary" :toolbar="false" />
</template>
显示代码
主从联动(单选高亮)
共 3 条
- 1
页
点击左侧行查看详情
主从场景:highlight-current-row + current-change(attrs 透传),点击行右侧实时预览详情,未选中显示空态。
<script setup lang="ts">
/** 场景:主从联动——单选行高亮,右侧实时预览该行详情 */
import { ref } from 'vue'
import { ElDescriptions, ElDescriptionsItem, ElEmpty } from 'element-plus'
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn } from 'vue3-ep-biz-lib'
type MemberRow = {
id: number
name: string
dept: string
role: string
joinedAt: string
}
const data: MemberRow[] = [
{ id: 1, name: '张伟', dept: '安全管理部', role: '安全负责人', joinedAt: '2021-03-12' },
{ id: 2, name: '李娜', dept: '研发中心', role: '前端负责人', joinedAt: '2022-07-01' },
{ id: 3, name: '王强', dept: '运维中心', role: '值班经理', joinedAt: '2020-11-20' },
]
const columns: BizTableColumn<MemberRow>[] = [
{ prop: 'name', label: '姓名', width: 90 },
{ prop: 'dept', label: '部门' },
{ prop: 'role', label: '岗位' },
{ prop: 'joinedAt', label: '入职', width: 110 },
]
const current = ref<MemberRow | null>(null)
</script>
<template>
<div style="display: flex; gap: 14px; max-width: 860px">
<BizTable
style="flex: 1.4"
:columns="columns"
:data="data"
row-key="id"
highlight-current-row
:toolbar="false"
@current-change="(row: MemberRow | null) => (current = row)"
/>
<div style="flex: 1">
<ElEmpty v-if="current === null" description="点击左侧行查看详情" :image-size="48" />
<ElDescriptions v-else :title="current.name" :column="1" border size="small">
<ElDescriptionsItem label="部门">{{ current.dept }}</ElDescriptionsItem>
<ElDescriptionsItem label="岗位">{{ current.role }}</ElDescriptionsItem>
<ElDescriptionsItem label="入职日期">{{ current.joinedAt }}</ElDescriptionsItem>
</ElDescriptions>
</div>
</div>
</template>
显示代码
订单管理(远程搜索 + 排序 + 左右固定列)
全部状态
订单管理
暂无数据
业务列表典型形态:关键词 / 状态搜索(提交到 params 后自动回页重新请求)、金额与时间远程排序(sortable: 'custom')、 状态枚举 el-tag、金额 formatter 千分位、订单号固定左侧 + 操作列固定右侧(横向滚动时常驻可见)。
<script setup lang="ts">
/** 场景:订单管理列表——关键词/状态搜索 + 远程排序 + 状态枚举标签 + 金额格式化 + 订单号/操作列左右固定 */
import { ref } from 'vue'
import { ElButton, ElInput, ElMessage, ElOption, ElSelect } from 'element-plus'
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn, BizTableDataSource } from 'vue3-ep-biz-lib'
type OrderStatus = 'pending' | 'paid' | 'shipped' | 'closed'
type OrderRow = {
id: number
orderNo: string
customer: string
channel: string
amount: number
status: OrderStatus
owner: string
createdAt: string
}
/** mock 数据源:业务侧替换为真实接口即可 */
const ALL_ROWS: OrderRow[] = Array.from({ length: 66 }, (_, i) => ({
id: i + 1,
orderNo: `SO-2026${1200 + i}`,
customer: `客户${(i % 12) + 1}号`,
channel: ['线上商城', '门店', '分销'][i % 3],
amount: 880 + i * 137,
status: (['pending', 'paid', 'shipped', 'closed'] as const)[i % 4],
owner: ['张三', '李四', '王五', '赵六'][i % 4],
createdAt: `2026-10-${String((i % 20) + 1).padStart(2, '0')}`,
}))
const dataSource: BizTableDataSource<OrderRow> = async ({ currentPage, pageSize, sort, params }) => {
await new Promise((resolve) => setTimeout(resolve, 350))
const keyword = String(params.keyword ?? '')
const status = String(params.status ?? '')
let list = ALL_ROWS.filter(
(row) =>
(keyword === '' || row.customer.includes(keyword) || row.orderNo.includes(keyword)) &&
(status === '' || row.status === status)
)
// 演示 sortable: 'custom' 远程排序(金额 / 创建时间)
if (sort.prop !== undefined && sort.order !== null && sort.order !== undefined) {
const prop = sort.prop as keyof OrderRow
list = [...list].sort((a, b) => {
// 数值列按数值比较,其余按本地化字符串比较
const result =
typeof a[prop] === 'number' && typeof b[prop] === 'number'
? Number(a[prop]) - Number(b[prop])
: String(a[prop]).localeCompare(String(b[prop]), 'zh-CN')
return sort.order === 'ascending' ? result : -result
})
}
const start = (currentPage - 1) * pageSize
return { list: list.slice(start, start + pageSize), total: list.length }
}
const columns: BizTableColumn<OrderRow>[] = [
{ prop: 'orderNo', label: '订单号', width: 130, fixed: 'left' },
{ prop: 'customer', label: '客户', minWidth: 120, showOverflowTooltip: true },
{ prop: 'channel', label: '渠道', width: 100 },
{
prop: 'amount',
label: '金额(元)',
width: 120,
align: 'right',
sortable: 'custom',
formatter: (row) => `¥${row.amount.toLocaleString('zh-CN')}`,
},
{
prop: 'status',
label: '状态',
width: 100,
enums: [
{ value: 'pending', label: '待支付', tag: 'warning' },
{ value: 'paid', label: '已支付', tag: 'success' },
{ value: 'shipped', label: '已发货', tag: 'primary' },
{ value: 'closed', label: '已关闭', tag: 'info' },
],
},
{ prop: 'owner', label: '跟进人', width: 90 },
{ prop: 'createdAt', label: '创建时间', width: 120, sortable: 'custom' },
{ prop: 'order-actions', label: '操作', width: 140, fixed: 'right', slot: 'actions' },
]
const keyword = ref('')
const status = ref('')
const params = ref<Record<string, unknown>>({})
const onSearch = (): void => {
params.value = { keyword: keyword.value, status: status.value }
}
const onReset = (): void => {
keyword.value = ''
status.value = ''
params.value = {}
}
const onView = (row: OrderRow): void => {
ElMessage.info(`查看订单 ${row.orderNo}(${row.customer})`)
}
const onShip = (row: OrderRow): void => {
ElMessage.success(`订单 ${row.orderNo} 已通知发货`)
}
</script>
<template>
<!-- 搜索区放在高度容器外,autoHeight 只作用于表格自身 -->
<div style="display: flex; gap: 8px; padding-bottom: 12px">
<el-input
v-model="keyword"
placeholder="订单号 / 客户"
style="width: 200px"
clearable
@keyup.enter="onSearch"
/>
<el-select v-model="status" placeholder="全部状态" style="width: 140px" clearable>
<el-option label="待支付" value="pending" />
<el-option label="已支付" value="paid" />
<el-option label="已发货" value="shipped" />
<el-option label="已关闭" value="closed" />
</el-select>
<el-button type="primary" @click="onSearch">查询</el-button>
<el-button @click="onReset">重置</el-button>
</div>
<div style="height: 360px">
<BizTable
:columns="columns"
:data-source="dataSource"
:params="params"
:toolbar="{ title: '订单管理' }"
>
<template #actions="{ row }">
<el-button link type="primary" @click="onView(row)">查看</el-button>
<el-button link type="primary" :disabled="row.status !== 'paid'" @click="onShip(row)">
发货
</el-button>
</template>
</BizTable>
</div>
</template>
显示代码
库存预警(单元格 / 行高亮 + 合计)
库存预警清单
共 23 条
- 1
- 2
- 3
页
低库存场景:slot 自定义单元格,低于预警线的库存红色高亮;row-class-name 整行淡红底、 show-summary 合计本页件数与库存金额(均经 attrs 透传 el-table),stripe / border 原生属性透传。
<script setup lang="ts">
/** 场景:库存预警清单——低库存单元格高亮 + 整行淡红底 + 本页合计 + 斑马纹边框 + 本地自动分页 */
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn } from 'vue3-ep-biz-lib'
type StockRow = {
id: number
sku: string
name: string
category: string
stock: number
warningLine: number
unitPrice: number
}
const data: StockRow[] = Array.from({ length: 23 }, (_, i) => ({
id: i + 1,
sku: `SKU-${10000 + i * 7}`,
name: `商品${i + 1}号(${['标准装', '促销装', '家庭装'][i % 3]})`,
category: ['食品', '日用', '数码'][i % 3],
stock: [96, 12, 240, 8, 55][i % 5],
warningLine: 20,
unitPrice: 19.9 + i * 3,
}))
const isAlert = (row: StockRow): boolean => row.stock < row.warningLine
const columns: BizTableColumn<StockRow>[] = [
{ prop: 'sku', label: 'SKU 编码', width: 120 },
{ prop: 'name', label: '商品名称', minWidth: 160, showOverflowTooltip: true },
{ prop: 'category', label: '类目', width: 90 },
{ prop: 'stock', label: '库存', width: 150, align: 'right', slot: 'stock' },
{ prop: 'warningLine', label: '预警线', width: 90, align: 'right' },
{
prop: 'unitPrice',
label: '单价(元)',
width: 110,
align: 'right',
formatter: (row) => row.unitPrice.toFixed(2),
},
]
/** 低于预警线整行淡红底(row-class-name 经 attrs 透传 el-table) */
const rowClassName = ({ row }: { row: StockRow }): string => (isAlert(row) ? 'stock-alert-row' : '')
/** 合计行:统计当前页件数与库存金额 */
const summary = (param: { columns: Array<{ property: string }>; data: StockRow[] }): string[] => {
const { columns: cols, data: rows } = param
const stockTotal = rows.reduce((sum, row) => sum + row.stock, 0)
const amountTotal = rows.reduce((sum, row) => sum + row.stock * row.unitPrice, 0)
return cols.map((col, index) => {
if (index === 0) return `合计(本页 ${rows.length} 项)`
if (col.property === 'stock') return `${stockTotal} 件`
if (col.property === 'unitPrice') return `库存 ¥${Math.round(amountTotal).toLocaleString('zh-CN')}`
return ''
})
}
</script>
<template>
<div style="height: 340px">
<BizTable
:columns="columns"
:data="data"
stripe
border
show-summary
:summary-method="summary"
:row-class-name="rowClassName"
:toolbar="{ title: '库存预警清单', refresh: false, columnSetting: false }"
>
<template #stock="{ row }">
<span :class="['stock-alert-cell', { 'is-danger': row.stock < row.warningLine }]">
{{ row.stock }}{{ row.stock < row.warningLine ? '(低于预警线)' : '' }}
</span>
</template>
</BizTable>
</div>
</template>
<style>
/* 单元格高亮与整行淡红底:el-table 内部节点,需全局样式 */
.stock-alert-cell.is-danger {
color: var(--el-color-danger);
font-weight: 600;
}
.el-table .stock-alert-row {
--el-table-tr-bg-color: var(--el-color-danger-light-9);
}
</style>
显示代码
成员跨页多选(reserve-selection + 批量操作)
选择项目成员 已选 0 人
共 26 条
- 1
- 2
- 3
页
选择场景:selection 列配置 reserve-selection 跨页 / 回页保留选中(依赖 row-key 唯一), selectable 禁用离职成员;toolbar-left 插槽放「批量移除 / 清空选中」,expose 提供 getSelectionRows / clearSelection。
<script setup lang="ts">
/** 场景:项目成员选择——reserve-selection 跨页保留选中 + selectable 禁用离职成员 + 批量移除 */
import { ref } from 'vue'
import { ElButton, ElMessage, ElTag } from 'element-plus'
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn, BizTableInstance } from 'vue3-ep-biz-lib'
type MemberRow = {
id: number
name: string
dept: string
role: 'admin' | 'dev' | 'test' | 'ops'
state: 'active' | 'left'
joinedAt: string
}
const rows = ref<MemberRow[]>(
Array.from({ length: 26 }, (_, i) => ({
id: i + 1,
name: `成员${i + 1}号`,
dept: ['前端组', '后端组', '测试组', '运维组'][i % 4],
role: (['dev', 'test', 'ops', 'admin'] as const)[i % 4],
state: i % 7 === 6 ? 'left' : 'active',
joinedAt: `2025-${String((i % 12) + 1).padStart(2, '0')}-08`,
}))
)
const columns: BizTableColumn<MemberRow>[] = [
{
type: 'selection',
width: 44,
// 跨页 / 回页保留选中(依赖 row-key 唯一)
reserveSelection: true,
// 离职成员不可勾选
selectable: (row) => row.state === 'active',
},
{ type: 'index', label: '#', width: 60 },
{ prop: 'name', label: '姓名', width: 110 },
{ prop: 'dept', label: '所属小组', width: 110 },
{
prop: 'role',
label: '角色',
width: 100,
enums: [
{ value: 'admin', label: '管理员', tag: 'danger' },
{ value: 'dev', label: '研发', tag: 'primary' },
{ value: 'test', label: '测试', tag: 'warning' },
{ value: 'ops', label: '运维', tag: 'success' },
],
},
{
prop: 'state',
label: '状态',
width: 90,
enums: [
{ value: 'active', label: '在职', tag: 'success' },
{ value: 'left', label: '已离职', tag: 'info' },
],
},
{ prop: 'joinedAt', label: '入职时间', width: 120 },
]
const tableRef = ref<BizTableInstance<MemberRow>>()
const selected = ref<MemberRow[]>([])
const onSelectionChange = (current: MemberRow[]): void => {
selected.value = current
}
const onRemove = (): void => {
const ids = new Set(selected.value.map((row) => row.id))
rows.value = rows.value.filter((row) => !ids.has(row.id))
ElMessage.success(`已移除 ${ids.size} 名成员`)
tableRef.value?.clearSelection()
selected.value = []
}
const onClear = (): void => {
tableRef.value?.clearSelection()
}
</script>
<template>
<div style="height: 360px">
<BizTable
ref="tableRef"
:columns="columns"
:data="rows"
:toolbar="{ title: '选择项目成员', refresh: false, columnSetting: false }"
@selection-change="onSelectionChange"
>
<template #toolbar-left>
<span style="font-size: 14px">
选择项目成员
<el-tag style="margin-left: 8px" size="small" type="primary">
已选 {{ selected.length }} 人
</el-tag>
</span>
<el-button
style="margin-left: 12px"
type="danger"
size="small"
plain
:disabled="selected.length === 0"
@click="onRemove"
>
批量移除
</el-button>
<el-button size="small" :disabled="selected.length === 0" @click="onClear">
清空选中
</el-button>
</template>
</BizTable>
</div>
</template>
显示代码
合同台账(多列固定 + 超长省略 + 本地排序)
合同台账
共 35 条
- 1
- 2
- 3
- 4
页
横向滚动场景:左右多列 fixed + show-overflow-tooltip 超长文案省略浮层;sortable: true 为本地排序(当前页内排序,无需远程); row-dblclick 双击行查看(el-table 原生事件透传)。
<script setup lang="ts">
/** 场景:合同台账——左右多列固定横向滚动 + 超长省略浮层 + sortable 本地排序 + 双击行查看 */
import { ElButton, ElMessage } from 'element-plus'
import { BizTable } from 'vue3-ep-biz-lib'
import type { BizTableColumn } from 'vue3-ep-biz-lib'
type ContractRow = {
id: number
contractNo: string
title: string
party: string
type: 'sales' | 'purchase' | 'service' | 'nda'
amount: number
signDate: string
expireDate: string
owner: string
status: 'active' | 'expiring' | 'expired'
}
const LONG_TITLES = [
'关于华东区域 2026 年度产品分销合作及市场推广联合运营的战略框架协议',
'智慧园区综合管理平台一期建设软件开发服务合同(含三年运维支持与二期优先续约权)',
'数据中心机房设备及配套工程采购与安装总承包合同',
'客户数据合规使用与保密协议',
]
const data: ContractRow[] = Array.from({ length: 35 }, (_, i) => ({
id: i + 1,
contractNo: `HT-2026-${String(1001 + i)}`,
title: LONG_TITLES[i % 4],
party: `合作方${(i % 9) + 1}有限公司`,
type: (['sales', 'purchase', 'service', 'nda'] as const)[i % 4],
amount: 36 + i * 17,
signDate: `2026-0${(i % 9) + 1}-1${i % 9}`,
expireDate: `2027-0${(i % 9) + 1}-1${i % 9}`,
owner: ['陈一', '刘二', '张三', '李四'][i % 4],
status: (['active', 'expiring', 'expired'] as const)[i % 3],
}))
const columns: BizTableColumn<ContractRow>[] = [
{ prop: 'contractNo', label: '合同编号', width: 130, fixed: 'left' },
{ prop: 'title', label: '合同名称', minWidth: 260, showOverflowTooltip: true },
{ prop: 'party', label: '签约方', width: 150, showOverflowTooltip: true },
{
prop: 'type',
label: '类型',
width: 90,
enums: [
{ value: 'sales', label: '销售', tag: 'success' },
{ value: 'purchase', label: '采购', tag: 'primary' },
{ value: 'service', label: '服务', tag: 'warning' },
{ value: 'nda', label: '保密', tag: 'info' },
],
},
{
prop: 'amount',
label: '金额(万元)',
width: 120,
align: 'right',
// sortable: true —— 本地排序(数据在当前页内排序,无需远程)
sortable: true,
formatter: (row) => row.amount.toLocaleString('zh-CN'),
},
{ prop: 'signDate', label: '签订日期', width: 110 },
{ prop: 'expireDate', label: '到期日期', width: 110 },
{ prop: 'owner', label: '负责人', width: 90 },
{
prop: 'status',
label: '状态',
width: 90,
enums: [
{ value: 'active', label: '履行中', tag: 'success' },
{ value: 'expiring', label: '即将到期', tag: 'warning' },
{ value: 'expired', label: '已到期', tag: 'info' },
],
},
{ prop: 'contract-actions', label: '操作', width: 90, fixed: 'right', slot: 'actions' },
]
const onRowDblclick = (row: ContractRow): void => {
ElMessage.info(`双击查看:${row.contractNo} · ${row.title.slice(0, 12)}…`)
}
const onView = (row: ContractRow): void => {
ElMessage.info(`查看合同 ${row.contractNo}`)
}
</script>
<template>
<div style="height: 380px">
<BizTable
:columns="columns"
:data="data"
border
:toolbar="{ title: '合同台账' }"
@row-dblclick="onRowDblclick"
>
<template #actions="{ row }">
<el-button link type="primary" @click="onView(row)">查看</el-button>
</template>
</BizTable>
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| columns | BizTableColumn[] | [] | 列 schema 配置,见下方 BizTableColumn |
| data | T[] | [] | 本地数据(与 dataSource 二选一,dataSource 优先) |
| data-source | BizTableDataSource<T> | — | 远程数据源函数,自动管理 loading / 分页 / 排序 |
| loading | boolean | false | 外部受控 loading(本地模式常用) |
| row-key | string | 'id' | 行 key 字段,reserveSelection 保留选中时必需唯一 |
| auto-height | boolean | true | 自适应父级高度(需父级确定高度) |
| pagination | boolean | true | 是否显示分页器;本地模式自动按页切片 |
| v-model:current-page | number | 1 | 当前页码(支持 v-model) |
| v-model:page-size | number | 10 | 每页条数(支持 v-model) |
| page-sizes | number[] | [10, 20, 50, 100] | 每页条数可选项 |
| pager-layout | string | 'total, sizes, prev, pager, next, jumper' | 分页布局 |
| toolbar | boolean | BizTableToolbar | false | 工具栏开关与配置 { title, refresh, columnSetting, density, fullscreen } |
| density | 'large' | 'default' | 'small' | 'default' | 初始密度,工具栏可切换 |
| params | Record<string, unknown> | {} | 远程模式业务查询参数,变化后回第一页重新请求 |
| auto-load | boolean | true | 远程模式挂载后自动请求 |
| auto-fetch | boolean | true | 分页 / 排序 / params 变化自动请求;关闭后由 refresh() 手动触发 |
未列出的属性(
height、max-height、stripe、border、show-header、lazy等)全部透传el-table原生 props。
BizTableColumn 结构
| 字段 | 类型 | 说明 |
|---|---|---|
| type | 'default' | 'selection' | 'index' | 'expand' | 列类型:多选 / 序号 / 展开行 |
| prop / label | string | 字段名 / 表头文案 |
| width / min-width / fixed / align / header-align | — | 同 el-table-column |
| sortable | boolean | 'custom' | 'custom' 时远程模式自动携带排序参数重新请求 |
| enums | BizTableEnumItem[] | 枚举映射 { value, label, tag } 渲染 el-tag |
| slot / headerSlot | string | 自定义单元格 / 表头插槽名 |
| children | BizTableColumn[] | 多级表头子列 |
| selectable / reserve-selection / index / formatter / sort-method 等 | — | 同 el-table-column 原生能力 |
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| update:current-page | (page: number) | 页码变化(v-model:current-page) |
| update:page-size | (size: number) | 每页条数变化(v-model:page-size) |
| page-change | (page: { currentPage, pageSize }) | 页码或条数变化 |
| load-success | (result: { list, total }) | 远程加载成功 |
| load-error | (error: unknown) | 远程加载失败 |
| refresh | — | 工具栏刷新或调用 refresh() |
| density-change | (density: BizTableSize) | 密度变化 |
| column-setting-change | (hidden, settings) | 列设置变化:hidden 为被隐藏列 key 集合,settings 为完整快照 { hidden, order, fixed }(含用户拖拽顺序与固定列;fixed 值 'left' | 'right' | false,false 为显式取消列配置自带的固定) |
| selection-change / select / select-all / sort-change / filter-change / row-click / cell-click / current-change / expand-change 等 | — | 透传 el-table 原生事件 |
Slots
| 插槽名 | 说明 |
|---|---|
| default | 直接书写 el-table-column,与 schema 列混合使用 |
| 动态插槽(column.slot) | 自定义单元格,作用域 { row, column, $index } |
| 动态插槽(column.headerSlot) | 自定义表头,作用域 { column, $index } |
| expand | 展开行内容(type: 'expand'),作用域 { row } |
| empty | 空数据内容(透传 el-table) |
| append | 最后一行追加内容(透传 el-table) |
| toolbar-left | 工具栏左侧(替换标题) |
| toolbar-right | 工具栏右侧按钮区(追加自定义按钮) |
Expose
| 属性 / 方法 | 说明 |
|---|---|
| tableRef | el-table 实例,可调用任意原生方法 |
| getSelectionRows | () => T[],当前多选行 |
| clearSelection | 清空多选 |
| toggleRowSelection | (row, selected?) 设置行选中态 |
| toggleAllSelection | 全选 / 反选 |
| toggleRowExpansion | (row, expanded?) 展开行开关 |
| setCurrentRow | (row) 单选高亮行 |
| clearSort / clearFilter | 清空排序 / 筛选 |
| doLayout | 重新计算表格布局(容器尺寸变化时组件已自动调用) |
| refresh | 远程模式重新请求;本地模式派发 refresh 事件 |
| setDensity | (density) 设置密度 |