需求描述

  • 最近产品说,某个el-table要实现按住shift键快速勾选功能
  • 大概就是仿windows系统的文件shift按住选中功能
  • 反正就是尽可能多的让用户勾选
  • 方便用户快速勾选操作
github完整代码:https://github.com/shuirongshuifu/vue3-echarts5-example

Windows系统的功能效果图

  • 比如可以向前多选
  • 或者向后多选
  • 大家可以自己尝试一下

自己实现的el-table勾选效果图

实现思路

  • 页面加载好了以后,绑定监听事件,监听用户键盘按下和抬起事件,看看是不是Shift键
  • 页面销毁时候,再卸载一下
  • 搞三个变量记录是否按下Shift键、勾选el-table是第几行,和再次勾选el-table是第几行
  • 假设第一次勾选的是第四行,第二次勾选的是第七行,只需要把四行和七行中间的五六行控制为勾选即可
  • 同理,第一次勾选第七行,第二次勾选第四行也是一样
  • 最后shift键抬起的时候,控制把三个变量重置即可
  • less word,more code

代码

先来个el-table

<template>
    <el-table ref="multipleTableRef" :data="tableData" style="width: 100%" @select="selectFn" @select-all="selectAllFn">
        <el-table-column type="selection" width="55" />
        <el-table-column label="Date" width="120" />
        <el-table-column property="name" label="Name" width="120" />
        <el-table-column property="address" label="Address" show-overflow-tooltip />
    </el-table>
</template>

// 数据
import { ref, onMounted, onBeforeUnmount, reactive } from 'vue'
const tableData = ref([
    {
        id: 1,
        date: '2016-05-01',
        name: '111',
        address: 'No. 189, Grove St, Los Angeles',
    },
    ...
])

再绑定监听事件

onMounted(() => {
    window.addEventListener('keydown', onKeyDown);
    window.addEventListener('keyup', onKeyUp);
});

onBeforeUnmount(() => {
    window.removeEventListener('keydown', onKeyDown);
    window.removeEventListener('keyup', onKeyUp);
});

const onKeyDown = (e) => {
    if (e.key === 'Shift') {
        clickInfo.isShiftPressed = true;
    }
};

const onKeyUp = (e) => {
    if (e.key === 'Shift') {
        // 鼠标抬起,重置初始状态
        clickInfo.isShiftPressed = false;
        clickInfo.startRowIndex = -1
        clickInfo.endRowIndex = -1
    }
};

定义相关变量信息

clickInfo是收集到的信息

const clickInfo = reactive({
    // 开始勾选的索引,初始没勾选为-1
    startRowIndex: -1,
    // 结束勾选的索引, 初始没勾选为-1
    endRowIndex: -1,
    // 是否按下shift键,初始没有摁下
    isShiftPressed: false
})

当然还需要定义表格实例和勾选存储数组,如下:

const multipleTableRef = ref()
const multipleSelection = ref([])

注意,这里要使用selectselect-all去控制,不使用selection-change事件,因为要更灵活第去控制了,如下:

// 全选
const selectAllFn = (selection) => {
    multipleSelection.value = selection
}

// 单选
const selectFn = (selection, row) => {
    multipleSelection.value = selection
    // Shift相关控制逻辑...
}

Shift勾选控制关键代码

  • 全选不用控制
  • 控制的逻辑主要在单选这一块
  • 请对着注释阅读:
// 单选
const selectFn = (selection, row) => {
    multipleSelection.value = selection
    // 获取当前点击的是第几行
    let i = tableData.value.findIndex((item) => item.id == row.id)
    // Shift按下逻辑
    if (clickInfo.isShiftPressed) {
        // 初始没勾选,就赋值开始勾选索引
        if (clickInfo.startRowIndex === -1) {
            clickInfo.startRowIndex = i
        }
        // 初始勾选了,说明是第二次勾选
        else {
            // 赋值索引
            clickInfo.endRowIndex = i
            // 执行把中间段的表格勾选上逻辑
            selectTable(clickInfo.startRowIndex, clickInfo.endRowIndex)
        }
    }
}

// 执行勾选逻辑
const selectTable = (startRowIndex, endRowIndex) => {
    // 第一次勾选后,紧接着再次勾选,有可能往前勾选,也有可能往后勾选,所以要做一个大小区分
    const startIndex = Math.min(startRowIndex, endRowIndex);
    const endIndex = Math.max(startRowIndex, endRowIndex);
    // 遍历去把中间段的勾选上
    tableData.value.forEach((rowData, rowIndex) => {
        // 若是中间项包含在已勾选的数组中去,就忽略之(这里我们用id为标识做区分)
        if (multipleSelection.value.some((msItem) => msItem.id == rowData.id)) { }
        // 若是不在勾选的数组中,在去看看要不勾选
        else {
            // 因为起始勾选和再次勾选的数据,已经保存到勾选数组中去了,所以不用管
            if (rowIndex > startIndex && rowIndex < endIndex) {
                // 只需把中间段的状态置为勾选,并丢到勾选数组中去就行了
                multipleTableRef.value.toggleRowSelection(rowData, rowIndex > startIndex && rowIndex < endIndex)
                multipleSelection.value.push(rowData)
            }
        }
    })
}
  • 至此,需求就算解决了...
  • 但是我们想,若是过两天,另外一个el-table也需要这个需求功能呢?
  • 再复制粘贴一份吗?
  • 似乎太麻烦,所以如何优化呢?
  • 如何能够做到复用呢?

hook优化

  • 懂的都懂,这里使用Vue3中的hook会更加合适
  • 更方便复用代码逻辑

什么是hook

