在上一篇文章中,我们展示了我们封装的element-plus table组件的一些基础示例,本篇文章将展示一些复杂示例的用法,让我们一起来看看吧。
示例8--table pro demo
这个示例主要展示了table的展开用法,以及table末尾插槽用法,然后对比el-table的用法。我们可以使用append来向表体下方添加一些描述信息之类。在表格列数据配置当中,我们同样可以使用renderHeader方法来自定义渲染列,不过这在element-plus中是会被警告的,不建议使用。同样的我们也可以指定fixed属性用于固定列。首先我们来看我们的html代码,如下所示:
html
<div>
<!--第一个表格-->
<element-table :data="tableData" :column="column0" :border="false" :stripe="false">
<template #header> headersdfsf </template>
<template #append> appendsdfsdf </template>
</element-table>
<!--第二个表格-->
<element-table :data="tableData" :column="column" :border="false" :stripe="false">
<template #default="props">
<p>State: {{ props.row.state }}</p>
<p>City: {{ props.row.city }}</p>
<p>Address: {{ props.row.address }}</p>
<p>Zip: {{ props.row.zip }}</p>
</template>
<template #header> headersdfsf </template>
<template #append> appendsdfsdf </template>
</element-table>
<!--第三个表格-->
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
</div>
这其中,我们第一个表格使用了header插槽和append插槽自定义了列与表体下方的渲染。第二个表格同理,不过也增加了default默认插槽的渲染,第三个就是我们的el-table的基础使用用法。接下来我们来看tsx代码如下:
tsx
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'ElTableProDemo',
setup() {
const searchVal = ref('');
const tableData = [
// 数据省略
];
const column0 = [
{
label: '日期',
prop: 'date',
},
{
type: 'expand',
slotName: 'default',
},
{
label: '操作项',
fixed: 'right',
render() {
return (
<el-button
onClick={() => {
console.log(searchVal.value);
}}
>
按钮
</el-button>
);
},
headerSlot: 'header',
//这一种方法会有警告提示
// renderHeader(){
// return <el-input vModel={searchVal.value} size="default" placeholder="Type to search" />
// }
},
];
const column = [
{
label: '日期',
prop: 'date',
},
{
type: 'expand',
slotName: 'default',
},
{
label: '快递信息',
children: [
{
label: '名称',
prop: 'name',
},
{
label: '地址',
prop: 'state',
// children: [
// {
// label: '州',
// prop: 'state',
// },
// {
// label: 'city',
// prop: 'city',
// }
// ]
},
],
},
{
label: '操作项',
fixed: 'right',
render() {
return (
<el-button
onClick={() => {
console.log(searchVal.value);
}}
>
按钮
</el-button>
);
},
headerSlot: 'header',
// renderHeader(){
// return <el-input vModel={searchVal.value} size="default" placeholder="Type to search" />
// }
},
];
return {
column,
column0,
tableData,
};
}
});
可以看到,这里我们在render方法中返回一个按钮,注意这里我们使用的是tsx写法,如果是ts代码,则需要使用vue提供的h函数,如上一篇文章介绍到的示例用法。同时我们也使用了children属性来指定嵌套列。
以上代码展示效果如下所示:
示例9--可编辑的表格
在这个示例当中,我还碰到了一个很有意思的问题,那就是表格列数据如果发生了改变,页面视图是不会发生更新的,最开始我排查了底层封装,发现如果列数据发生变动,根本就不会执行到底层渲染逻辑,所以我才用了一种hack方式来解决掉这个问题,也就是给element-table组件标签添加一个key属性,值就时列数据,也就是说列数据如果发生变动,那么key属性值也就会发生变动,因此视图就会发生重新渲染更新。
首先我们来看html代码,就是一个按钮和一个表格,如下所示:
html
<el-button @click="toggleColumnSort">互换姓名与标签的顺序</el-button>
<!-- colum变更时不添加key则不会变化,这个问题有点奇怪 -->
<element-table :column="column" :data="tableData" :key="column"/>
接下来,主要逻辑在于tsx代码,我们创建2个ref响应式数据,用于存储列数据和表体数据,这里主要的难点在于列数据的定义,表体数据比较简单。如下:
tsx
import { ref } from 'vue';
// 这里是我们封装的列数据类型,后续会提到
import { ElTableColumnProps } from '../components/tableProps';
const column = ref<ElTableColumnProps[]>([
/* 这里先忽略 */
])
const tableData = ref([
// 数据省略
]);
按钮事件逻辑也比较简单,就是调整一下列数据的索引即可,如下:
tsx
const toggleColumnSort = () => {
column.value = [...column.value.slice(0, 1), column.value[2], column.value[1], ...column.value.slice(3)];
};
接下来就是列数据的定义,列数据,我们同样还是使用render函数来自定义列,请注意,这里我们为当前表格上下文的行数据中添加了一个_edit属性,代表是否可编辑,同时添加了一个_pre_data来存储行数据,这是因为我们编辑了行数据之后,数据会有所变动,所以我们需要在编辑之前提前存储数据,然后取消编辑的时候,我们就可以恢复原来数据。
因此,这里我们需要自定义三个按钮,分别是编辑,保存,取消按钮,日期我们会使用日期组件el-date-picker来编辑,姓名则是一个输入框组件,标签则是一个选择框组件。
首先,我们来看标签选项,如下所示:
ts
const tagOptions = [
{
label: '家',
value: '家',
},
{
label: '公司',
value: '公司',
},
];
基于以上分析,我们就可以定义出列数据了,如下所示:
tsx
const column = ref<ElTableColumnProps[]>([
{
prop: 'date',
label: '日期',
render: (value, scope) =>
scope.row._edit ?
<el-date-picker
model-value={value}
width={160}
type='date'
onUpdate:modelValue={(val: { toLocaleDateString: () => any; }) => {
scope.row[scope.column.property] = val?.toLocaleDateString();
}}
placeholder='Pick a day'
/>
: value,
},
{
prop: 'name',
label: '姓名',
render: (value, scope) =>
scope.row._edit ? <el-input
model-value={value}
onUpdate:modelValue={(val: any) => {
scope.row[scope.column.property] = val;
}}
/> : value,
},
{
prop: 'tag',
label: '标签',
render: (value, scope) =>
scope.row._edit ?
<el-select
model-value={value}
style='width: 120px'
onUpdate:modelValue={(val: string) => {
scope.row[scope.column.property] = val;
}}
>
{tagOptions.map((option) => (
<el-option label={option.label} value={option.value}></el-option>
))}
</el-select>
: <el-tag type={scope.row.tag === '家' ? 'info' : 'success'}>
{value}
</el-tag>,
},
{
label: '操作',
render: (scope) =>
scope.row._edit ?
<div>
<el-button
type='primary'
onClick={() => {
handleSave(scope);
}}
>保存</el-button>
<el-button
onClick={() => {
handleCancel(scope);
}}
> 取消</el-button>
</div> :
<el-button
type='primary'
onClick={function () {
handleEdit(scope);
}}
> 编辑</el-button>
},
]);
这里尤其需要注意的就是在tsx代码中,我们可能无法使用v-model双向绑定指令,因此我们使用的是model-value属性和onUpdate:modelValue事件来实现数据的绑定已经变更,当然如果是写在ts代码中,则不必如此麻烦,不过如果是写ts代码,那么写组件就需要用h函数,写起来一样的麻烦。
接下来,就是三个方法的实现了,保存很简单,就是修改_edit属性为false即可,如果在实际业务当中,就需要调用保存接口。而取消就是重置行数据,编辑的逻辑则是修改_edit属性为true,并缓存行数据,代码分别如下所示:
ts
const handleEdit = (scope: { row: { _pre_data: string; _edit: boolean; }; }) => {
scope.row._pre_data = JSON.stringify(scope.row);
scope.row._edit = true;
};
const handleSave = (scope: { row: { _edit: boolean; }; }) => {
scope.row._edit = false;
};
const handleCancel = (scope: { row: { _pre_data: string; }; }) => {
Object.assign(scope.row, {
...JSON.parse(scope.row._pre_data),
_edit: false,
_pre_data: null,
});
};
以上代码展示效果如下所示:
示例10--表体数据展开
数据的展开,只需要使用type属性值为expand,然后需要自定义展开项,就可以使用插槽,以下是一个简单的示例。html代码如下所示:
html
<element-table :column="column" :data="tableData" style="width: 100%">
<template #default="props">
<p>State: {{ props.row.state }}</p>
<p>City: {{ props.row.city }}</p>
<p>Address: {{ props.row.address }}</p>
<p>Zip: {{ props.row.zip }}</p>
</template>
</element-table>
ts代码如下所示:
ts
const column = [
// 主要在这里,哪一列想要展开,就在哪里添加这个配置对象,这里是第一列展开
{
type: "expand",
slotName: "default",
},
{
label: "Date",
prop: "date",
},
{
label: "Name",
prop: "name",
},
];
const tableData = [
// 数据省略
];
以上代码展示效果如下所示:
示例11--数据过滤器(包含排序)
在列中设置 filters
和 filter-method
属性即可开启该列的筛选, filters 是一个数组,filter-method
是一个方法,它用于决定某些数据是否显示, 会传入三个参数:value
, row
和 column
。
同样的也可以配置sortable属性,用于开启排序功能。
表格实例对象,我们通过一个ref响应式数据绑定,表格实例对象提供了很多方法,其中就包含了清除过滤的方法,即clearFilter方法,该方法支持传入一个列属性名组成的数组,比如你的表格配置了日期列,属性名为date,那么就可以传入["date"]
参数。
并且我们也可以给列设置一个formatter函数,用于格式化列的展示数据,还可以设置filter-placement属性来设置过滤弹出框的定位。
接下来,我们就来看看具体的html代码和ts代码。
html
<!--重置日期过滤-->
<el-button @click="resetDateFilter">reset date filter</el-button>
<!--重置所有过滤-->
<el-button @click="clearFilter">reset all filters</el-button>
<!--表格-->
<element-table
:column="column"
ref="tableRef"
row-key="date"
:data="tableData"
style="width: 100%"
>
<template #default="scope">
<el-tag :type="scope.row.tag === 'Home' ? 'info' : 'success'" disable-transitions>
{{ scope.row.tag }}
</el-tag>
</template>
</element-table>
需要注意,这里我们需要给row-key设置一个过滤属性值,这里设置的是date属性,当然我们也可以设置其它值。然后我们绑定了一个tableRef值,这里就需要用到element-plus提供的2个类型TableColumnCtx和ElTable,即表格列上下文类型和表格实例对象类型。
代码如下所示:
ts
import { ref } from "vue";
//导入的两个类型
import type { TableColumnCtx } from "element-plus/es/components/table/src/table-column/defaults";
import type { ElTable } from "element-plus";
接下来,我们就创建一个存储表格实例对象的响应式ref,代码如下:
ts
const tableRef = ref<InstanceType<typeof ElTable>>();
接下来就基于这个表格实例调用clearFilter方法,这就是2个按钮的事件逻辑,同样的我们也定义了formatter以及filter-method的方法,代码如下所示:
ts
const resetDateFilter = () => tableRef.value!.clearFilter(["date"]);
const clearFilter = () => tableRef.value!.clearFilter();
const formatter = (row: User) => row.address;
const filterTag = (value: string, row: User) => row.tag === value;
const filterHandler = (value: string, row: User, column: TableColumnCtx<User>) => {
const property = column["property"] as keyof User;
return row[property] === value;
};
note: 这里的!是ts的非空断言操作符。
然后就是列数据和表体数据的定义了,如下所示:
ts
const column = [
{
type: "index",
width: 50,
},
{
prop: "date",
label: "Date",
sortable: true,
width: 180,
"column-key": "date",
filters: [
{ text: "2016-05-01", value: "2016-05-01" },
{ text: "2016-05-02", value: "2016-05-02" },
{ text: "2016-05-03", value: "2016-05-03" },
{ text: "2016-05-04", value: "2016-05-04" },
],
"filter-method": filterHandler,
},
{
prop: "name",
label: "Name",
width: 120,
},
{
prop: "address",
label: "Address",
formatter,
},
{
prop: "tag",
label: "Tag",
filters: [
{ text: "Home", value: "Home" },
{ text: "Office", value: "Office" },
],
"filter-method": filterTag,
"filter-placement": "bottom-end",
slotName: "default",
},
];
const tableData: User[] = [
// 数据忽略
]
以上代码展示效果如下所示:
示例12--固定列
可以为列数据配置一个fixed字段,该字段是一个布尔值true或者false或者字符串值'left'或者'right',默认值是false,即可设置一个固定列。代码如下:
html
<element-table border :column="column" :data="tableData" />
ts
const handleClick = () => {
console.log("click");
};
const column = [
{
prop: "date",
fixed: true,
width: 150,
label: "Date",
},
{
prop: "name",
label: "Name",
width: 120,
},
{
prop: "state",
label: "State",
width: 120,
},
{
prop: "city",
label: "City",
width: 120,
},
{
prop: "address",
label: "Address",
width: 360,
},
{
prop: "zip",
label: "Zip",
width: 180,
},
{
label: "Operations",
width: 200,
fixed: "right",
render() {
return (
<>
<el-button type="text" size="small" onClick={handleClick}>
Detail
</el-button>
<el-button type="text" size="small">
Edit
</el-button>
</>
);
},
},
];
const tableData = [
// ...
];
以上代码展示效果如下图所示:
示例13--固定表头
可以为表格设置一个具体的高度,即height属性,这样当视图高度小于设置的高度,则会固定住表头,只允许滚动表体数据。代码如下所示:
html
<element-table border :column="column" :data="tableData" :height="250" />
ts
// 略
以上代码展示效果如下图所示:
示例14--流体高度
当数据量动态变化时,可以为表格设置一个最大高度。
通过设置 max-height
属性为 el-table
指定最大高度。 此时若表格所需的高度大于最大高度,则会显示一个滚动条。
来看示例代码如下:
html
<element-table border :column="column" :data="tableData" max-height="250">
<template #default="scope">
<el-button text type="primary" size="small" @click.prevent="deleteRow(scope.$index)">Remove</el-button>
</template>
</element-table>
<el-button class="mt-4" style="width: 100%" @click="onAddItem">Add Item</el-button>
ts代码如下所示:
ts
import { ref } from "vue";
const now = new Date();
const column = [
{
prop: "date",
fixed: true,
width: 150,
label: "Date",
},
{
prop: "name",
label: "Name",
width: 120,
},
{
prop: "state",
label: "State",
width: 120,
},
{
prop: "city",
label: "City",
width: 120,
},
{
prop: "address",
label: "Address",
width: 360,
},
{
prop: "zip",
label: "Zip",
width: 180,
},
{
label: "Operations",
fixed: "right",
width: 120,
slotName: "default",
},
];
const tableData = ref([
// ...
]);
const deleteRow = (index: number) => {
tableData.value.splice(index, 1);
};
const onAddItem = () => {
now.setDate(now.getDate() + 1);
tableData.value.push({
date: formatDate(now, "yyyy-MM-dd")!,
name: "Tom",
state: "California",
city: "Los Angeles",
address: "No. 189, Grove St, Los Angeles",
zip: "CA 90036",
});
};
这里可以额外介绍一下formatDate方法的实现,当然我们也可以使用day.js依赖。这个方法有多种实现方式,这里采用的是正则表达式匹配法,将字符串按照年月日来匹配,每次匹配到一个,用一个括号包裹,这样就可以当成一个分组(捕获组),然后通过读取$1
实例属性就可以获取到每一个匹配到的日期了,然后我们为每一个日期定义一个字符串属性,值分别利用date日期实例对象的相关方法,例如年就是date.getFullYear()
。基于以上的分析,我们就能实现如下这个工具函数:
ts
// 第一个参数为日期对象或者日期字符串,第二个参数则是格式化的参数
const formatDate = (date: string | Date, fmt: string = "yyyy-MM-dd hh:mm:ss") => {
if (typeof date == "string") {
return date;
}
if (!date || date === null) return null;
const o = {
"M+": date.getMonth() + 1, // 月份
"d+": date.getDate(), // 日
"h+": date.getHours(), // 小时
"m+": date.getMinutes(), // 分
"s+": date.getSeconds(), // 秒
"q+": Math.floor((date.getMonth() + 3) / 3), // 季度
S: date.getMilliseconds(), // 毫秒
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(RegExp.$1, `${date.getFullYear()}`.slice(4 - RegExp.$1.length));
for (const k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
const item = o[k as keyof typeof o]
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? String(item) : `00${item}`.slice(`${item}`.length)
);
}
}
return fmt;
}
以上代码展示效果如下图所示:
本篇文章就到这里为止,下一章我们将介绍最后的几个示例用法。