Maison  >  Article  >  interface Web  >  Comment implémenter des tables copiables avec Vue3

Comment implémenter des tables copiables avec Vue3

WBOY
WBOYavant
2023-05-11 20:19:121615parcourir

L'encapsulation de table la plus basique

L'encapsulation de table la plus basique permet aux utilisateurs de se concentrer uniquement sur les données de ligne et de colonne, sans prêter attention à la structure DOM. Nous pouvons nous référer à . AntDesign, columns dataSource Ces deux attributs sont essentiels Le code est le suivant : DOM 结构是怎样的,我们可以参考 AntDesigncolumns 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>
      )
    }
  }
})

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

实现复制功能

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

export const Table = defineComponent({
  props: {
      // ...
  },
  setup(props, { slots, expose }) {
    // 新增,存储table节点
    const tableRef = ref<HTMLTableElement | null>(null)

    // ...
    
    // 复制的核心方法
    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(&#39;copy&#39;)
    }
    
    // 将复制方法暴露出去以供父组件可以直接调用
    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<HTMLTableElement | null>(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) 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(&#39;copy&#39;)
        
        // 别忘了把状态重置回来
        copying.value = false
      })
    }
    
    expose({ copy })

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

Ce qui nécessite une attention particulière, c'est ColumnslotName dans /code>, qui doit pouvoir personnaliser le contenu de la colonne qui doit être restituée (dans AntDesign, c'est via TableColumn code > Implémenté par le composant, ici <code>slotName est utilisé directement pour plus de commodité).

Implémentez la fonction de copie

Tout d'abord, nous pouvons sélectionner manuellement la table à copier et l'essayer. Nous avons constaté que la table prend en charge la sélection et la copie, l'idée d'implémentation est donc très simple. puis exécutez la commande de copie. Le code est le suivant :

<template>
  <button @click="handleCopy">点击按钮复制表格</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 &#39;vue&#39;
import { Table as CTable } from &#39;../components&#39;
import arrowUpIcon from &#39;../assets/arrow-up.svg&#39;

const columns = [
  { title: &#39;序号&#39;, dataIndex: &#39;serial&#39; },
  { title: &#39;班级&#39;, dataIndex: &#39;class&#39; },
  { title: &#39;姓名&#39;, dataIndex: &#39;name&#39; },
  { title: &#39;状态&#39;, dataIndex: &#39;status&#39;, slotName: &#39;status&#39;, slotNameOnCopy: &#39;statusOnCopy&#39; }
]

const dataSource = [
  { serial: 1, class: &#39;三年级1班&#39;, name: &#39;张三&#39; },
  { serial: 2, class: &#39;三年级2班&#39;, name: &#39;李四&#39; },
  { serial: 3, class: &#39;三年级3班&#39;, name: &#39;王五&#39; },
  { serial: 4, class: &#39;三年级4班&#39;, name: &#39;赵六&#39; },
  { serial: 5, class: &#39;三年级5班&#39;, name: &#39;宋江&#39; },
  { serial: 6, class: &#39;三年级6班&#39;, name: &#39;卢俊义&#39; },
  { serial: 7, class: &#39;三年级7班&#39;, name: &#39;吴用&#39; },
  { serial: 8, class: &#39;三年级8班&#39;, name: &#39;公孙胜&#39; },
]

const table = ref<InstanceType<typeof CTable> | null>(null)
const handleCopy = () => {
  table.value?.copy()
}
</script>

<style scoped>
.status-icon {
  width: 20px;
  height: 20px;
}
</style>

De cette façon, la fonction de copie est terminée. Il n'est pas nécessaire de faire attention à la façon de copier en externe. Il vous suffit d'appeler le copy<.> méthode exposée par le composant. <h3></h3>Gestion des éléments non copiables dans le tableau<p></p>Bien que la fonction de copie soit très simple, il s'agit uniquement de copier du texte. S'il y a des éléments non copiables (tels que des images) dans le tableau, ceux-ci doivent être remplacés par ceux correspondants. symboles de texte lors de la copie, comment y parvenir ? <p></p>La solution consiste à définir un état de copie à l'intérieur du composant, à définir l'état sur copie lors de l'appel de la méthode de copie et à restituer un contenu différent en fonction de cet état (rendre les images dans l'état de non-copie et restituer les symboles de texte correspondants dans l'état de copie). état de copie), code Comme suit : 🎜<pre class="brush:js;">import { defineComponent, ref, nextTick } from &amp;#39;vue&amp;#39; import type { PropType } from &amp;#39;vue&amp;#39; interface Column { title: string; dataIndex: string; slotName?: string; slotNameOnCopy?: string; } type TableRecord = Record&lt;string, unknown&gt;; export const Table = defineComponent({ props: { columns: { type: Array as PropType&lt;Column[]&gt;, required: true, }, dataSource: { type: Array as PropType&lt;TableRecord[]&gt;, default: () =&gt; [], }, rowKey: { type: Function as PropType&lt;(record: TableRecord) =&gt; string&gt;, } }, setup(props, { slots, expose }) { const tableRef = ref&lt;HTMLTableElement | null&gt;(null) const copying = ref(false) const getRowKey = (record: TableRecord, index: number) =&gt; { 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 ) =&gt; { if (copying.value &amp;&amp; slotNameOnCopy) { return slots[slotNameOnCopy]?.(text, record, index) } if (slotName) { return slots[slotName]?.(text, record, index) } return text } const copy = () =&gt; { copying.value = true nextTick(() =&gt; { if (!tableRef.value) return const range = document.createRange() range.selectNode(tableRef.value) const selection = window.getSelection() if (!selection) return if (selection.rangeCount &gt; 0) { selection.removeAllRanges() } selection.addRange(range) document.execCommand(&amp;#39;copy&amp;#39;) copying.value = false }) } expose({ copy }) return (() =&gt; { return ( &lt;table ref={tableRef}&gt; &lt;tr&gt; {props.columns.map(column =&gt; { const { title, dataIndex } = column return &lt;th key={dataIndex}&gt;{title}&lt;/th&gt; })} &lt;/tr&gt; {props.dataSource.map((record, index) =&gt; { return ( &lt;tr key={getRowKey(record, index)}&gt; {props.columns.map((column, i) =&gt; { const { dataIndex, slotName, slotNameOnCopy } = column const text = record[dataIndex] return ( &lt;td key={dataIndex}&gt; {getTdContent(text, record, i, slotName, slotNameOnCopy)} &lt;/td&gt; ) })} &lt;/tr&gt; ) })} &lt;/table&gt; ) }) as unknown as { copy: typeof copy } } })</pre>🎜Test🎜🎜Enfin nous pouvons écrire une démo pour tester si la fonction est normale Le code est le suivant : 🎜rrreee🎜Ci-joint le code complet : 🎜rrreee.</.>

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer