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

前言

表格是前端非常常用的一个控件,但是每次都使用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的相关知识,提升工作效率。



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

相关内容

热门资讯

上实资本科技基金张曙东:健康、...   12月6日,由中国证券报主办的2025科创投资大会在安徽合肥举行。在“构建双创融合生态促进科创投...
突发!俄罗斯发动大规模袭击,“... 据环球网援引乌克兰“Strana.ua”新闻网等媒体报道,乌克兰能源部6日称,俄罗斯使用无人机和导弹...
转发周知!2026征兵报名正式... 转自:四川在线2026年男兵应征报名通道今天(12月6日)正式开启。登录网址登录全国征兵网(http...
突发!部分业绩不佳基金经理或将...   中国财富  近日,《基金管理公司绩效考核管理指引(征求意见稿)》(以下简称《指引》)已经下发。 ...
一周文商旅速报(12.01—1... 首旅酒店:张淑娟因个人原因辞去副总经理职务11月30日,首旅酒店发布公告称,公司副总经理张淑娟女士因...