表格合并组件配置
最近的需求有一个这样的功能,单元格合并,听起来很简单是不是,那么继续往下看。 首先我先大致介绍一下项目技术栈:v3 + ts + element-plus + vxe-table 相关。
项目中涉及到的表格数据都是使用的vxe-table框架,于是发现vxe-table并不满足我现在的需求,于是就有了自定义合并单元格的需求。
首先看下原型:

我的想法
每一行都可以作为一个组件,而标题,副标题,展开收起的状态,表格的表格都是可以进行配置的。
再加上使用的是vxe-table框架,那么就可以直接在vxe-table中进行配置, 接下来改造一下vxe-table的配置。
// 执行价格
interface CUSTOM_COLUMN extends VxeTableDefines.ColumnOptions {
// 合并表头单元格
headerMergeCells?: number;
}
// 继承原有的vxe-table的配置
export interface CUSTOM_EXECUTE_PRICE extends VxeGridProps {
columns: CUSTOM_COLUMN[];
}
const EXECUTE_PRICE_COLUMN: CUSTOM_EXECUTE_PRICE = {
// 这俩列是固定不变的,
columns: [
{
title: "全国价(元)",
width: 200,
slots: { default: "nationalPrice" },
},
{
title: "专属价(元)",
width: 200,
slots: { default: "exclusivePrice" },
},
],
data: [],
};
// 基础价格配置
export const baseTableConfig = cloneDeep({
...EXECUTE_PRICE_COLUMN,
columns: [
{
title: "属性名称",
slots: { default: "name" },
headerMergeCells: 1,
},
...EXECUTE_PRICE_COLUMN.columns,
],
});
// 颜色价格配置
export const colorTableConfig = cloneDeep({
...EXECUTE_PRICE_COLUMN,
columns: [
{
title: "属性名称",
slots: { default: "name" },
headerMergeCells: 1,
},
...EXECUTE_PRICE_COLUMN.columns,
],
});
// 规格价格配置
export const specTableConfig = cloneDeep({
...EXECUTE_PRICE_COLUMN,
columns: [
{
title: "属性名称",
slots: { default: "name" },
headerMergeCells: 2,
},
...EXECUTE_PRICE_COLUMN.columns,
],
});
// 行业价格配置
export const industryTableConfig = cloneDeep({
...EXECUTE_PRICE_COLUMN,
columns: [
{
title: "属性名称",
slots: { default: "name" },
headerMergeCells: 2,
},
...EXECUTE_PRICE_COLUMN.columns,
],
});这样的话,我们的这个表格配置就基本上是这样的。
接下来,需要去封装一个组件,来实现这个功能。
<script setup lang="ts">
import { CUSTOM_EXECUTE_PRICE } from "../config/table";
defineOptions({
name: "ExecutePriceDialogItem",
});
const props = defineProps<{
title: string;
tipText?: string;
gridOption: CUSTOM_EXECUTE_PRICE;
}>();
const expand = ref(true);
const tableConfig = ref(props.gridOption);
const colspan = computed(() => {
return tableConfig.value.columns.reduce((prev, cur) => {
return prev + (cur.headerMergeCells || 1);
}, 0);
});
watch(
() => props.gridOption,
newVal => {
tableConfig.value = newVal;
},
{ deep: true, immediate: true }
);
</script>
<template>
<div class="price-item">
<div class="header">
<h3 class="title">{{ title }}</h3>
<div class="expand-icon" @click="expand = !expand">
{{ expand ? "- 收起" : "+ 展开" }}
</div>
<div class="tip-text">{{ tipText }}</div>
</div>
<div v-if="expand" class="price__table">
<!-- 因为vxe-table 的最新版本,不支持表头合并,所以这里使用原生table -->
<table>
<thead>
<tr>
<th
v-for="item in tableConfig.columns"
:key="item.field"
:colspan="item.headerMergeCells || 1"
:width="item.width"
>
{{ item.title }}
</th>
</tr>
</thead>
<tbody>
<template v-if="tableConfig.data!.length > 0">
<!-- 这里将数据作为插槽的形式 -->
<tr v-for="(item, index) in tableConfig.data" :key="index">
<slot :row="item" :row-index="index" />
</tr>
</template>
<template v-else>
<tr>
<td :colspan="colspan">暂无数据</td>
</tr>
</template>
</tbody>
</table>
<!-- 使用vxe-table: 表头不能自定义与合并,与原型设计不符 -->
<!-- <vxe-grid v-bind="tableConfig" ref="vxeGridRef">
<template
v-for="col in tableConfig.columns?.filter((c) => c.slots?.default)"
:key="col.field"
#[col.slots!.default]="{ row }"
>
<slot :name="col.slots!.default" :row="row" />
</template>
</vxe-grid> -->
</div>
</div>
</template>
<style lang="scss" scoped>
.price-item {
.header {
display: flex;
align-items: center;
.title {
margin-right: 12px;
}
.expand-icon {
cursor: pointer;
color: var(--el-color-primary);
}
.tip-text {
flex: 1;
text-align: right;
color: #4dbc15;
}
}
:deep() {
.price__table {
table {
width: 100%;
border-collapse: collapse;
th,
td {
border: 1px solid #eee;
padding: 8px;
text-align: center;
}
th {
background-color: #f9f9f9;
color: #111;
font-weight: bold;
}
}
}
}
}
</style>子组件完成之后,接下来完成父组件的引入
<template>
<!-- 不需要合并的配置 -->
<executePriceDialogItem
:grid-option="state.baseTableConfig"
title="基础价格配置"
>
<template #default="{ row }">
<td></td>
<td></td>
<td></td>
</template>
</executePriceDialogItem>
<!-- 需要合并的配置 -->
<executePriceDialogItem
:grid-option="state.specTableConfig"
title="规格价格配置"
>
<template #default="{ row }">
<!-- _cell:表示需要合并的单元格数量 -->
<td v-if="row._cell" :rowspan="row._cell" style="width: 200px"></td>
<td></td>
<td></td>
<td></td>
</template>
</executePriceDialogItem>
</template>那么这样一个组件就可以同步使用其他的场景了,当然数据格式是需要自己去整理的。