IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Vue3中使用hook实现按住Shift快速勾选el-table功能

    水冗水孚发表于 2024-06-07 09:14:42
    love 0

    需求描述

    • 最近产品说,某个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([])

    注意,这里要使用select和select-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...


沪ICP备19023445号-2号
友情链接