使用Vue3实现一个可复制的表格
创始人
2024-05-26 08:53:54
0

前言

表格是前端非常常用的一个控件,但是每次都使用v-for指令手动绘制tr/th/td这些元素是非常麻烦的。同时,基础的 table 样式通常也是不满足需求的,因此一个好的表格封装就显得比较重要了。

最基础的表格封装

最基础基础的表格封装所要做的事情就是让用户只关注行和列的数据,而不需要关注 DOM 结构是怎样的,我们可以参考 AntDesigncolumns dataSource 这两个属性是必不可少的,代码如下:

import { defineComponent } from 'vue'
import type { PropType } from 'vue'interface Column {title: string;dataIndex: string;slotName?: string;
}
type TableRecord = Record;export const Table = defineComponent({props: {columns: {type: Array as PropType,required: true,},dataSource: {type: Array as PropType,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 ({props.columns.map(column => {const { title, dataIndex } = columnreturn })}{props.dataSource.map((record, index) => {return ({props.columns.map((column, i) => {const { dataIndex, slotName } = columnconst text = record[dataIndex]return ()})})})}
{title}
{getTdContent(text, record, i, slotName)}
)}} })

需要关注一下的是 Column 中有一个 slotName 属性,这是为了能够自定义该列的所需要渲染的内容(在 AntDesign 中是通过 TableColumn 组件实现的,这里为了方便直接使用 slotName)。

实现复制功能

首先我们可以手动选中表格复制尝试一下,发现表格是支持选中复制的,那么实现思路也就很简单了,通过代码选中表格再执行复制命令就可以了,代码如下:

export const Table = defineComponent({props: {// ...},setup(props, { slots, expose }) {// 新增,存储table节点const tableRef = ref(null)// ...// 复制的核心方法const copy = () => {if (!tableRef.value) returnconst range = document.createRange()range.selectNode(tableRef.value)const selection = window.getSelection()if (!selection) returnif (selection.rangeCount > 0) {selection.removeAllRanges()}selection.addRange(range)document.execCommand('copy')}// 将复制方法暴露出去以供父组件可以直接调用expose({ copy })return (() => {return (// ...)}) as unknown as { copy: typeof copy } // 这里是为了让ts能够通过类型校验,否则调用`copy`方法ts会报错}
}) 

这样复制功能就完成了,外部是完全不需要关注如何复制的,只需要调用组件暴露出去的 copy 方法即可。

处理表格中的不可复制元素

虽然复制功能很简单,但是这也仅仅是复制文字,如果表格中有一些不可复制元素(如图片),而复制时需要将这些替换成对应的文字符号,这种该如何实现呢?

解决思路就是在组件内部定义一个复制状态,调用复制方法时把状态设置为正在复制,根据这个状态渲染不同的内容(非复制状态时渲染图片,复制状态是渲染对应的文字符号),代码如下:

export const Table = defineComponent({props: {// ...},setup(props, { slots, expose }) {const tableRef = ref(null)// 新增,定义复制状态const copying = ref(false)// ...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 保证复制到正确的内容nextTick(() => {if (!tableRef.value) returnconst range = document.createRange()range.selectNode(tableRef.value)const selection = window.getSelection()if (!selection) returnif (selection.rangeCount > 0) {selection.removeAllRanges()}selection.addRange(range)document.execCommand('copy')// 别忘了把状态重置回来copying.value = false})}expose({ copy })return (() => {return (// ...)}) as unknown as { copy: typeof copy }}
}) 

测试

最后我们可以写一个demo测一下功能是否正常,代码如下:

 

附上完整代码:

import { defineComponent, ref, nextTick } from 'vue'
import type { PropType } from 'vue'interface Column {title: string;dataIndex: string;slotName?: string;slotNameOnCopy?: string;
}
type TableRecord = Record;export const Table = defineComponent({props: {columns: {type: Array as PropType,required: true,},dataSource: {type: Array as PropType,default: () => [],},rowKey: {type: Function as PropType<(record: TableRecord) => string>,}},setup(props, { slots, expose }) {const tableRef = ref(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 = truenextTick(() => {if (!tableRef.value) returnconst range = document.createRange()range.selectNode(tableRef.value)const selection = window.getSelection()if (!selection) returnif (selection.rangeCount > 0) {selection.removeAllRanges()}selection.addRange(range)document.execCommand('copy')copying.value = false})}expose({ copy })return (() => {return ({props.columns.map(column => {const { title, dataIndex } = columnreturn })}{props.dataSource.map((record, index) => {return ({props.columns.map((column, i) => {const { dataIndex, slotName, slotNameOnCopy } = columnconst text = record[dataIndex]return ()})})})}
{title}
{getTdContent(text, record, i, slotName, slotNameOnCopy)}
)}) as unknown as { copy: typeof copy }} })

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

相关内容

热门资讯

你有哪些关于雨夜的故事? 你有哪些关于雨夜的故事?我记得那时还是在上中学的时候。那天晚上忽然间下了很大的雨。是很大的暴雨那一种...
找一本穿越滴小说 女主可以灵魂... 找一本穿越滴小说 女主可以灵魂出体 貌似有灵力,很强 原本是代嫁为妾 ,被指成主母,对男主的妾又煮又...
想写恐怖小说,该去哪里呢 想写恐怖小说,该去哪里呢看鬼片去鬼城
女孩子取什么名字好听又有内涵 女孩子取什么名字好听又有内涵婉静:温顺娴静。适用于女孩取名字。出自《后汉书•皇后纪上•明德马皇后》:...
云南畹町口岸迎来缅甸芒果进口高...   中新网昆明7月1日电 (赵吕媛)连日来,在位于中缅边境的云南畹町口岸,一辆辆装载着缅甸圣德龙芒果...
投教精品 | 一图看懂《上海证... (转自:南京证券阳光微讯)
喝大酒,吹大牛,大忽悠猜三个数... 喝大酒,吹大牛,大忽悠猜三个数字这三个数字是960。喝大酒,是酒的谐音9;吹大牛是牛的谐音6;大忽悠...
康辰药业跌2.08%,成交额1... 7月2日,康辰药业盘中下跌2.08%,截至10:11,报32.02元/股,成交1789.43万元,换...
受贿数额特别巨大!中国联通原副... 转自:扬子晚报中国联合网络通信集团有限公司原党组成员、副总经理曹兴信涉嫌受贿一案,由国家监察委员会调...
北仲沙龙 | 破局类案裁判差异... 转自:网络2025年6月27日下午,由北京仲裁委员会/北京国际仲裁院(以下称“北仲”)主办的2025...