Appearance
BizEditableTable 可编辑表格
列配置声明编辑控件(input / number / select / date / time / switch / text / slot),常驻编辑或点击进入编辑;新增 / 复制 / 删除行(可配二次确认与行数上下限),单元格必填 + 数字区间 + 自定义 validator 校验(错误行内标红),数值列自动合计行。mode="form" 切换为卡片子表单:每条记录一张卡片、字段按网格铺开、控件常驻——字段多不适合表格列、或单屏记录数少的录入场景(联系人、资质证照、条款明细)。订单明细、报价单行项、BOM 物料清单、发票行项——「批量行编辑」场景(BizTable 只读、BizFormDialog 单条记录编辑,均不覆盖);轻量键值配置(请求头 / 变量表 / 元数据,Record 模型 + 重复键校验 + JSON 导入导出)用 BizKeyValueEditor。
订单明细行
input / number / select 组合 +
summary-columns 数值合计 + row-template 新行默认值。<script setup lang="ts">
/** 场景:订单明细行——input/number/select 组合 + 金额列自动合计 */
import { ref } from 'vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableColumn, BizEditableRow } from 'vue3-ep-biz-lib'
const columns: BizEditableColumn[] = [
{ prop: 'name', label: '商品名称', type: 'input', required: true, minWidth: 150 },
{
prop: 'unit',
label: '单位',
type: 'select',
width: 80,
options: [
{ label: '个', value: '个' },
{ label: '箱', value: '箱' },
{ label: '套', value: '套' },
],
},
{ prop: 'qty', label: '数量', type: 'number', min: 1, max: 9999, required: true, width: 110 },
{ prop: 'price', label: '单价(元)', type: 'number', min: 0, precision: 2, width: 120 },
]
const rows = ref<BizEditableRow[]>([
{ name: '机械键盘 87 键', unit: '个', qty: 2, price: 399 },
{ name: '无线鼠标', unit: '个', qty: 1, price: 129 },
])
const rowTemplate = (): BizEditableRow => ({ name: '', unit: '个', qty: 1, price: 0 })
</script>
<template>
<div>
<BizEditableTable
v-model="rows"
:columns="columns"
:summary-columns="['qty', 'price']"
:row-template="rowTemplate"
/>
</div>
</template>
显示代码
提交校验
费用报销明细(点击「提交校验」查看标红)
expose
validate() 全量校验,错误标红到单元格并返回错误清单。<script setup lang="ts">
/** 场景:行数据提交校验——必填 + 数字区间 + 自定义 validator,错误标红到单元格 */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableColumn, BizEditableRow, BizEditableTableInstance } from 'vue3-ep-biz-lib'
const tableRef = ref<BizEditableTableInstance>()
const columns: BizEditableColumn[] = [
{ prop: 'account', label: '费用科目', type: 'input', required: true, minWidth: 130 },
{
prop: 'amount',
label: '金额(元)',
type: 'number',
min: 0.01,
max: 100000,
precision: 2,
required: true,
width: 130,
},
{
prop: 'remark',
label: '备注',
type: 'input',
width: 160,
validator: (value) =>
value && String(value).length > 20 ? '备注不能超过 20 字' : true,
},
]
const rows = ref<BizEditableRow[]>([
{ account: '交通费', amount: 0, remark: '' },
{ account: '', amount: 350, remark: '这一条备注特别长特别长特别长特别长特别长' },
])
const submit = async (): Promise<void> => {
const result = await tableRef.value?.validate()
if (result?.valid) {
ElMessage.success(`校验通过,提交 ${rows.value.length} 条费用`)
}
}
</script>
<template>
<div>
<BizEditableTable ref="tableRef" v-model="rows" :columns="columns" :summary-columns="['amount']">
<template #toolbar>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">费用报销明细(点击「提交校验」查看标红)</span>
</template>
</BizEditableTable>
<ElButton type="primary" style="margin-top: 10px" @click="submit">提交校验</ElButton>
</div>
</template>
显示代码
点击进入编辑
edit-trigger="click":单元格平时只读,点击出现控件(失焦 / 选中后退出)。<script setup lang="ts">
/** 场景:点击编辑——edit-trigger=click,单元格平时只读展示、点击进入编辑 */
import { ref } from 'vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableColumn, BizEditableRow } from 'vue3-ep-biz-lib'
const columns: BizEditableColumn[] = [
{ prop: 'warehouse', label: '仓库名称', type: 'input', minWidth: 130 },
{ prop: 'keeper', label: '负责人', type: 'input', width: 110 },
{
prop: 'region',
label: '所属大区',
type: 'select',
width: 110,
options: [
{ label: '华东', value: 'HD' },
{ label: '华南', value: 'HN' },
{ label: '华北', value: 'HB' },
],
},
]
const rows = ref<BizEditableRow[]>([
{ warehouse: '杭州一号仓', keeper: '张三', region: 'HD' },
{ warehouse: '深圳前置仓', keeper: '李四', region: 'HN' },
])
</script>
<template>
<div>
<BizEditableTable v-model="rows" :columns="columns" edit-trigger="click" :addable="false" :deletable="false" />
</div>
</template>
显示代码
slot 自定义列
type: 'slot' + #col-字段名 作用域插槽自定义渲染(进度标签)。<script setup lang="ts">
/** 场景:slot 自定义列——项目排期:日期 + 开关 + 自定义渲染列混排 */
import { ref } from 'vue'
import { ElTag } from 'element-plus'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableColumn, BizEditableRow } from 'vue3-ep-biz-lib'
const columns: BizEditableColumn[] = [
{ prop: 'task', label: '任务', type: 'input', required: true, minWidth: 130 },
{ prop: 'owner', label: '负责人', type: 'input', width: 90 },
{ prop: 'startDate', label: '开始日期', type: 'date', width: 140 },
{ prop: 'blocked', label: '是否阻塞', type: 'switch', width: 90 },
{ prop: 'progress', label: '进度', type: 'slot', width: 90 },
]
const rows = ref<BizEditableRow[]>([
{ task: '接口联调', owner: '张三', startDate: '2026-10-09', blocked: false, progress: 80 },
{ task: '安全评审', owner: '李四', startDate: '2026-10-11', blocked: true, progress: 20 },
])
const progressType = (v: number): 'success' | 'warning' | 'info' => (v >= 80 ? 'success' : v >= 40 ? 'warning' : 'info')
</script>
<template>
<div>
<BizEditableTable v-model="rows" :columns="columns">
<template #col-progress="{ value }">
<ElTag size="small" :type="progressType(Number(value))">{{ value }}%</ElTag>
</template>
</BizEditableTable>
</div>
</template>
显示代码
行复制与删除
copyable 复制行、confirm-delete 删除二次确认、min-rows 保底行数。<script setup lang="ts">
/** 场景:行复制与删除——copyable 复制行、confirmDelete 二次确认、minRows 保底 */
import { ref } from 'vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableColumn, BizEditableRow } from 'vue3-ep-biz-lib'
const columns: BizEditableColumn[] = [
{ prop: 'name', label: '奖项名称', type: 'input', minWidth: 150 },
{ prop: 'count', label: '名额', type: 'number', min: 1, width: 120 },
]
const rows = ref<BizEditableRow[]>([
{ name: '一等奖', count: 1 },
{ name: '二等奖', count: 3 },
])
</script>
<template>
<div style="max-width: 640px">
<BizEditableTable v-model="rows" :columns="columns" copyable :min-rows="1" confirm-delete />
</div>
</template>
显示代码
发票行项
税率 select + 金额精度 + 只读 text 列 + row-add / row-delete 事件日志。
<script setup lang="ts">
/** 场景:发票行项——税率 select + 金额精度 + 只读文本列 + 行事件日志 */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableColumn, BizEditableRow, BizEditableTableInstance } from 'vue3-ep-biz-lib'
const tableRef = ref<BizEditableTableInstance>()
const logs = ref<string[]>([])
const columns: BizEditableColumn[] = [
{ prop: 'item', label: '品目', type: 'input', required: true, minWidth: 120 },
{
prop: 'taxRate',
label: '税率',
type: 'select',
width: 100,
options: [
{ label: '13%', value: 0.13 },
{ label: '9%', value: 0.09 },
{ label: '6%', value: 0.06 },
],
},
{ prop: 'amount', label: '不含税金额', type: 'number', min: 0, precision: 2, width: 130 },
{ prop: 'amountText', label: '价税合计', type: 'text', width: 130 },
]
const rows = ref<BizEditableRow[]>([
{ item: 'SaaS 服务费', taxRate: 0.06, amount: 10000, amountText: '¥10,600.00' },
{ item: '硬件设备', taxRate: 0.13, amount: 5000, amountText: '¥5,650.00' },
])
const onRowAdd = (): void => {
logs.value.unshift('row-add:新行已追加')
}
const onRowDelete = (): void => {
logs.value.unshift('row-delete:行已删除')
}
const submit = async (): Promise<void> => {
const result = await tableRef.value?.validate()
if (result?.valid) ElMessage.success('发票草稿已保存')
}
</script>
<template>
<div>
<BizEditableTable
v-model="rows"
:columns="columns"
:summary-columns="['amount']"
summary-label="价税合计前小计"
@row-add="onRowAdd"
@row-delete="onRowDelete"
/>
<div style="margin-top: 10px; display: flex; gap: 12px; align-items: flex-start">
<ElButton type="primary" @click="submit">保存发票</ElButton>
<div style="font-size: 12px; color: var(--el-text-color-secondary); line-height: 20px">
<div v-for="(log, i) in logs.slice(0, 3)" :key="i">{{ log }}</div>
</div>
</div>
</div>
</template>
显示代码
卡片子表单(form 模式)
最多 5 位
联系人 1(王芳)
联系人 2(未命名)
请选择
mode="form":每条记录一张卡片,字段按 form-columns 网格铺开(col.full 占满整行);卡片标题 form-title 工厂定制,校验 / 增删 / 行数上下限与表格模式同一套契约。<script setup lang="ts">
/** 场景:form 卡片子表单——紧急联系人多条录入(字段多、每条独立成卡) */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableTableInstance } from 'vue3-ep-biz-lib'
const ref1 = ref<BizEditableTableInstance>()
interface Contact {
name: string
relation: string
phone: string
email: string
remark: string
}
const rows = ref<Partial<Contact>[]>([
{ name: '王芳', relation: '配偶', phone: '13800001111', email: 'wangfang@example.com', remark: '首选联系人' },
{ name: '', relation: '', phone: '', email: '', remark: '' },
])
const columns = [
{ prop: 'name', label: '姓名', type: 'input' as const, required: true, placeholder: '必填' },
{
prop: 'relation',
label: '关系',
type: 'select' as const,
required: true,
options: [
{ label: '配偶', value: '配偶' },
{ label: '父母', value: '父母' },
{ label: '子女', value: '子女' },
{ label: '同事', value: '同事' },
],
},
{
prop: 'phone',
label: '手机号',
type: 'input' as const,
required: true,
validator: (v: unknown): string | true =>
typeof v === 'string' && /^1\d{10}$/.test(v) ? true : '手机号格式不正确',
},
{ prop: 'email', label: '邮箱', type: 'input' as const, placeholder: '选填' },
{ prop: 'remark', label: '备注', type: 'input' as const, full: true, placeholder: '占满整行(col.full)' },
]
const onSubmit = async (): Promise<void> => {
const result = await ref1.value?.validate()
if (result?.valid) ElMessage.success(`已保存 ${rows.value.length} 位联系人`)
}
</script>
<template>
<BizEditableTable
ref="ref1"
v-model="rows"
mode="form"
:columns="columns"
:form-columns="2"
:form-title="(row, index) => `联系人 ${index + 1}(${(row.name as string) || '未命名'})`"
:min-rows="1"
:max-rows="5"
empty-text="暂无联系人,点击「新增一行」添加"
>
<template #toolbar><span style="font-size: 13px; color: var(--el-text-color-secondary)">最多 5 位</span></template>
</BizEditableTable> <el-button type="primary" style="margin-top: 12px" @click="onSubmit">保存联系人</el-button>
</template>
显示代码
表格⇄卡片双形态
第 1 条
第 2 条
第 3 条
数量合计: 72
同一份 columns 与数据在
table / form 模式间即时切换(宽表录入与打印视图共用一套配置);form 模式的合计以底部合计条呈现。<script setup lang="ts">
/** 场景:表格⇄卡片双形态——同一份 columns/数据在 table 与 form 模式间切换 */
import { ref } from 'vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
const mode = ref<'table' | 'form'>('form')
interface QuoteItem {
name: string
spec: string
qty: number
price: number
days: number
}
const rows = ref<Partial<QuoteItem>[]>([
{ name: '边缘网关', spec: '4G版·4口RS485', qty: 12, price: 1280, days: 7 },
{ name: '温湿度传感器', spec: '壁挂·电池版', qty: 60, price: 96, days: 5 },
{ name: '', spec: '', qty: undefined, price: undefined, days: undefined },
])
const columns = [
{ prop: 'name', label: '物料名称', type: 'input' as const, required: true },
{ prop: 'spec', label: '规格', type: 'input' as const },
{ prop: 'qty', label: '数量', type: 'number' as const, required: true, min: 1 },
{ prop: 'price', label: '单价(元)', type: 'number' as const, required: true, min: 0, precision: 2 },
{ prop: 'days', label: '交期(天)', type: 'number' as const, min: 1, max: 60 },
]
</script>
<template>
<el-space direction="vertical" size="large" style="width: 100%">
<el-radio-group v-model="mode">
<el-radio-button value="form">卡片模式(mode=form)</el-radio-button>
<el-radio-button value="table">表格模式(默认)</el-radio-button>
</el-radio-group>
<BizEditableTable
v-model="rows"
:mode="mode"
:columns="columns"
:form-columns="3"
:summary-columns="['qty']"
summary-label="合计"
/>
</el-space>
</template>
显示代码
证照录入(日期 + 合计条 + slot)
form 模式组合形态:日期控件、数值合计条(年审费)、slot 附件列占满整行。
<script setup lang="ts">
/** 场景:资质证照录入——form 模式 + 日期控件 + 合计条 + slot 附件列 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Paperclip } from '@element-plus/icons-vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
interface Qualification {
certName: string
certNo: string
issuedAt: string
expireAt: string
annualFee: number
attachment: string
}
const rows = ref<Partial<Qualification>[]>([
{ certName: '安全生产许可证', certNo: 'AQ-2026-0117', issuedAt: '2026-01-10', expireAt: '2029-01-09', annualFee: 2000, attachment: 'safety-license.pdf' },
{ certName: 'ISO9001', certNo: '', issuedAt: '', expireAt: '', annualFee: 3600, attachment: '' },
])
const columns = [
{ prop: 'certName', label: '证书名称', type: 'input' as const, required: true },
{
prop: 'certNo',
label: '证书编号',
type: 'input' as const,
required: true,
validator: (v: unknown, _row: unknown, index: number): string | true =>
index === 0 || (typeof v === 'string' && v.length >= 6) ? true : '证书编号至少 6 位',
},
{ prop: 'issuedAt', label: '发证日期', type: 'date' as const, required: true },
{ prop: 'expireAt', label: '有效期至', type: 'date' as const, required: true },
{ prop: 'annualFee', label: '年审费(元)', type: 'number' as const, min: 0, precision: 2 },
{ prop: 'attachment', label: '附件', type: 'slot' as const, full: true },
]
</script>
<template>
<BizEditableTable
v-model="rows"
mode="form"
:columns="columns"
:form-columns="2"
:form-title="(_row, index) => `证照 ${index + 1}`"
:summary-columns="['annualFee']"
empty-text="暂无证照"
>
<template #col-attachment="{ value }">
<el-link v-if="value" type="primary" :icon="Paperclip">{{ value }}</el-link>
<el-button v-else size="small" @click="ElMessage.info('业务侧对接上传组件')">上传附件</el-button>
</template>
</BizEditableTable>
</template>
显示代码
行级锁定
班次 1
早班
5
2026-10-01
已生效(锁定)
班次 2
中班
5
2026-10-01
已生效(锁定)
班次 3
草稿
列配置
disabled(row) 按行状态锁定:已生效记录整体只读(form 模式字段只读展示,text 列状态文案)。<script setup lang="ts">
/** 场景:行级锁定 + form 模式——已生效记录锁定只读(列 disabled 联动行状态) */
import { computed, ref } from 'vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
interface ShiftRule {
name: string
days: number
status: 'draft' | 'active'
startAt: string
}
const rows = ref<Partial<ShiftRule>[]>([
{ name: '早班', days: 5, status: 'active', startAt: '2026-10-01' },
{ name: '中班', days: 5, status: 'active', startAt: '2026-10-01' },
{ name: '', days: undefined, status: 'draft', startAt: '' },
])
/** 已生效(active)的行锁定:form 模式下字段整体只读展示 */
const columns = computed(() => [
{
prop: 'name',
label: '班次名称',
type: 'input' as const,
required: true,
disabled: (row: Record<string, unknown>) => row.status === 'active',
},
{
prop: 'days',
label: '每周天数',
type: 'number' as const,
min: 1,
max: 7,
disabled: (row: Record<string, unknown>) => row.status === 'active',
},
{
prop: 'startAt',
label: '生效日期',
type: 'date' as const,
disabled: (row: Record<string, unknown>) => row.status === 'active',
},
{
prop: 'status',
label: '状态',
type: 'text' as const,
formatter: (row: Record<string, unknown>) => (row.status === 'active' ? '已生效(锁定)' : '草稿'),
},
])
</script>
<template>
<BizEditableTable
v-model="rows"
mode="form"
:columns="columns"
:form-columns="3"
:form-title="(_row, index) => `班次 ${index + 1}`"
:min-rows="1"
empty-text="暂无班次"
/>
</template>
显示代码
KV 键值对编辑(kv 模式)
mode="kv":键 / 值两列 + 可选备注列。系统参数、回调请求头、商品扩展属性、设备元数据等配置场景;键必填唯一、值可按类型校验(见后续示例)。<script setup lang="ts">
/** 场景:回调请求头配置——KV 键值对编辑(含备注列) */
import { ref } from 'vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableKvRow } from 'vue3-ep-biz-lib'
const rows = ref<BizEditableKvRow[]>([
{ key: 'Content-Type', value: 'application/json', remark: '默认 JSON' },
{ key: 'X-App-Id', value: 'portal-2026', remark: '分配的应用标识' },
{ key: '', value: '', remark: '' },
])
</script>
<template>
<BizEditableTable v-model="rows" mode="kv" key-label="Header" value-label="取值" kv-remark remark-label="说明" :min-rows="0" />
</template>
显示代码
键重复 / 键为空 / 值非数字逐行标红(键唯一校验跨行联动,改任一键即全表重算);
validate() 后 toRecord() 导出为对象。<script setup lang="ts">
/** 场景:系统参数配置——键必填唯一 + 数字值类型校验,validate() 后看错误与 Record 结果 */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableKvRow, BizEditableTableInstance } from 'vue3-ep-biz-lib'
const tableRef = ref<BizEditableTableInstance>()
const rows = ref<BizEditableKvRow[]>([
{ key: 'retry.count', value: '3' },
{ key: 'timeout.seconds', value: 'abc' },
{ key: 'retry.count', value: '5' },
{ key: '', value: '30' },
])
const submit = (): void => {
void tableRef.value?.validate().then((result) => {
if (result.valid) {
ElMessage.success('校验通过,Record 见下方')
}
})
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<BizEditableTable
ref="tableRef"
v-model="rows"
mode="kv"
key-label="参数名"
value-label="参数值"
value-type="number"
:show-index="false"
:confirm-delete="false"
/>
<div style="display: flex; gap: 8px; align-items: center">
<ElButton type="primary" size="small" @click="submit">校验并取值</ElButton>
<pre style="margin: 0; flex: 1; padding: 8px 12px; font-size: 12px; border-radius: 6px; background: var(--el-fill-color-light)">{{ JSON.stringify(tableRef?.toRecord(), null, 2) }}</pre>
</div>
</div>
</template>
显示代码
value-type="json":值列做 JSON.parse 语法校验——采集配置、阈值规则等结构化元数据录入护栏。<script setup lang="ts">
/** 场景:设备元数据——JSON 值类型校验(标签阈值 / 采集配置) */
import { ref } from 'vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableKvRow } from 'vue3-ep-biz-lib'
const rows = ref<BizEditableKvRow[]>([
{ key: 'collect.interval', value: '{"unit":"second","value":15}', remark: '采集周期' },
{ key: 'alarm.thresholds', value: '[{"metric":"temp","op":">","value":80}]', remark: '告警阈值' },
{ key: 'broken.json', value: '{temp: 80}', remark: '这行会报 JSON 语法错误' },
])
</script>
<template>
<BizEditableTable v-model="rows" mode="kv" key-label="元数据键" value-label="JSON 值" value-type="json" kv-remark remark-label="用途" />
</template>
显示代码
行对象带
locked: true 即为系统内置项:键不可修改、行不可删除(删除按钮换为锁形提示),业务项自由维护。<script setup lang="ts">
/** 场景:环境变量配置——系统内置项锁定(键不可改、行不可删),业务项自由维护 */
import { ref } from 'vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableKvRow } from 'vue3-ep-biz-lib'
const rows = ref<BizEditableKvRow[]>([
{ key: 'NODE_ENV', value: 'production', remark: '系统内置', locked: true },
{ key: 'APP_VERSION', value: '1.2.0', remark: '系统内置', locked: true },
{ key: 'LOG_LEVEL', value: 'info' },
{ key: 'FEATURE_DARK_MODE', value: 'on', remark: '灰度开关' },
])
</script>
<template>
<BizEditableTable v-model="rows" mode="kv" key-label="变量名" value-label="值" kv-remark remark-label="说明" />
</template>
显示代码
点击上方按钮载入,或直接新增录入
{}loadRecord(record) 载入对象、toRecord() 导出对象——KV 行数组与 Record<string, string> 双向互转,直接对接后端配置接口。<script setup lang="ts">
/** 场景:商品扩展属性——Record ⇄ KV 行互转(loadRecord 载入 / toRecord 取出) */
import { ref } from 'vue'
import { ElButton } from 'element-plus'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableKvRow, BizEditableTableInstance } from 'vue3-ep-biz-lib'
const tableRef = ref<BizEditableTableInstance>()
const rows = ref<BizEditableKvRow[]>([])
const output = ref<Record<string, string>>({})
const source: Record<string, string> = {
产地: '福建安溪',
净含量: '250g',
保质期: '540天',
储存方式: '阴凉干燥处密封保存',
}
const load = (): void => {
tableRef.value?.loadRecord(source)
output.value = {}
}
const dump = (): void => {
output.value = tableRef.value?.toRecord() ?? {}
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<div style="display: flex; gap: 8px">
<ElButton size="small" @click="load">loadRecord 载入商品属性</ElButton>
<ElButton size="small" type="primary" @click="dump">toRecord 导出</ElButton>
</div>
<BizEditableTable
ref="tableRef"
v-model="rows"
mode="kv"
key-label="属性名"
value-label="属性值"
empty-text="点击上方按钮载入,或直接新增录入"
/>
<pre style="margin: 0; padding: 8px 12px; font-size: 12px; border-radius: 6px; background: var(--el-fill-color-light)">{{ JSON.stringify(output, null, 2) }}</pre>
</div>
</template>
显示代码
sensitive 值列渲染为密码框(点击眼睛切换显隐)——密钥、Token 类配置默认掩码。<script setup lang="ts">
/** 场景:第三方渠道密钥——敏感值掩码(密码框显隐切换),提交前才可见 */
import { ref } from 'vue'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableKvRow } from 'vue3-ep-biz-lib'
const rows = ref<BizEditableKvRow[]>([
{ key: 'oss.accessKeyId', value: 'LTAI5tFakeFakeFake', remark: '仅存储用' },
{ key: 'oss.accessKeySecret', value: 'sk-fake-secret-2026', remark: '切勿泄露' },
{ key: 'sms.apiKey', value: '', remark: '待配置' },
])
</script>
<template>
<BizEditableTable v-model="rows" mode="kv" key-label="密钥项" value-label="密钥值" kv-remark remark-label="备注" sensitive />
</template>
显示代码
粘贴填充(Excel / TSV)
也可复制 Excel 区域后点击表格内任意输入框 Ctrl+V
pasteable 开启后聚焦表格内任意输入框 Ctrl+V:Excel 复制的区域(TSV)按列序批量追加行;select 列按 value / label 自动匹配、number 列合法数字自动转型。WMS 批量入库、批量改价等高频录入场景。<script setup lang="ts">
/** 场景:批量入库——聚焦表格 Ctrl+V 粘贴 Excel 区域(TSV)批量追加行 */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableRow, BizEditableTableInstance } from 'vue3-ep-biz-lib'
const tableRef = ref<BizEditableTableInstance>()
const rows = ref<BizEditableRow[]>([
{ sku: 'SKU-1001', name: '无线鼠标', bin: 'A-01-02', qty: 36 },
])
const columns = [
{ prop: 'sku', label: 'SKU', required: true },
{ prop: 'name', label: '品名' },
{ prop: 'bin', label: '库位' },
{ prop: 'qty', label: '数量', type: 'number' as const },
]
const sample = ['SKU-2001\t机械键盘\tB-02-11\t50', 'SKU-2002\t显示器支架\tB-02-12\t18', 'SKU-2003\tUSB-C 扩展坞\tB-03-05\t25'].join('\n')
const fillSample = (): void => {
tableRef.value?.pasteText(sample)
ElMessage.info('已模拟粘贴 3 行(等同聚焦表格后 Ctrl+V)')
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<div style="display: flex; align-items: center; gap: 12px">
<ElButton size="small" @click="fillSample">模拟粘贴 3 行</ElButton>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">也可复制 Excel 区域后点击表格内任意输入框 Ctrl+V</span>
</div>
<BizEditableTable
ref="tableRef"
v-model="rows"
:columns="columns"
pasteable
:max-rows="200"
empty-text="暂无数据,可新增一行或直接粘贴"
/>
</div>
</template>
显示代码
粘贴前弹出预览确认;上限 5 行之外的行会被跳过
paste-preview 粘贴前弹预览确认(前 5 行 + 行数 + 跳过统计);max-rows 上限之外的行自动跳过,paste-fill 事件回传统计。<script setup lang="ts">
/** 场景:批量改价——粘贴预览确认(前 5 行 + 行数 + 跳过统计)+ 行数上限保护 */
import { ref } from 'vue'
import { ElButton } from 'element-plus'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableRow, BizEditableTableInstance } from 'vue3-ep-biz-lib'
const tableRef = ref<BizEditableTableInstance>()
const rows = ref<BizEditableRow[]>([{ sku: 'P-001', price: 199, memberPrice: 179 }])
const columns = [
{ prop: 'sku', label: '货号', required: true },
{ prop: 'price', label: '售价', type: 'number' as const, required: true },
{ prop: 'memberPrice', label: '会员价', type: 'number' as const },
]
const sample = Array.from({ length: 8 }, (_, i) => `P-10${i + 1}\t${199 + i * 10}\t${179 + i * 10}`).join('\n')
const lastEvent = ref('(未粘贴)')
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<div style="display: flex; align-items: center; gap: 12px">
<ElButton size="small" @click="tableRef?.pasteText(sample)">模拟粘贴 8 行</ElButton>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">粘贴前弹出预览确认;上限 5 行之外的行会被跳过</span>
</div>
<BizEditableTable
ref="tableRef"
v-model="rows"
:columns="columns"
pasteable
paste-preview
:max-rows="5"
:confirm-delete="false"
@paste-fill="(_rows, appended, skipped) => (lastEvent = `追加 ${appended} 行,跳过 ${skipped} 行`)"
/>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">paste-fill 事件:{{ lastEvent }}</span>
</div>
</template>
显示代码
键为空或与已有键重复的行自动跳过;已有行内容不被覆盖
kv 模式同样支持粘贴:键 / 值 / 备注三列映射,空键与重复键自动跳过(已有行不被覆盖)——.env、Excel 配置表整体搬运。
<script setup lang="ts">
/** 场景:KV 粘贴导入——从 Excel / .env 复制的「键<TAB>值」两列直接粘进 KV 表(空键重复键跳过) */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizEditableTable } from 'vue3-ep-biz-lib'
import type { BizEditableKvRow, BizEditableTableInstance } from 'vue3-ep-biz-lib'
const tableRef = ref<BizEditableTableInstance>()
const rows = ref<BizEditableKvRow[]>([{ key: 'app.name', value: '订单中心', remark: '已存在(验证去重)' }])
const sample = [
'app.name\t订单中心V2',
'app.port\t8080',
'\t空键会被跳过',
'app.port\t9090',
'log.level\twarn',
].join('\n')
const fill = (): void => {
tableRef.value?.pasteText(sample)
ElMessage.info('5 行内容:空键 1 行、重复键 2 行被跳过,实际导入 2 行')
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<div style="display: flex; align-items: center; gap: 12px">
<ElButton size="small" @click="fill">模拟粘贴 5 行</ElButton>
<span style="font-size: 13px; color: var(--el-text-color-secondary)">键为空或与已有键重复的行自动跳过;已有行内容不被覆盖</span>
</div>
<BizEditableTable
ref="tableRef"
v-model="rows"
mode="kv"
key-label="配置项"
value-label="值"
kv-remark
pasteable
:show-index="false"
/>
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| model-value / v-model | BizEditableRow[] | [] | 行数据数组 |
| columns | BizEditableColumn[] | [] | 列配置(见下) |
| mode | 'table' | 'form' | 'kv' | 'table' | 表格行内编辑 / 卡片子表单 / 键值对编辑 |
| form-columns | number | 2 | form 模式每行字段数 |
| form-title | (row, index) => string | 第 N 条 | form 模式卡片标题工厂 |
| edit-trigger | 'always' | 'click' | 'always' | 常驻控件 / 点击进入编辑(仅 table 模式) |
| show-index | boolean | true | 序号列 |
| addable | boolean | true | 顶部「新增一行」按钮 |
| deletable | boolean | true | 行删除按钮 |
| copyable | boolean | false | 行复制按钮 |
| confirm-delete | boolean | true | 删除前二次确认 |
| min-rows / max-rows | number | 0 / Infinity | 行数下限(删除按钮隐藏)/ 上限(新增禁用) |
| summary-columns | string[] | [] | 数值合计列(table 模式 el-table 合计行 / form 模式底部合计条) |
| summary-label | string | '合计' | 合计行首列文案 |
| empty-text | string | 内置文案 | 空态文案 |
| row-template | () => BizEditableRow | — | 新增行默认值工厂 |
| key-label / value-label / remark-label | string | 键 / 值 / 备注 | kv 模式列文案 |
| kv-remark | boolean | false | kv 模式显示备注列 |
| value-type | 'string' | 'number' | 'boolean' | 'json' | 'string' | kv 模式值类型校验 |
| sensitive | boolean | false | kv 模式值列密码框掩码 |
| pasteable | boolean | false | 粘贴填充(table / kv 模式) |
| paste-preview | boolean | false | 粘贴前预览确认(前 5 行 + 行数) |
ts
interface BizEditableColumn {
prop: string
label: string
type?: 'input' | 'number' | 'select' | 'date' | 'time' | 'switch' | 'text' | 'slot'
/** form 模式:占满整行(备注、长文本) */
full?: boolean
width?: string | number
minWidth?: string | number
align?: 'left' | 'center' | 'right'
placeholder?: string
options?: { label: string; value: string | number | boolean }[] // select
min?: number; max?: number; precision?: number; controls?: boolean // number
activeText?: string // switch
formatter?: (row, index) => string // text 列展示
required?: boolean
validator?: (value, row, index) => string | true
disabled?: (row, index) => boolean // 单元格级锁定
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| update:model-value | (rows: BizEditableRow[]) | 任意单元格 / 行变更回发 |
| change | (rows: BizEditableRow[]) | 同 update(便于不绑 v-model 时监听) |
| row-add | (row, index) | 新增行 |
| row-delete | (row, index) | 删除行 |
| paste-fill | (rows, appended, skipped) | 粘贴填充完成(追加的行 / 追加数 / 跳过数) |
Slots
| 插槽名 | 作用域 | 说明 |
|---|---|---|
| toolbar | — | 工具栏左侧说明区 |
| toolbar-extra | — | 工具栏右侧、新增按钮之前的扩展区 |
col-字段名 | { row, index, value } | 自定义单元格(type 为 slot 时必用,也可覆盖其他列) |
| empty | — | 空态内容 |
Expose
| 名称 | 类型 | 说明 |
|---|---|---|
| addRow | (partial?: BizEditableRow) => void | 外部新增行 |
| deleteRow | (index: number) => Promise<void> | 外部删除行(走确认逻辑) |
| copyRow | (index: number) => void | 复制行并插入其后 |
| validate | () => Promise<{ valid: boolean; errors: { index; prop; message }[] }> | 全量校验 |
| clearValidate | () => void | 清除错误标记 |
| getRows | () => BizEditableRow[] | 读取行数据副本 |
| clearRows | () => void | 清空全部行 |
| toRecord | () => Record<string, string> | kv 模式:行数组 → 对象(键非空行,重复键后者覆盖) |
| loadRecord | (record: Record<string, string>) => void | kv 模式:对象 → 行数组并提交 |
| pasteText | (text: string) => void | 程序化粘贴入口(等同 Ctrl+V 的 TSV 内容) |