feat: 支持粘贴来自PPT表格的数据

This commit is contained in:
zxc 2025-03-21 21:51:39 +08:00
parent 83e9871c80
commit 7f4400f34d
3 changed files with 82 additions and 34 deletions

View File

@ -68,5 +68,31 @@ export const pasteExcelClipboardString = (text: string): string[][] | null => {
if (colCount === -1) colCount = data[index].length
else if (colCount !== data[index].length) return null
}
return data
}
// 尝试解析剪贴板内容是否为HTML table代码
export const pasteHTMLTableClipboardString = (text: string): string[][] | null => {
const parser = new DOMParser()
const doc = parser.parseFromString(text, 'text/html')
const table = doc.querySelector('table')
const data: string[][] = []
if (!table) return data
const rows = table.querySelectorAll('tr')
for (const row of rows) {
const rowData = []
const cells = row.querySelectorAll('td, th')
for (const cell of cells) {
const text = cell.textContent ? cell.textContent.trim() : ''
const colspan = parseInt(cell.getAttribute('colspan') || '1', 10)
for (let i = 0; i < colspan; i++) {
rowData.push(text)
}
}
data.push(rowData)
}
return data
}

View File

@ -95,7 +95,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
import type { ChartData, ChartType } from '@/types/slides'
import { KEYS } from '@/configs/hotkey'
import { CHART_TYPE_MAP } from '@/configs/chart'
import { pasteCustomClipboardString, pasteExcelClipboardString } from '@/utils/clipboard'
import { pasteCustomClipboardString, pasteExcelClipboardString, pasteHTMLTableClipboardString } from '@/utils/clipboard'
import Button from '@/components/Button.vue'
import Popover from '@/components/Popover.vue'
import PopoverMenuItem from '@/components/PopoverMenuItem.vue'
@ -265,6 +265,18 @@ const clear = () => {
}
}
const fillTableData = (data: string[][], rowIndex: number, colIndex: number) => {
const maxRow = rowIndex + data.length
const maxCol = colIndex + data[0].length
for (let i = rowIndex; i < maxRow; i++) {
for (let j = colIndex; j < maxCol; j++) {
const inputRef = document.querySelector(`#cell-${i}-${j}`) as HTMLInputElement
if (!inputRef) continue
inputRef.value = data[i - rowIndex][j - colIndex]
}
}
}
//
const handlePaste = (e: ClipboardEvent, rowIndex: number, colIndex: number) => {
e.preventDefault()
@ -273,24 +285,22 @@ const handlePaste = (e: ClipboardEvent, rowIndex: number, colIndex: number) => {
const clipboardDataFirstItem = e.clipboardData.items[0]
if (clipboardDataFirstItem && clipboardDataFirstItem.kind === 'string' && clipboardDataFirstItem.type === 'text/plain') {
clipboardDataFirstItem.getAsString(text => {
const clipboardData = pasteCustomClipboardString(text)
if (typeof clipboardData === 'object') return
const excelData = pasteExcelClipboardString(text)
if (excelData) {
const maxRow = rowIndex + excelData.length
const maxCol = colIndex + excelData[0].length
for (let i = rowIndex; i < maxRow; i++) {
for (let j = colIndex; j < maxCol; j++) {
const inputRef = document.querySelector(`#cell-${i}-${j}`) as HTMLInputElement
if (!inputRef) continue
inputRef.value = excelData[i - rowIndex][j - colIndex]
}
}
}
})
if (clipboardDataFirstItem && clipboardDataFirstItem.kind === 'string') {
if (clipboardDataFirstItem.type === 'text/plain') {
clipboardDataFirstItem.getAsString(text => {
const clipboardData = pasteCustomClipboardString(text)
if (typeof clipboardData === 'object') return
const excelData = pasteExcelClipboardString(text)
if (excelData) fillTableData(excelData, rowIndex, colIndex)
})
}
else if (clipboardDataFirstItem.type === 'text/html') {
clipboardDataFirstItem.getAsString(html => {
const htmlData = pasteHTMLTableClipboardString(html)
if (htmlData) fillTableData(htmlData, rowIndex, colIndex)
})
}
}
}

View File

@ -12,7 +12,7 @@
<script lang="ts" setup>
import { onBeforeUnmount, ref, watch } from 'vue'
import { pasteCustomClipboardString, pasteExcelClipboardString } from '@/utils/clipboard'
import { pasteCustomClipboardString, pasteExcelClipboardString, pasteHTMLTableClipboardString } from '@/utils/clipboard'
const props = withDefaults(defineProps<{
value?: string
@ -54,20 +54,32 @@ const handleFocus = () => {
const clipboardDataFirstItem = e.clipboardData.items[0]
if (clipboardDataFirstItem && clipboardDataFirstItem.kind === 'string' && clipboardDataFirstItem.type === 'text/plain') {
clipboardDataFirstItem.getAsString(text => {
const clipboardData = pasteCustomClipboardString(text)
if (typeof clipboardData === 'object') return
const excelData = pasteExcelClipboardString(text)
if (excelData) {
emit('insertExcelData', excelData)
if (textareaRef.value) textareaRef.value.innerHTML = excelData[0][0]
return
}
document.execCommand('insertText', false, text)
})
if (clipboardDataFirstItem && clipboardDataFirstItem.kind === 'string') {
if (clipboardDataFirstItem.type === 'text/plain') {
clipboardDataFirstItem.getAsString(text => {
const clipboardData = pasteCustomClipboardString(text)
if (typeof clipboardData === 'object') return
const excelData = pasteExcelClipboardString(text)
if (excelData) {
emit('insertExcelData', excelData)
if (textareaRef.value) textareaRef.value.innerHTML = excelData[0][0]
return
}
document.execCommand('insertText', false, text)
})
}
else if (clipboardDataFirstItem.type === 'text/html') {
clipboardDataFirstItem.getAsString(html => {
const htmlData = pasteHTMLTableClipboardString(html)
if (htmlData) {
emit('insertExcelData', htmlData)
if (textareaRef.value) textareaRef.value.innerHTML = htmlData[0][0]
return
}
})
}
}
}
}