复杂的概念简单化...

  • 说到代码复用这一块,我们会想到什么?
  • 哦,有组件的复用,比如封装一个公共的卡片组件、表单组件
  • 哦,有工具函数的复用,比如有一个获取当前的年月日时分秒的函数
  • 哦,Vue2还有Mixin这个可以概念
  • 同样的,hook也是一种复用的方式,就是单独拎出来,哪里需要哪里引入,哪里使用即可

hook代码

我们思考一下,这个需求的什么东西可以单独拎出来呢?

  • 那这里绑定、销毁键盘按下抬起事件可以拎出来
  • 收集的clickInfo信息也可以单独拎出来
  • 甚至于勾选时候控制表格,给表格的multipleSelection塞值,也可以单独拎出来

于是乎,我们就可以这样做了

  • 新建一个hook文件夹,用于存放越来越多的hook
  • 取个名字,一般用use开头useShiftQuickSelect.ts文件
  • 拎出来操作,再暴露出去,给外边用
  • 代码:

hook/useShiftQuickSelect.ts

import { onMounted, onBeforeUnmount, reactive } from 'vue'
export function useShiftQuickSelect() {

    onMounted(() => {
        window.addEventListener('keydown', onKeyDown);
        window.addEventListener('keyup', onKeyUp);
    });

    onBeforeUnmount(() => {
        window.removeEventListener('keydown', onKeyDown);
        window.removeEventListener('keyup', onKeyUp);
    });

    const onKeyDown = (e) => {
        if (e.key === 'Shift') {
            clickInfo.isShiftPressed = true;
        }
    };

    const onKeyUp = (e) => {
        if (e.key === 'Shift') {
            // Shift抬起重置
            clickInfo.isShiftPressed = false;
            clickInfo.startRowIndex = -1
            clickInfo.endRowIndex = -1
        }
    };

    const clickInfo = reactive({
        startRowIndex: -1,
        endRowIndex: -1,
        isShiftPressed: false
    })

    /**
     * tableData表格数据、multipleSelection勾选数组,multipleTableRef表格实例
     * key用于进行对比的标识字段,一般都是每一行的唯一身份证即id
     * */ 
    const ctr = (tableData, multipleSelection, multipleTableRef, key) => {
        // 获取当前点击的是第几行
        let i = tableData.findIndex((item) => item.id == key)
        // Shift按下逻辑
        if (clickInfo.isShiftPressed) {
            // 初始没勾选,就赋值开始勾选索引
            if (clickInfo.startRowIndex === -1) {
                clickInfo.startRowIndex = i
            } 
            // 初始已经勾选,就说明是shift快速勾选
            else {
                // 索引赋值
                clickInfo.endRowIndex = i
                // 把开始索引和结束索引进行大小对比
                const { startRowIndex, endRowIndex } = clickInfo
                const startIndex = Math.min(startRowIndex, endRowIndex);
                const endIndex = Math.max(startRowIndex, endRowIndex);
                // 遍历操作
                tableData.forEach((rowData, rowIndex) => {
                    // 若是这一项包含在已勾选的数组中去(已勾选),就忽略之;没勾选就控制其勾选
                    if (!multipleSelection.some((msItem) => msItem.id == rowData.id)) {
                        // 中间段勾选
                        if (rowIndex > startIndex && rowIndex < endIndex) {
                            // 改表格状态并存起来
                            multipleTableRef.toggleRowSelection(rowData, rowIndex > startIndex && rowIndex < endIndex)
                            multipleSelection.push(rowData)
                        }
                    }
                })
            }
        }
    }
    return { ctr }
}
  • 这里我暴露一个ctr函数,给外层用,外层只要传递进来表格绑定的数据tableData
  • 传进来勾选数组multipleSelection
  • 传进来表格实例multipleTableRef,用于控制表格勾选
  • 和区分某一行是否被勾选的字段(比如用id字段来区分判断)
  • 这样的话,就简单多了
  • 具体多简单,让我们看看使用的地方,代码:

使用hook的代码

<template>
    <el-table ref="multipleTableRef" :data="tableData" style="width: 100%" @select="selectFn" @select-all="selectAllFn">
        <el-table-column type="selection" width="55" />
        <el-table-column label="Date" width="120">
            <template #default="scope">{{ scope.row.date }}</template>
        </el-table-column>
        <el-table-column property="name" label="Name" width="120" />
        <el-table-column property="address" label="Address" show-overflow-tooltip />
    </el-table>
    <button @click="look">查看已勾选的</button>
</template>

<script setup>
import { ref } from 'vue'
import { useShiftQuickSelect } from "@/hook/useShiftQuickSelect.js";

const tableData = ref([
    {
        id: 1,
        date: '2016-05-01',
        name: '111',
        address: 'No. 189, Grove St, Los Angeles',
    }
])

const multipleTableRef = ref()
const multipleSelection = ref([])

const look = () => {
    console.log('multipleSelection', multipleSelection.value);
}

// 全选
const selectAllFn = (selection) => {
    multipleSelection.value = selection
}

// 单选
const { ctr } = useShiftQuickSelect()
const selectFn = (selection, row) => {
    multipleSelection.value = selection
    // hook操作控制函数
    ctr(tableData.value, multipleSelection.value, multipleTableRef.value, row.id)
}

</script>
  • 看到了叭,只有几行就行了。设置与可以说,仅通过ctr函数传递一下参数,问题就解决了。
  • 大大提升了效率
  • hook is yyds...
A good memory is better than a bad pen. Write it down...

水冗水孚
1.1k 声望585 粉丝

每一个不曾起舞的日子,都是对生命的辜负