博客 / 詳情

返回

Vue3中使用hook實現按住Shift快速勾選el-table功能

需求描述

  • 最近產品説,某個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...
user avatar u_16213706 頭像 sergey-chikuyonok 頭像 careteenl 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.