需求
又到了一天一个新需求的环节了,今天万恶了资本主义又不知道从哪发现了一个新大陆。
要在原有的el-table上 可拖动列、调整列宽、本地按照用户保存、控制动态列项显示 。
要不是看在我 爱学习 才不给你开发呢!!!!
话不多说上图吧:
这是一个6列的页面,用户不想要最后一列了,只需要点击列头右侧按钮,在弹出层里,就可以 进行如下操作:
- 拖拽改变列项顺序,
- 控制哪列显示,
- 重置恢复初始代码配置顺序,
思路
实现关闭浏览器还能缓存住 那最后一定存 localstorage 里了。table列项数据一定要配置项了。然后 控制配置项 就好了。那就撸起袖子一把梭哈。0.o
table组件
<script lang="ts">
import { Component, Prop, Mixins, Emit } from 'vue-property-decorator';
import { TableMix } from 'feok-lib';
import TableSettingMix from '@/views/mixins/TableSettingMix';
import ColumnsDrawer from '@/components/ColumnsDrawer';
@Component({
components: {
ColumnsDrawer,
},
})
export default class Table extends Mixins(
TableMix,
TableSettingMix,
) {
@Prop({ default: 'XXXX' })
public tableUserKey!: string; // local缓存的key名,一定要保证不同哦
public COLUMNS: any[] = [
{
prop: 'order_id',
label: '入库单号/入库仓',
sort: 0,
width: '',
minWidth: '120',
drag: false, // 是否可拖拽
display: true, // 是否显示
disabled: true, // 禁用弹窗选中操作
slot: 'order_id', // 如果跟prop相同,就认为是名为order_id的插槽
},
...
// 多个就是复制上面的就好了,简化代码就不写了
];
}
</script>
<template>
<div class="test-table">
<el-table
size="small"
border
:data="data"
:ref="refStr"
v-bind="$attrs"
v-on="$listeners"
v-loading="loading"
:empty-text="emptyText"
row-key="id"
@header-dragend="headerDragend"
>
<!-- 我习惯了开头留一个插槽,末尾留一个插槽,可以无视 -->
<slot name="firstColumn"></slot>
<template v-for="(item, index) of removelastColumnsWidth(columns)">
<!-- 插槽的配置项 -->
<slot :name="item.slot" v-if="item.prop === item.slot" :item="item" :index="index"></slot>
<el-table-column
v-if="item.prop === 'order_id'"
prop="order_id"
:width="item.width"
:key="`${index}-${item.prop}`"
:minWidth="item.minWidth"
:resizable="item.resizable"
:label="label"
>
<template slot-scope="{row}">
<div>{{$ph(row.order_id)}}</div>
</template>
</el-table-column>
<!-- 同理 后面的不写了 -->
</template>
<!-- 我习惯了开头留一个插槽,末尾留一个插槽,可以无视 -->
<slot name="handle" :setting="settingColumns"></slot>
</el-table>
// 弹框组件
<ColumnsDrawer
v-if="destoryDrawer"
:visiable="visiable"
:columns="columns"
@close="close"
@reset="reset"
@on-done="saveSetting"
@closed="destoryDrawer=false"
/>
</div>
</template>
复制代码
看完上面的代码,你一定好奇ColumnsDrawer
组件是干嘛的,有必要么,那我们来看一下吧,其实他就是弹框组件。
你会发现最终用的el-drawer + el-tree
实现的。
弹框组件
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
@Component
export default class ColumnsDrawer extends Vue {
public get defaultCheckedKeys() {
// 默认显示想,记得我们刚才配置的display属性么,不记得划上去看一眼吧
return this.data
.filter((item: any) => item.display)
.map((item: any) => item.prop);
}
@Prop() public visiable!: boolean; // el-drawer显示项
@Prop({ default: () => [] }) public columns!: any[]; // table 列项配置项
public data: any[] = []; // el-tree数据(其实就是table深拷贝)
public defaultProps = { // el-tree 配置项,可以参考el官网
children: 'children',
label: 'label',
};
// el-tree的一系列拖动事件(想了解去官网,没用到就不解释了,看英文直译就好)
public handleDragStart(snode: any, event: Event) {
// console.log(snode, event);
}
public handleDragEnter(snode: any, enode: any, event: Event) {
// console.log(snode, enode, event);
}
public handleDragLeave(snode: any, enode: any, event: Event) {
// console.log(snode, enode, event);
}
public handleDragOver(snode: any, enode: any, event: Event) {
// console.log(snode, enode, event);
}
// 拖拽结束
public handleDragEnd(snode: any, enode: any, event: Event) {
// 互换sort 实现改变列项顺序
const tempSort = snode.data.sort;
snode.data.sort = enode.data.sort;
enode.data.sort = tempSort;
}
public handleDrop(snode: any, enode: any, event: Event) {
// console.log(snode, enode, event);
}
// 拖拽时判定目标节点能否被放置
public allowDrop(draggingNode: any, dropNode: any, type: string) {
// console.log(dropNode.data.drag);
if (dropNode.data.drag === false || type === 'inner') {
return false;
} else {
return true;
}
}
// 判断节点能否被拖拽
public allowDrag(node: any) {
return node.data.drag;
}
// 保存
public save() {
this.$emit('on-done', this.data);
}
// 选中变化
public checkChange(item: any, checked: boolean) {
item.display = checked;
}
public created() {
this.data = JSON.parse(JSON.stringify(this.columns));
}
}
</script>
<template>
<div class="test-drawer">
<el-drawer
v-on="$listeners"
title="配置列项"
:visible="visiable"
:show-close="false"
:wrapper-closable="true"
direction="rtl"
size="auto"
>
<template slot="title">
<span>配置列项</span>
<sub class="feok-link re-md-sm-margin-left" @click="$emit('reset')" title="重置列项配置信息">重置</sub>
<!-- <i class="el-icon-refresh re-md-sm-margin-left" @click="$emit('reset')"></i> -->
</template>
<div class="columns-setting">
<el-tree
class="setting-columns"
:data="data"
:props="defaultProps"
draggable
show-checkbox
node-key="prop"
:default-checked-keys="defaultCheckedKeys"
@node-drag-start="handleDragStart"
@node-drag-enter="handleDragEnter"
@node-drag-leave="handleDragLeave"
@node-drag-over="handleDragOver"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
@check-change="checkChange"
:allow-drop="allowDrop"
:allow-drag="allowDrag"
>
<div class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<i class="el-icon-rank" v-if="data.drag"></i>
</div>
</el-tree>
<div class="setting-btn">
<el-button size="small" type="primary" @click="save">确定</el-button>
<el-button size="small" @click="$emit('close')">取消</el-button>
</div>
</div>
</el-drawer>
</div>
</template>
<style lang="less">
.test-drawer {
.columns-setting {
display: flex;
flex-direction: column;
min-width: 210px;
height: 100%;
padding: 10px;
.setting-columns {
flex: 1;
li {
list-style: none;
}
}
.setting-btn {
flex: 0 0 auto;
}
.custom-tree-node {
display: flex;
justify-content: space-between;
width: 100%;
[class*='el-icon'] {
opacity: 0.6;
}
}
}
.el-drawer__header {
margin-bottom: 0;
padding: 20px;
border-bottom: 1px solid #f0f0f0;
font-weight: bold;
background: rgb(243, 243, 244);
}
.el-tree-node {
margin-bottom: 10px;
[class*='el-tree-node__expand'] {
display: none;
}
}
}
</style>
复制代码
好了,组件写完了,是不是可以直接用了?等等,好像少了点东西没给我啊,我是不是藏私了?
被发现了,那我交代吧。
TableSettingMix混合
/**
* @author Yuanr
* @description table列项调整混合
*/
import { Component, Vue, Prop } from 'vue-property-decorator';
import { get } from 'lodash';
import ColumnsDrawer from '@/components/ColumnsDrawer';
interface COLUMNS_SETTING {
prop: string; // 列key
label: string; // 列名称
sort: number; // 列排序,必须每一个项都有排序值
width: string; // 列宽
drag: boolean; // 列是否可以拖拽排序
display: boolean; // 列是否显示
disabled: boolean; // 列项设置时禁用选中操作
slot: string; // 列项是否为slot
}
@Component({
components: {
ColumnsDrawer,
},
})
export default class TableSettingMix extends Vue {
@Prop({ default: 'TableColumns' }) public tableUserKey!: string; // TABLE_USER_KEY tableUserKey
@Prop({ default: '' }) public tableVersion!: string; // 这里本来想加版本号,后来没用上
public readonly USER_KEY = `${get(JSON.parse(sessionStorage.getItem('userinfo')!), 'account', '').trim()}`;
public REFNAME = 'TABLE_REF';
public COLUMNS: COLUMNS_SETTING[] = [
// {
// prop: 'name', // 列key
// label: '姓名', // 列名称
// sort: 0, // 列排序,必须每一个项都有排序值
// width: '', // 列宽
// drag: false, // 列是否可以拖拽排序
// display: true, // 列是否显示
// disabled: true, // 列项设置时禁用选中操作
// slot: 'default', // 列项是否为slot
// },
];
public visiable = false; // el-drawer visible
public destoryDrawer = false; // el-drawer v-if
public columns: any[] = [];
// 本地存储格式 Table__USER_KEY__TABLE_USER_KEY__TABLE_VERSION
get localstorageKey() {
return `Table__${this.USER_KEY}__${this.tableUserKey}__${this.tableVersion}`;
}
// 获取table
get refStr() {
return (this as any).refName || this.REFNAME;
}
// 列model规则: TABLECOLUMNS__项目__模块__Table名称
// 列项排序
public sort(data: any[]) {
return data.sort((s, e) => {
return s.sort - e.sort;
});
}
// 弹出列项设置
public settingColumns() {
this.destoryDrawer = true;
this.visiable = true;
}
public close() {
this.visiable = false;
}
// 修改列项宽度
public headerDragend(newWidth: number, oldWidth: number, column: any, event: Event) {
if (!column.resizable) {
return false;
}
const data = JSON.parse(JSON.stringify(this.columns));
const item: any = data.find((item: any) => item.prop === column.property);
if (item) {
item.width = newWidth;
this.setColumns(data);
this.syncColumns(data);
}
}
// 保存设置
public saveSetting(data: any[]) {
this.setColumns(data);
this.syncColumns(data);
this.sortColumns();
this.close();
}
public sortColumns() {
this.columns = this.sort(this.columns);
}
// 根据实际情况,移除最后一项的宽度调整
public get removelastColumnsWidth() {
return (data: any[]) => {
return data.filter((item: any) => item.display).map((item: any, index: number, arr: any[]) => {
if (index === arr.length - 1) {
return {
...item,
width: '',
resizable: false,
};
} else {
return item;
}
});
};
}
// sessionStorage.getItem('token');
// 获取columns
public getColumns() {
if (this.tableUserKey && (localStorage as any)[this.localstorageKey]) {
return JSON.parse((localStorage as any)[this.localstorageKey]);
} else {
return [];
}
}
// 设置 columns
public setColumns(data: any[]) {
if (this.tableUserKey) {
(localStorage as any)[this.localstorageKey] = JSON.stringify(data);
}
}
public syncColumns(data: any[]) {
const map: any = {};
for (const column of data) {
map[column.prop] = column;
}
this.columns = this.columns.map((column: any) => ({
...column,
...(map[column.prop] || {}),
prop: column.prop,
label: column.label,
}));
}
public created() {
// 初始化数据
this.columns = this.COLUMNS;
// 同步设置
if (this.tableUserKey && (localStorage as any)[this.localstorageKey]) {
this.syncColumns(this.getColumns());
} else {
this.syncColumns(this.COLUMNS);
}
// 排序
this.sortColumns();
}
public mounted() {
// elementUI Bug 修复
try {
const tableHeaderRef: any = ((this.$refs as any)[this.refStr] as any).$refs.tableHeader;
const handleMouseMove = tableHeaderRef.handleMouseMove;
const handleMouseOut = tableHeaderRef.handleMouseOut;
tableHeaderRef.handleMouseMove = (event: Event, column: any) => {
if (!column.resizable) {
return false;
}
handleMouseMove(event, column);
const target: any = get(event, 'target.tagName') === 'TH' ? get(event, 'target') : (get(event, 'target.classList').value === 'cell' ? get(event, 'target.parentNode') : undefined);
if (!target) {
return false;
}
if (tableHeaderRef.draggingColumn) {
target.classList.add('th-draging');
} else {
target.classList.remove('th-draging');
}
if (tableHeaderRef.dragging) {
target.classList.remove('th-draging');
}
};
tableHeaderRef.handleMouseOut = () => {
handleMouseOut();
const draggingArr: NodeList = document.querySelectorAll('.th-draging');
for (const drag of draggingArr) {
(drag as any).classList.remove('th-draging');
}
};
} catch (e) {
throw Error('elementUI Bug 待解决');
}
}
// 删除用户自定义
public reset() {
if (this.tableUserKey) {
(localStorage as any)[this.localstorageKey] = '';
this.columns = this.COLUMNS;
}
this.close();
this.$nextTick(() => {
this.setColumns(this.COLUMNS);
});
}
}
复制代码
好了,剩下就是组件调用了
<PurchaseOrderTable
ref="PurchaseOrderTable"
refName="PurchaseOrderTable"
/>
复制代码
总结
其实,重点是弹窗及里面的怎么实现。当我们找到一个方法的时候,剩下的都迎刃而解了。这时候给大兄弟们提个醒,没事就看看官网文档,每次都会有新收获~
兄台,请留步。这里有几点要注意一下哦~
问题1:注意下混合里的本地存储格式定义
因为每个项目定义的规则不一定,所以在这里修改
// 本地存储格式 Table__USER_KEY__TABLE_USER_KEY__TABLE_VERSION
get localstorageKey() {
return `Table__${this.USER_KEY}__${this.tableUserKey}__${this.tableVersion}`;
}
复制代码
问题2:是不是每一个table都的手写一个配置项啊
这里很遗憾的告诉大家,是的,这只是一个初步解决方案。如果大家有更简洁的方案。欢迎留言~
问题3:留言为什么叫我狗子
我可能是狗,留言哪些小伙伴他们也不-- 当-- 人-- 啊。下回见。👋
彩蛋
啊哈,大家好啊,又进入的彩蛋环节,今天的彩蛋是。 element-ui宣布适配Vue3.0了!!!!
被迫用Ant的小伙伴们可以等一下了!
秀逗马得~~~~~~~~~
公布地址: juejin.cn/post/690073…
Git地址: github.com/element-plu…
官网地址: element-plus.gitee.io/#/zh-CN
如果这篇文章有用,欢迎评论, 点赞, 加关注
。
我是leo:祝大家早日升职加薪。