使用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月将有两只... 近日,银行正股股价表现强势,多只银行转债触发强制赎回条款。7月1日是杭银转债最后一个交易日,其最后转...
300548,“改名”,股价历... 科技股和顺周期板块再现“跷跷板”走势。今天上午,顺周期板块走强,银行、有色金属、白酒、新能源等板块上...
明阳电路在昆山投资成立集成电路... 人民财讯7月2日电,企查查APP显示,近日,昆山华芯微测集成电路有限公司成立,法定代表人为窦旭才,经...
民生银行“易创E贷”获“202... 在近日举行的第七届数字普惠金融大会上,民生银行“易创E贷”产品凭借在服务科技型中小微企业的创新与实践...
机器人大军逼近,很快,亚马逊的...   炒股就看金麒麟分析师研报,权威,专业,及时,全面,助您挖掘潜力主题机会! 亚马逊正迅速接近仓库...
广东省教育考试院通报:不存在中... 7月2日,广东省教育考试院发布通报称,7月1日下午广东省初中学业水平考试(以下简称“中考”)数学科目...
国际油价承压 中东油企拟放缓全... 财联社7月2日讯(编辑 秦嘉禾)在国际油价下行压力加剧背景下,中东两大国有能源巨头——沙特阿美(Sa...
光伏50ETF、光伏龙头ETF... 光伏设备板块走强,亿晶光电、欧晶科技涨停。银华光伏50ETF、汇添富光伏龙头ETF、浦银安盛光伏龙头...
世界银行发布重磅预测:黄金今年... 错过上半年,别再错过下半年!世界银行继续看好贵金属前景,黄金、白银、铂金或将继续延续强势……世界银行...
洪灝称高分红投资策略应继续有所... 【#洪灝称高分红投资策略应继续有所表现#】中国股市近期一个显著趋势是分红和回购活动显著增加。其中,许...