ProTable 数据表格
基于 plus-pro-components / Table 的 dh-design-pc 二次封装,保留 PlusTable 的大部分高级能力,并额外补充了 dh-design-pc 风格的业务操作列策略。
如果业务场景是“筛选 + 表格 + 分页”的完整列表页,优先使用 ProPage;如果只需要单独的一张业务数据表,再直接使用 ProTable。
import { createApp } from 'vue'
import { ProTable } from '@szhn/dh-design-pc'
const app = createApp({})
app.use(ProTable)基础用法
<template>
<dh-pro-table v-bind="baseTableProps" :columns="baseTableColumns" :data="tableData" />
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(4)
</script>标题栏
<template>
<div class="pro-table-stack">
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
:title-bar="{ title: '表格标题' }"
:has-tool-bar="true"
:has-pagination="false"
>
<template #toolbar>
<el-button plain size="small">查看日志</el-button>
<el-button plain size="small">导出数据</el-button>
<el-button type="primary" size="small">创建应用</el-button>
</template>
</dh-pro-table>
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
:title-bar="{ title: '表格标题' }"
:has-tool-bar="true"
:has-pagination="false"
>
<template #title>
<el-button type="primary" size="small">新增</el-button>
<el-button type="danger" size="small">批量删除</el-button>
</template>
<template #toolbar>
<el-button plain size="small">查看日志</el-button>
<el-button plain size="small">导出数据</el-button>
<el-button type="primary" size="small">创建应用</el-button>
</template>
</dh-pro-table>
</div>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(3)
</script>
<style scoped>
.pro-table-stack {
display: flex;
flex-direction: column;
gap: 24px;
}
</style>分页栏
<template>
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
:pagination="{ align: 'right' }"
:total="60"
v-model:page="page"
v-model:page-size="pageSize"
/>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const page = ref(1)
const pageSize = ref(5)
const tableData = ref(createTableData(pageSize.value))
watch([page, pageSize], () => {
tableData.value = createTableData(pageSize.value)
})
</script>自定义状态
<template>
<dh-pro-table v-bind="baseTableProps" :columns="baseTableColumns" :data="tableData" :has-pagination="false" />
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(5)
</script>自定义表格项(formatter / render)
<template>
<dh-pro-table v-bind="baseTableProps" :columns="columns" :data="tableData" :has-pagination="false" />
</template>
<script lang="ts" setup>
import type { PcProColumn } from '@szhn/dh-design-pc'
import { baseTableProps, createTableData } from './shared'
const tableData = createTableData(4)
const columns: PcProColumn[] = [
{
label: '名称',
prop: 'name',
render: (value: string | number) => `资产:${value}`,
},
{
label: '状态',
prop: 'status',
render: (value: string | number) => (value === '1' ? '已解决' : '处理中'),
},
{
label: '时间',
prop: 'time',
valueType: 'date-picker',
},
]
</script>自定义表格项(插槽)
<template>
<dh-pro-table v-bind="baseTableProps" :columns="baseTableColumns" :data="tableData" :has-pagination="false">
<template #plus-cell-name="{ row }">
<div class="pro-cell-name">
<span class="pro-cell-name__dot" />
<span>{{ row.name }}</span>
</div>
</template>
</dh-pro-table>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(4)
</script>
<style scoped>
.pro-cell-name {
align-items: center;
display: flex;
gap: 8px;
}
.pro-cell-name__dot {
background: var(--el-color-primary);
border-radius: 999px;
height: 8px;
width: 8px;
}
</style>自定义表头(插槽)
<template>
<dh-pro-table v-bind="baseTableProps" :columns="baseTableColumns" :data="tableData" :has-pagination="false">
<template #plus-header-name>
<span class="pro-header-name">资产名称 / 编号</span>
</template>
</dh-pro-table>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(4)
</script>
<style scoped>
.pro-header-name {
color: var(--el-color-primary);
font-weight: 700;
}
</style>空值展示
<template>
<dh-pro-table v-bind="baseTableProps" :columns="baseTableColumns" :data="tableData" :has-pagination="false" />
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps } from './shared'
const tableData = [
{ id: 1, name: '', status: '0', tag: '', time: null },
{ id: 2, name: '2name', status: '1', tag: 'success', time: new Date() },
]
</script>序号栏
<template>
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
has-index-column
:has-pagination="false"
/>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(5)
</script>多选
<template>
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
is-selection
:has-pagination="false"
@selection-change="handleSelectionChange"
/>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(5)
const handleSelectionChange = (rows: unknown[]) => console.log('selection', rows)
</script>单选
<template>
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
is-radio
:has-pagination="false"
/>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(4)
</script>展开行
<template>
<dh-pro-table v-bind="baseTableProps" :columns="baseTableColumns" :data="tableData" has-expand :has-pagination="false">
<template #expand="{ row }">
<div class="pro-expand">
<strong>{{ row.name }}</strong>
<span>状态:{{ row.status }}</span>
</div>
</template>
</dh-pro-table>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(4)
</script>
<style scoped>
.pro-expand {
display: flex;
gap: 12px;
}
</style>树形结构
<template>
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
row-key="id"
:tree-props="{ children: 'children' }"
:has-pagination="false"
/>
</template>
<script lang="ts" setup>
import type { TableRow } from './shared'
import { baseTableColumns, baseTableProps } from './shared'
const tableData: TableRow[] = [
{
id: 1,
name: '一级节点 1',
status: '0',
tag: 'success',
time: new Date(),
children: [
{
id: 11,
name: '二级节点 1-1',
status: '1',
tag: 'warning',
time: new Date(),
},
],
},
{
id: 2,
name: '一级节点 2',
status: '2',
tag: 'danger',
time: new Date(),
},
]
</script>多级表头
<template>
<dh-pro-table v-bind="baseTableProps" :columns="columns" :data="tableData" :has-pagination="false" />
</template>
<script lang="ts" setup>
import type { PcProColumn } from '@szhn/dh-design-pc'
import { baseTableProps, createTableData } from './shared'
const tableData = createTableData(4)
const columns: PcProColumn[] = [
{
label: '基本信息',
prop: 'base',
children: [
{ label: '名称', prop: 'name' },
{ label: '状态', prop: 'status' },
],
},
{
label: '扩展信息',
prop: 'extra',
children: [
{ label: '标签', prop: 'tag' },
{ label: '时间', prop: 'time', valueType: 'date-picker' },
],
},
]
</script>操作栏
<template>
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
:has-pagination="false"
:action-bar="actionBar"
/>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(4)
const actionBar = {
type: 'link',
buttons: [
{ text: '查看', onClick: ({ row }: any) => console.log('view', row) },
{ text: '编辑', onClick: ({ row }: any) => console.log('edit', row) },
{ text: '删除', type: 'danger', onClick: ({ row }: any) => console.log('delete', row) },
],
}
</script>操作栏(二次确认)
<template>
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
:has-pagination="false"
:action-bar="actionBar"
/>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(4)
const actionBar = {
type: 'link',
confirmType: 'popconfirm',
buttons: [
{ text: '查看', onClick: ({ row }: any) => console.log('view', row) },
{
text: '删除',
type: 'danger',
confirm: true,
onConfirm: ({ row }: any) => console.log('confirm delete', row),
},
],
}
</script>操作列(平铺)
<template>
<dh-pro-table
v-bind="baseTableProps"
:data="data"
:columns="columns"
:action-items="actions"
:action-max="3"
:action-width="200"
:has-pagination="false"
/>
</template>
<script lang="ts" setup>
import type { PcProColumn, PcProActionItem } from '@szhn/dh-design-pc'
import { baseTableProps } from './shared'
const columns: PcProColumn[] = [
{ label: '资产编号', prop: 'code', width: 140 },
{ label: '资产名称', prop: 'name' },
{ label: '负责人', prop: 'owner' },
]
const data = [
{ code: 'ZC-1001', name: '资产样例 1', owner: '张三' },
{ code: 'ZC-1002', name: '资产样例 2', owner: '李四' },
{ code: 'ZC-1003', name: '资产样例 3', owner: '王五' },
]
const actions: PcProActionItem[] = [
{ text: '查看', onClick: ({ row }) => console.log('view', row) },
{ text: '编辑', onClick: ({ row }) => console.log('edit', row) },
{
text: '删除',
type: 'danger',
confirm: { title: '确定删除该资产?' },
onClick: ({ row }) => console.log('delete', row),
},
]
</script>操作列(更多下拉 + 悬停展开)
<template>
<p class="pro-demo-tip">默认 3 条平铺,超出自动折入"更多"下拉;开启 hover 展开后,悬停行时可临时展示全部操作。</p>
<dh-pro-table
v-bind="baseTableProps"
:data="data"
:columns="columns"
:action-items="actions"
:action-max="3"
:action-width="260"
action-hover-expand
:has-pagination="false"
/>
</template>
<script lang="ts" setup>
import type { PcProColumn, PcProActionItem } from '@szhn/dh-design-pc'
import { baseTableProps } from './shared'
const columns: PcProColumn[] = [
{ label: '资产编号', prop: 'code', width: 140 },
{ label: '资产名称', prop: 'name' },
{ label: '负责人', prop: 'owner' },
]
const data = [
{ code: 'ZC-1001', name: '资产样例 1', owner: '张三' },
{ code: 'ZC-1002', name: '资产样例 2', owner: '李四' },
]
const actions: PcProActionItem[] = [
{ text: '查看', onClick: ({ row }) => console.log('view', row) },
{ text: '编辑', onClick: ({ row }) => console.log('edit', row) },
{ text: '复制', onClick: ({ row }) => console.log('copy', row) },
{ text: '导出', onClick: ({ row }) => console.log('export', row) },
{ text: '归档', onClick: ({ row }) => console.log('archive', row) },
{
text: '删除',
type: 'danger',
confirm: { title: '确定删除该资产?' },
onClick: ({ row }) => console.log('delete', row),
},
]
</script>
<style scoped>
.pro-demo-tip {
color: var(--el-text-color-secondary);
font-size: 13px;
margin: 0 0 12px;
}
</style>远程分页
<template>
<dh-pro-table
v-bind="baseTableProps"
:data="data"
:columns="columns"
:loading="loading"
:total="total"
v-model:page="page"
v-model:page-size="pageSize"
@pagination-change="onPaginationChange"
/>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import type { PcProColumn } from '@szhn/dh-design-pc'
import { baseTableProps } from './shared'
const columns: PcProColumn[] = [
{ label: '资产编号', prop: 'code', width: 140 },
{ label: '资产名称', prop: 'name' },
{ label: '所属地', prop: 'region' },
{ label: '更新时间', prop: 'updatedAt', width: 180 },
]
const page = ref(1)
const pageSize = ref(10)
const total = ref(86)
const loading = ref(false)
const data = ref<any[]>([])
const fetchList = () => {
loading.value = true
setTimeout(() => {
data.value = Array.from({ length: pageSize.value }).map((_, i) => {
const idx = (page.value - 1) * pageSize.value + i
return {
code: `ZC-${1000 + idx}`,
name: `资产样例 ${idx + 1}`,
region: idx % 2 ? '海口市' : '三亚市',
updatedAt: '2024-08-12 14:30',
}
})
loading.value = false
}, 300)
}
watch([page, pageSize], fetchList, { immediate: true })
const onPaginationChange = (info: { page: number; pageSize: number }) => {
console.log('pagination-change', info)
}
</script>内容区高度
<template>
<div class="pro-adaptive-wrap">
<dh-pro-table
v-bind="baseTableProps"
:columns="baseTableColumns"
:data="tableData"
:height="360"
:pagination="{ pageSize: 20 }"
:total="120"
/>
</div>
</template>
<script lang="ts" setup>
import { baseTableColumns, baseTableProps, createTableData } from './shared'
const tableData = createTableData(20)
</script>
<style scoped>
.pro-adaptive-wrap {
height: 480px;
overflow: hidden;
}
</style>与 ProSearch 联动
<template>
<div class="with-search-demo">
<dh-pro-search
v-model="searchModel"
:columns="searchColumns"
:col="4"
layout="horizontal"
@search="handleSearch"
@reset="handleReset"
@advanced-apply="handleSearch"
>
<template #toolbar>
<el-button type="primary">新建</el-button>
<el-button>导出</el-button>
</template>
</dh-pro-search>
<dh-pro-table
v-bind="baseTableProps"
v-model:page="page"
v-model:page-size="pageSize"
:columns="tableColumns"
:data="pagedRows"
:total="filteredRows.length"
:action-items="actionItems"
/>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import type { PcProActionItem, PcProColumn } from '@szhn/dh-design-pc'
import { Button as ElButton } from '@szhn/dh-design-pc'
import { baseTableProps } from './shared'
interface Row {
id: number
name: string
type: string
region: string
status: string
owner: string
updatedAt: string
applicant: string
department: string
project: string
approvalNo: string
payType: string
}
const typeOptions = [
{ label: '商铺', value: 'shop' },
{ label: '办公用房', value: 'office' },
{ label: '停车位', value: 'parking' },
]
const regionOptions = [
{ label: '海口市', value: 'haikou' },
{ label: '三亚市', value: 'sanya' },
{ label: '儋州市', value: 'danzhou' },
]
const statusOptions = [
{ label: '运营中', value: 'running', color: 'green' },
{ label: '待出租', value: 'pending', color: 'yellow' },
{ label: '维护中', value: 'maintaining', color: 'blue' },
{ label: '已停用', value: 'closed', color: 'red' },
]
const payTypeOptions = [
{ label: '租金', value: '租金' },
{ label: '押金', value: '押金' },
{ label: '服务费', value: '服务费' },
{ label: '维修款', value: '维修款' },
]
const searchColumns: PcProColumn[] = [
{ label: '资产名称/编号', prop: 'name', valueType: 'input', fieldProps: { placeholder: '请输入' } },
{ label: '资产类型', prop: 'type', valueType: 'select', options: typeOptions, fieldProps: { placeholder: '请选择' } },
{ label: '所属地', prop: 'region', valueType: 'select', options: regionOptions, fieldProps: { placeholder: '请选择' } },
{ label: '资产归属', prop: 'owner', valueType: 'input', fieldProps: { placeholder: '请输入' } },
{ label: '运营状态', prop: 'status', valueType: 'select', options: statusOptions, fieldProps: { placeholder: '请选择' } },
{ label: '更新日期', prop: 'updatedAt', valueType: 'date-picker', fieldProps: { placeholder: '请选择日期' } },
{ label: '申请人', prop: 'applicant', valueType: 'input', fieldProps: { placeholder: '请输入' } },
{ label: '申请部门', prop: 'department', valueType: 'input', fieldProps: { placeholder: '请输入' } },
// 以下为"更多筛选"中的扩展条件
{ label: '关联项目名称', prop: 'project', valueType: 'input', fieldProps: { placeholder: '请输入' } },
{ label: '付款类型', prop: 'payType', valueType: 'select', options: payTypeOptions, fieldProps: { placeholder: '请选择' } },
{ label: '关联审批单', prop: 'approvalNo', valueType: 'input', fieldProps: { placeholder: '请输入审批单编号' } },
]
const tableColumns: PcProColumn[] = [
{ label: '资产名称/编号', prop: 'name', minWidth: 180, tableColumnProps: { showOverflowTooltip: true } },
{ label: '资产类型', prop: 'type', valueType: 'select', options: typeOptions, width: 120 },
{ label: '所属地', prop: 'region', valueType: 'select', options: regionOptions, width: 120 },
{ label: '资产归属', prop: 'owner', minWidth: 150, tableColumnProps: { showOverflowTooltip: true } },
{ label: '运营状态', prop: 'status', valueType: 'select', options: statusOptions, width: 120 },
{ label: '更新日期', prop: 'updatedAt', width: 130 },
{ label: '申请人', prop: 'applicant', width: 100 },
{ label: '申请部门', prop: 'department', width: 130, tableColumnProps: { showOverflowTooltip: true } },
]
const actionItems: PcProActionItem[] = [
{ text: '查看', onClick: ({ row }) => console.log('view', row) },
{ text: '编辑', onClick: ({ row }) => console.log('edit', row) },
{ text: '删除', type: 'danger', confirm: { title: '确认删除?' }, onClick: ({ row }) => console.log('delete', row) },
]
const tableData: Row[] = [
{ id: 1, name: 'HAK-SY-001 临街商铺', type: 'shop', region: 'haikou', owner: '经营性资产管理', status: 'running', updatedAt: '2026-05-01', applicant: '林明', department: '资产运营部', project: '国兴大道商业街更新', approvalNo: 'SP-001', payType: '租金' },
{ id: 2, name: 'SYA-BG-018 办公用房', type: 'office', region: 'sanya', owner: '综合运营管理', status: 'pending', updatedAt: '2026-04-28', applicant: '周琴', department: '综合管理部', project: '三亚办公资产盘点', approvalNo: 'SP-006', payType: '押金' },
{ id: 3, name: 'DZH-PK-088 地下车位', type: 'parking', region: 'danzhou', owner: '停车资源管理', status: 'running', updatedAt: '2026-04-22', applicant: '王卓', department: '园区服务中心', project: '停车资源统一运营', approvalNo: 'SP-014', payType: '月租' },
{ id: 4, name: 'HAK-SY-021 社区底商', type: 'shop', region: 'haikou', owner: '经营性资产管理', status: 'closed', updatedAt: '2026-04-02', applicant: '吴越', department: '资产运营部', project: '社区商业焕新', approvalNo: 'SP-009', payType: '租金' },
{ id: 5, name: 'SYA-BG-022 联合办公', type: 'office', region: 'sanya', owner: '综合运营管理', status: 'running', updatedAt: '2026-03-29', applicant: '刘欣', department: '招商运营部', project: '联合办公扩容', approvalNo: 'SP-003', payType: '服务费' },
{ id: 6, name: 'HAK-PK-113 地面车位', type: 'parking', region: 'haikou', owner: '停车资源管理', status: 'pending', updatedAt: '2026-03-18', applicant: '高磊', department: '园区服务中心', project: '停车收费规范化', approvalNo: 'SP-018', payType: '月租' },
{ id: 7, name: 'HAK-CC-007 办公园区', type: 'office', region: 'haikou', owner: '综合运营管理', status: 'maintaining', updatedAt: '2026-03-10', applicant: '赵宁', department: '文旅运营部', project: '园区设施升级', approvalNo: 'SP-015', payType: '维修款' },
]
const searchModel = ref<Record<string, any>>({})
const appliedModel = ref<Record<string, any>>({})
const page = ref(1)
const pageSize = ref(5)
const filteredRows = computed(() => {
const q = appliedModel.value
return tableData.filter(row => {
if (q.name && !row.name.includes(String(q.name))) return false
if (q.type && row.type !== q.type) return false
if (q.region && row.region !== q.region) return false
if (q.owner && !row.owner.includes(String(q.owner))) return false
if (q.status && row.status !== q.status) return false
if (q.applicant && !row.applicant.includes(String(q.applicant))) return false
if (q.department && !row.department.includes(String(q.department))) return false
if (q.project && !row.project.includes(String(q.project))) return false
if (q.payType && row.payType !== q.payType) return false
if (q.approvalNo && !row.approvalNo.includes(String(q.approvalNo))) return false
return true
})
})
const pagedRows = computed(() => {
const start = (page.value - 1) * pageSize.value
return filteredRows.value.slice(start, start + pageSize.value)
})
function handleSearch(values: Record<string, any>) {
appliedModel.value = { ...values }
page.value = 1
}
function handleReset(values: Record<string, any>) {
searchModel.value = { ...values }
appliedModel.value = { ...values }
page.value = 1
}
</script>
<style scoped>
.with-search-demo {
display: flex;
flex-direction: column;
gap: 16px;
}
</style>表格设计规范
ProTable 默认对齐 PC 端表格规范:默认行高 40px,紧凑模式 32px,宽松模式 48px;表格只保留横向行分割线和必要的冻结列分隔线,不显示列间竖线;普通空值显示为 -;普通文本列默认开启超出提示;分页区域保持在表格下方并右对齐。
表格形态
表格分为基础形态、行选择形态和可展开形态。基础形态用于纯信息浏览;行选择形态用于批量管理;可展开形态用于父子层级或补充详情,层级建议不超过 2 层。
样式规则
单色表格适用于列数少、信息密度高、需要严格纵向对齐的阅读场景;斑马纹表格适用于列数多、行内容长度不一、需要快速横向定位的宽表格。三档密度分别对应 32px、40px、48px 行高。
表头显示 / 冻结
表头纵向滚动时固定在表格顶部,字段与数据的对应关系不随滚动丢失;核心字段列可固定在左侧,操作列固定在右侧。单元格内容超过列宽时最多展示两行并截断,悬停时展示完整内容。
操作列
操作列固定在表格右侧,直接外露最多 3 项操作;超过 3 项后通过「更多」聚合。每项操作最多显示约 5 个字,超出后截断并在悬停时展示完整文字。
分页器
分页器固定在表格可视区底部,包含每页条数、页码跳转和上一页 / 下一页。表格纵向滚动时分页器不离开视线。
适配规则
表格容器宽度小于 768px 时,由表格外层区域提供横向滚动;大于等于 768px 时完整展开。包含侧边筛选导航的表格,在容器小于 1000px 时优先收起辅助列或侧边区域。
<template>
<div class="pro-table-design-demo">
<section class="design-rule-panel">
<div class="design-rule-header">
<div>
<strong>密度与样式</strong>
<span>三档行高、单色 / 斑马纹、选择和操作列组合。</span>
</div>
<div class="design-rule-controls">
<el-radio-group v-model="density" size="small">
<el-radio-button label="compact">紧凑 32px</el-radio-button>
<el-radio-button label="default">常规 40px</el-radio-button>
<el-radio-button label="loose">宽松 48px</el-radio-button>
</el-radio-group>
<el-radio-group v-model="tableTone" size="small">
<el-radio-button label="plain">单色</el-radio-button>
<el-radio-button label="stripe">斑马纹</el-radio-button>
</el-radio-group>
</div>
</div>
<dh-pro-table
:columns="columns"
:data="rows"
:density="density"
:stripe="tableTone === 'stripe'"
:action-items="actions"
:action-max="3"
:action-width="180"
cell-text-mode="double"
empty-cell-text="-"
fixed-header
is-selection
:min-table-width="1180"
:pagination="{ pageSizeList: [10, 20, 50, 100] }"
:total="36"
v-model:page="page"
v-model:page-size="pageSize"
/>
</section>
<section class="design-rule-grid">
<div class="design-rule-panel">
<div class="design-rule-header">
<div>
<strong>基础表格</strong>
<span>纯信息浏览,保持横向行分割线。</span>
</div>
</div>
<dh-pro-table
:columns="compactColumns"
:data="rows.slice(0, 4)"
density="default"
:has-pagination="false"
/>
</div>
<div class="design-rule-panel">
<div class="design-rule-header">
<div>
<strong>行选择表格</strong>
<span>用于批量操作,选择列保持居中。</span>
</div>
</div>
<dh-pro-table
:columns="compactColumns"
:data="rows.slice(4, 8)"
density="default"
is-selection
stripe
:has-pagination="false"
/>
</div>
<div class="design-rule-panel design-rule-panel--wide">
<div class="design-rule-header">
<div>
<strong>可展开表格</strong>
<span>用于补充详情或父子数据,子层级不超过 2 层。</span>
</div>
</div>
<dh-pro-table
:columns="compactColumns"
:data="rows.slice(0, 3)"
density="default"
has-expand
:has-pagination="false"
>
<template #expand="{ row }">
<div class="design-rule-expand">
<span>{{ row.code }}</span>
<span>{{ row.fullName }}</span>
<span>{{ row.owner || '-' }}</span>
</div>
</template>
</dh-pro-table>
</div>
</section>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { PcProActionItem, PcProColumn, PcProTableDensity } from '@szhn/dh-design-pc'
const density = ref<PcProTableDensity>('default')
const tableTone = ref<'plain' | 'stripe'>('stripe')
const page = ref(1)
const pageSize = ref(10)
const columns: PcProColumn[] = [
{ label: '项目编号', prop: 'code', minWidth: 140, fixed: 'left', priority: 1 },
{
label: '项目名称',
prop: 'fullName',
minWidth: 240,
fixed: 'left',
priority: 1,
tableColumnProps: { showOverflowTooltip: true },
},
{ label: '所属区域', prop: 'region', width: 130, priority: 2 },
{ label: '负责人', prop: 'owner', width: 120, priority: 2 },
{ label: '当前状态', prop: 'status', width: 120, priority: 3, hideBelow: 768 },
{ label: '更新时间', prop: 'updatedAt', width: 160, priority: 4, hideBelow: 1000 },
]
const compactColumns = columns.slice(0, 4)
const rows = Array.from({ length: 12 }).map((_, index) => ({
id: index,
code: `XM-${String(index + 1).padStart(4, '0')}`,
fullName:
index % 4 === 0
? '项目名称项目名称项目名称项目名称项目名称项目名称'
: `项目名称 ${index + 1}`,
owner: index === 2 ? '' : ['张三', '李四', '王五'][index % 3],
region: index % 2 === 0 ? '海口市' : '三亚市',
status: ['待处理', '执行中', '已完成'][index % 3],
updatedAt: index === 3 ? null : '2026-05-26 09:00',
}))
const actions: PcProActionItem[] = [
{ text: '查看', onClick: ({ row }) => console.log('view', row) },
{ text: '编辑', onClick: ({ row }) => console.log('edit', row) },
{ text: '导出', onClick: ({ row }) => console.log('export', row) },
{ text: '添加指标添加指标', onClick: ({ row }) => console.log('add metric', row) },
{ text: '修改', onClick: ({ row }) => console.log('change', row) },
]
</script>
<style scoped>
.pro-table-design-demo {
display: grid;
gap: 16px;
}
.design-rule-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.design-rule-panel {
display: grid;
gap: 12px;
min-width: 0;
}
.design-rule-panel--wide {
grid-column: 1 / -1;
}
.design-rule-header {
display: flex;
gap: 12px;
align-items: flex-start;
justify-content: space-between;
}
.design-rule-header strong,
.design-rule-header span {
display: block;
}
.design-rule-header span {
margin-top: 4px;
color: var(--el-text-color-secondary);
font-size: 13px;
line-height: 20px;
}
.design-rule-controls {
display: inline-flex;
flex-wrap: wrap;
gap: 8px;
justify-content: flex-end;
}
.design-rule-expand {
display: flex;
flex-wrap: wrap;
gap: 12px;
padding: 10px 12px;
color: var(--el-text-color-secondary);
font-size: 13px;
}
@media (max-width: 900px) {
.design-rule-grid {
grid-template-columns: 1fr;
}
.design-rule-header {
flex-direction: column;
}
.design-rule-controls {
justify-content: flex-start;
}
}
</style>常用配置:
<dh-pro-table
:columns="columns"
:data="data"
density="compact"
empty-cell-text="-"
fixed-header
:min-table-width="960"
cell-text-mode="double"
/>列宽和窄屏展示可以直接写在列配置里。priority 越小越重要,hide-below 可让辅助列在小宽度下隐藏。
const columns = [
{ label: '项目名称', prop: 'name', minWidth: 220, fixed: 'left', priority: 1 },
{ label: '更新时间', prop: 'updatedAt', width: 160, priority: 3, hideBelow: 768 },
]ProTable API
Props
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| data | 表格数据 | Array | [] |
| columns | 列定义 | PcProColumn[] | [] |
| loading | 表格加载态 | boolean | false |
| total | 总条数 | number | 0 |
| page / v-model:page | 当前页码 | number | 1 |
| page-size / v-model:page-size | 每页条数 | number | 10 |
| density | 表格密度,可选 compact / default / loose | string | default |
| row-height | 自定义行高,未传时跟随 density | number | — |
| empty-cell-text | 普通空值占位 | string | - |
| fixed-header | 固定表头;未传 height 时默认给表格内容区 420px 高度 | boolean | false |
| min-table-width | 表格最小宽度,小容器下由外层区域横向滚动 | number / string | — |
| responsive-min-table-width | 窄内容区下让表格回到容器宽度,避免隐藏列后出现空白横向滚动 | boolean | false |
| cell-text-mode | 单元格文本截断方式,可选 single / double | string | single |
| action-items | 业务操作列配置,传入即自动追加一列 | PcProActionItem[] | [] |
| action-max | 操作列最大平铺数 | number | 3 |
| action-more-text | 更多操作文案 | string | 更多 |
| action-hover-expand | 鼠标悬停行时是否展开更多操作 | boolean | false |
| action-fixed | 操作列固定方向 | 'left' / 'right' / false | right |
| action-width | 操作列宽度 | number / string | 180 |
| action-label | 操作列表头文案 | string | 操作 |
| has-tool-bar | 是否展示工具栏 | boolean | true |
| has-pagination | 是否展示分页 | boolean | true |
| title-bar | 标题栏配置,透传给 PlusTable | boolean / object | true |
其余 PlusTable 官方属性(如 title-bar、pagination、adaptive、is-selection、is-radio、has-expand、tree-props 等)可直接透传。
Column 扩展
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| priority | 列重要度,1 为核心列,4 为辅助列 | 1 / 2 / 3 / 4 | — |
| hide-below | 小于指定宽度时隐藏当前列,内置支持 768 / 1000 / 1280 | number | — |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| update:page | 页码变化 | (n: number) |
| update:page-size | 每页条数变化 | (n: number) |
| size-change | 兼容分页事件 | (n: number) |
| current-change | 兼容分页事件 | (n: number) |
| pagination-change | 分页变化 | (info: PageInfo) |
其余 el-table / PlusTable 事件同样支持透传。
Slots
| 插槽名 | 说明 |
|---|---|
| title | 标题栏左侧区域 |
| toolbar | 标题栏右侧工具区域 |
| expand | 展开行内容 |
| append | 表格底部附加内容 |
| empty | 空状态 |
| plus-cell-* | 自定义单元格 |
| plus-header-* | 自定义表头 |
Expose
| 方法名 | 说明 | 类型 |
|---|---|---|
| getTableRef | 获取底层 PlusTable 实例 | () => PlusTable |
