利用Vue3實現(xiàn)可復(fù)制表格的方法詳解
前言
表格是前端非常常用的一個控件,但是每次都使用v-for指令手動繪制tr/th/td這些元素是非常麻煩的。同時,基礎(chǔ)的 table 樣式通常也是不滿足需求的,因此一個好的表格封裝就顯得比較重要了。
最基礎(chǔ)的表格封裝
最基礎(chǔ)基礎(chǔ)的表格封裝所要做的事情就是讓用戶只關(guān)注行和列的數(shù)據(jù),而不需要關(guān)注 DOM 結(jié)構(gòu)是怎樣的,我們可以參考 AntDesign,columns dataSource 這兩個屬性是必不可少的,代碼如下:
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Column {
title: string;
dataIndex: string;
slotName?: string;
}
type TableRecord = Record<string, unknown>;
export const Table = defineComponent({
props: {
columns: {
type: Array as PropType<Column[]>,
required: true,
},
dataSource: {
type: Array as PropType<TableRecord[]>,
default: () => [],
},
rowKey: {
type: Function as PropType<(record: TableRecord) => string>,
}
},
setup(props, { slots }) {
const getRowKey = (record: TableRecord, index: number) => {
if (props.rowKey) {
return props.rowKey(record)
}
return record.id ? String(record.id) : String(index)
}
const getTdContent = (
text: any,
record: TableRecord,
index: number,
slotName?: string
) => {
if (slotName) {
return slots[slotName]?.(text, record, index)
}
return text
}
return () => {
return (
<table>
<tr>
{props.columns.map(column => {
const { title, dataIndex } = column
return <th key={dataIndex}>{title}</th>
})}
</tr>
{props.dataSource.map((record, index) => {
return (
<tr key={getRowKey(record, index)}>
{props.columns.map((column, i) => {
const { dataIndex, slotName } = column
const text = record[dataIndex]
return (
<td key={dataIndex}>
{getTdContent(text, record, i, slotName)}
</td>
)
})}
</tr>
)
})}
</table>
)
}
}
})需要關(guān)注一下的是 Column 中有一個 slotName 屬性,這是為了能夠自定義該列的所需要渲染的內(nèi)容(在 AntDesign 中是通過 TableColumn 組件實現(xiàn)的,這里為了方便直接使用 slotName)。
實現(xiàn)復(fù)制功能
首先我們可以手動選中表格復(fù)制嘗試一下,發(fā)現(xiàn)表格是支持選中復(fù)制的,那么實現(xiàn)思路也就很簡單了,通過代碼選中表格再執(zhí)行復(fù)制命令就可以了,代碼如下:
export const Table = defineComponent({
props: {
// ...
},
setup(props, { slots, expose }) {
// 新增,存儲table節(jié)點
const tableRef = ref<HTMLTableElement | null>(null)
// ...
// 復(fù)制的核心方法
const copy = () => {
if (!tableRef.value) return
const range = document.createRange()
range.selectNode(tableRef.value)
const selection = window.getSelection()
if (!selection) return
if (selection.rangeCount > 0) {
selection.removeAllRanges()
}
selection.addRange(range)
document.execCommand('copy')
}
// 將復(fù)制方法暴露出去以供父組件可以直接調(diào)用
expose({ copy })
return (() => {
return (
// ...
)
}) as unknown as { copy: typeof copy } // 這里是為了讓ts能夠通過類型校驗,否則調(diào)用`copy`方法ts會報錯
}
})這樣復(fù)制功能就完成了,外部是完全不需要關(guān)注如何復(fù)制的,只需要調(diào)用組件暴露出去的 copy 方法即可。
處理表格中的不可復(fù)制元素
雖然復(fù)制功能很簡單,但是這也僅僅是復(fù)制文字,如果表格中有一些不可復(fù)制元素(如圖片),而復(fù)制時需要將這些替換成對應(yīng)的文字符號,這種該如何實現(xiàn)呢?
解決思路就是在組件內(nèi)部定義一個復(fù)制狀態(tài),調(diào)用復(fù)制方法時把狀態(tài)設(shè)置為正在復(fù)制,根據(jù)這個狀態(tài)渲染不同的內(nèi)容(非復(fù)制狀態(tài)時渲染圖片,復(fù)制狀態(tài)是渲染對應(yīng)的文字符號),代碼如下:
export const Table = defineComponent({
props: {
// ...
},
setup(props, { slots, expose }) {
const tableRef = ref<HTMLTableElement | null>(null)
// 新增,定義復(fù)制狀態(tài)
const copying = ref(false)
// ...
const getTdContent = (
text: any,
record: TableRecord,
index: number,
slotName?: string,
slotNameOnCopy?: string
) => {
// 如果處于復(fù)制狀態(tài),則渲染復(fù)制狀態(tài)下的內(nèi)容
if (copying.value && slotNameOnCopy) {
return slots[slotNameOnCopy]?.(text, record, index)
}
if (slotName) {
return slots[slotName]?.(text, record, index)
}
return text
}
const copy = () => {
copying.value = true
// 將復(fù)制行為放到 nextTick 保證復(fù)制到正確的內(nèi)容
nextTick(() => {
if (!tableRef.value) return
const range = document.createRange()
range.selectNode(tableRef.value)
const selection = window.getSelection()
if (!selection) return
if (selection.rangeCount > 0) {
selection.removeAllRanges()
}
selection.addRange(range)
document.execCommand('copy')
// 別忘了把狀態(tài)重置回來
copying.value = false
})
}
expose({ copy })
return (() => {
return (
// ...
)
}) as unknown as { copy: typeof copy }
}
})測試
最后我們可以寫一個demo測一下功能是否正常,代碼如下:
<template>
<button @click="handleCopy">點擊按鈕復(fù)制表格</button>
<c-table
:columns="columns"
:data-source="dataSource"
border="1"
style="margin-top: 10px;"
ref="table"
>
<template #status>
<img class="status-icon" :src="arrowUpIcon" />
</template>
<template #statusOnCopy>
→
</template>
</c-table>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Table as CTable } from '../components'
import arrowUpIcon from '../assets/arrow-up.svg'
const columns = [
{ title: '序號', dataIndex: 'serial' },
{ title: '班級', dataIndex: 'class' },
{ title: '姓名', dataIndex: 'name' },
{ title: '狀態(tài)', dataIndex: 'status', slotName: 'status', slotNameOnCopy: 'statusOnCopy' }
]
const dataSource = [
{ serial: 1, class: '三年級1班', name: '張三' },
{ serial: 2, class: '三年級2班', name: '李四' },
{ serial: 3, class: '三年級3班', name: '王五' },
{ serial: 4, class: '三年級4班', name: '趙六' },
{ serial: 5, class: '三年級5班', name: '宋江' },
{ serial: 6, class: '三年級6班', name: '盧俊義' },
{ serial: 7, class: '三年級7班', name: '吳用' },
{ serial: 8, class: '三年級8班', name: '公孫勝' },
]
const table = ref<InstanceType<typeof CTable> | null>(null)
const handleCopy = () => {
table.value?.copy()
}
</script>
<style scoped>
.status-icon {
width: 20px;
height: 20px;
}
</style>附上完整代碼:
import { defineComponent, ref, nextTick } from 'vue'
import type { PropType } from 'vue'
interface Column {
title: string;
dataIndex: string;
slotName?: string;
slotNameOnCopy?: string;
}
type TableRecord = Record<string, unknown>;
export const Table = defineComponent({
props: {
columns: {
type: Array as PropType<Column[]>,
required: true,
},
dataSource: {
type: Array as PropType<TableRecord[]>,
default: () => [],
},
rowKey: {
type: Function as PropType<(record: TableRecord) => string>,
}
},
setup(props, { slots, expose }) {
const tableRef = ref<HTMLTableElement | null>(null)
const copying = ref(false)
const getRowKey = (record: TableRecord, index: number) => {
if (props.rowKey) {
return props.rowKey(record)
}
return record.id ? String(record.id) : String(index)
}
const getTdContent = (
text: any,
record: TableRecord,
index: number,
slotName?: string,
slotNameOnCopy?: string
) => {
if (copying.value && slotNameOnCopy) {
return slots[slotNameOnCopy]?.(text, record, index)
}
if (slotName) {
return slots[slotName]?.(text, record, index)
}
return text
}
const copy = () => {
copying.value = true
nextTick(() => {
if (!tableRef.value) return
const range = document.createRange()
range.selectNode(tableRef.value)
const selection = window.getSelection()
if (!selection) return
if (selection.rangeCount > 0) {
selection.removeAllRanges()
}
selection.addRange(range)
document.execCommand('copy')
copying.value = false
})
}
expose({ copy })
return (() => {
return (
<table ref={tableRef}>
<tr>
{props.columns.map(column => {
const { title, dataIndex } = column
return <th key={dataIndex}>{title}</th>
})}
</tr>
{props.dataSource.map((record, index) => {
return (
<tr key={getRowKey(record, index)}>
{props.columns.map((column, i) => {
const { dataIndex, slotName, slotNameOnCopy } = column
const text = record[dataIndex]
return (
<td key={dataIndex}>
{getTdContent(text, record, i, slotName, slotNameOnCopy)}
</td>
)
})}
</tr>
)
})}
</table>
)
}) as unknown as { copy: typeof copy }
}
})到此這篇關(guān)于利用Vue3實現(xiàn)可復(fù)制表格的方法詳解的文章就介紹到這了,更多相關(guān)Vue3可復(fù)制表格內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue 路由子組件created和mounted不起作用的解決方法
今天小編就為大家分享一篇vue 路由子組件created和mounted不起作用的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11
詳解vuejs2.0 select 動態(tài)綁定下拉框支持多選
這篇文章主要介紹了vuejs2.0 select動態(tài)綁定下拉框 ,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-04-04
elementui 開始結(jié)束時間可以選擇同一天不同時間段的實現(xiàn)代碼
這篇文章主要介紹了elementui 開始結(jié)束時間可以選擇同一天不同時間段的實現(xiàn)代碼,需要先在main.js中導入相應(yīng)代碼,代碼簡單易懂,需要的朋友可以參考下2024-02-02

