Skip to content

ProTable 数据表格

基于 plus-pro-components / Table 的 dh-design-pc 二次封装,保留 PlusTable 的大部分高级能力,并额外补充了 dh-design-pc 风格的业务操作列策略。

如果业务场景是“筛选 + 表格 + 分页”的完整列表页,优先使用 ProPage;如果只需要单独的一张业务数据表,再直接使用 ProTable

ts
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>

常用配置:

vue
<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 可让辅助列在小宽度下隐藏。

ts
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表格加载态booleanfalse
total总条数number0
page / v-model:page当前页码number1
page-size / v-model:page-size每页条数number10
density表格密度,可选 compact / default / loosestringdefault
row-height自定义行高,未传时跟随 densitynumber
empty-cell-text普通空值占位string-
fixed-header固定表头;未传 height 时默认给表格内容区 420px 高度booleanfalse
min-table-width表格最小宽度,小容器下由外层区域横向滚动number / string
responsive-min-table-width窄内容区下让表格回到容器宽度,避免隐藏列后出现空白横向滚动booleanfalse
cell-text-mode单元格文本截断方式,可选 single / doublestringsingle
action-items业务操作列配置,传入即自动追加一列PcProActionItem[][]
action-max操作列最大平铺数number3
action-more-text更多操作文案string更多
action-hover-expand鼠标悬停行时是否展开更多操作booleanfalse
action-fixed操作列固定方向'left' / 'right' / falseright
action-width操作列宽度number / string180
action-label操作列表头文案string操作
has-tool-bar是否展示工具栏booleantrue
has-pagination是否展示分页booleantrue
title-bar标题栏配置,透传给 PlusTableboolean / objecttrue

其余 PlusTable 官方属性(如 title-barpaginationadaptiveis-selectionis-radiohas-expandtree-props 等)可直接透传。

Column 扩展

属性名说明类型默认值
priority列重要度,1 为核心列,4 为辅助列1 / 2 / 3 / 4
hide-below小于指定宽度时隐藏当前列,内置支持 768 / 1000 / 1280number

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