溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何用Vue3實(shí)現(xiàn)可復(fù)制表格

發(fā)布時(shí)間:2022-12-13 09:20:18 來(lái)源:億速云 閱讀:120 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇“如何用Vue3實(shí)現(xiàn)可復(fù)制表格”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“如何用Vue3實(shí)現(xiàn)可復(fù)制表格”文章吧。

最基礎(chǔ)的表格封裝

最基礎(chǔ)基礎(chǔ)的表格封裝所要做的事情就是讓用戶只關(guān)注行和列的數(shù)據(jù),而不需要關(guān)注 DOM 結(jié)構(gòu)是怎樣的,我們可以參考 AntDesigncolumns dataSource 這兩個(gè)屬性是必不可少的,代碼如下:

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 中有一個(gè) slotName 屬性,這是為了能夠自定義該列的所需要渲染的內(nèi)容(在 AntDesign 中是通過(guò) TableColumn 組件實(shí)現(xiàn)的,這里為了方便直接使用 slotName)。

實(shí)現(xiàn)復(fù)制功能

首先我們可以手動(dòng)選中表格復(fù)制嘗試一下,發(fā)現(xiàn)表格是支持選中復(fù)制的,那么實(shí)現(xiàn)思路也就很簡(jiǎn)單了,通過(guò)代碼選中表格再執(zhí)行復(fù)制命令就可以了,代碼如下:

export const Table = defineComponent({
  props: {
      // ...
  },
  setup(props, { slots, expose }) {
    // 新增,存儲(chǔ)table節(jié)點(diǎn)
    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能夠通過(guò)類型校驗(yàn),否則調(diào)用`copy`方法ts會(huì)報(bào)錯(cuò)
  }
})

這樣復(fù)制功能就完成了,外部是完全不需要關(guān)注如何復(fù)制的,只需要調(diào)用組件暴露出去的 copy 方法即可。

處理表格中的不可復(fù)制元素

雖然復(fù)制功能很簡(jiǎn)單,但是這也僅僅是復(fù)制文字,如果表格中有一些不可復(fù)制元素(如圖片),而復(fù)制時(shí)需要將這些替換成對(duì)應(yīng)的文字符號(hào),這種該如何實(shí)現(xiàn)呢?

解決思路就是在組件內(nèi)部定義一個(gè)復(fù)制狀態(tài),調(diào)用復(fù)制方法時(shí)把狀態(tài)設(shè)置為正在復(fù)制,根據(jù)這個(gè)狀態(tài)渲染不同的內(nèi)容(非復(fù)制狀態(tài)時(shí)渲染圖片,復(fù)制狀態(tài)是渲染對(duì)應(yīng)的文字符號(hào)),代碼如下:

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)重置回來(lái)
        copying.value = false
      })
    }
    
    expose({ copy })

    return (() => {
      return (
        // ...
      )
    }) as unknown as { copy: typeof copy }
  }
})

測(cè)試

最后我們可以寫(xiě)一個(gè)demo測(cè)一下功能是否正常,代碼如下:

<template>
  <button @click="handleCopy">點(diǎn)擊按鈕復(fù)制表格</button>
  <c-table
    :columns="columns"
    :data-source="dataSource"
    border="1"
    
    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: '序號(hào)', dataIndex: 'serial' },
  { title: '班級(jí)', dataIndex: 'class' },
  { title: '姓名', dataIndex: 'name' },
  { title: '狀態(tài)', dataIndex: 'status', slotName: 'status', slotNameOnCopy: 'statusOnCopy' }
]

const dataSource = [
  { serial: 1, class: '三年級(jí)1班', name: '張三' },
  { serial: 2, class: '三年級(jí)2班', name: '李四' },
  { serial: 3, class: '三年級(jí)3班', name: '王五' },
  { serial: 4, class: '三年級(jí)4班', name: '趙六' },
  { serial: 5, class: '三年級(jí)5班', name: '宋江' },
  { serial: 6, class: '三年級(jí)6班', name: '盧俊義' },
  { serial: 7, class: '三年級(jí)7班', name: '吳用' },
  { serial: 8, class: '三年級(jí)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實(shí)現(xiàn)可復(fù)制表格”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI