clipboard.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import Clipboard from 'clipboard'
  2. import { decrypt } from '@/utils/crypto'
  3. import { lang } from '@/main'
  4. // 全局隐藏的 input 元素,用于保持页面焦点
  5. let clipboardFocusInput: HTMLInputElement | null = null
  6. // 初始化隐藏的 input 元素以保持焦点
  7. export const initClipboardFocus = () => {
  8. if (clipboardFocusInput) return clipboardFocusInput
  9. clipboardFocusInput = document.createElement('input')
  10. clipboardFocusInput.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0;pointer-events:none;'
  11. // clipboardFocusInput.style.cssText = 'position:fixed;left:-0;top:-0;'
  12. // clipboardFocusInput.setAttribute('readonly', 'readonly')
  13. clipboardFocusInput.setAttribute('tabindex', '-1')
  14. document.querySelector('.viewport-wrapper')?.appendChild(clipboardFocusInput)
  15. // 尝试聚焦
  16. clipboardFocusInput.focus()
  17. readClipboard()
  18. return clipboardFocusInput
  19. }
  20. /**
  21. * 复制文本到剪贴板
  22. * @param text 文本内容
  23. */
  24. export const copyText = (text: string) => {
  25. return new Promise((resolve, reject) => {
  26. const fakeElement = document.createElement('button')
  27. const clipboard = new Clipboard(fakeElement, {
  28. text: () => text,
  29. action: () => 'copy',
  30. container: document.body,
  31. })
  32. clipboard.on('success', e => {
  33. clipboard.destroy()
  34. resolve(e)
  35. })
  36. clipboard.on('error', e => {
  37. clipboard.destroy()
  38. reject(e)
  39. })
  40. document.body.appendChild(fakeElement)
  41. fakeElement.click()
  42. document.body.removeChild(fakeElement)
  43. })
  44. }
  45. // 读取剪贴板
  46. // 读取剪贴板
  47. export const readClipboard = (): Promise<string> => {
  48. return new Promise((resolve, reject) => {
  49. if (navigator.clipboard?.readText) {
  50. navigator.clipboard.readText().then(text => {
  51. if (!text) reject('剪贴板为空或者不包含文本')
  52. return resolve(text)
  53. })
  54. }
  55. else reject('浏览器不支持或禁止访问剪贴板,请使用快捷键 Ctrl + V')
  56. })
  57. }
  58. // 解析加密后的剪贴板内容
  59. export const pasteCustomClipboardString = (text: string) => {
  60. let clipboardData
  61. try {
  62. clipboardData = JSON.parse(decrypt(text))
  63. }
  64. catch {
  65. clipboardData = text
  66. }
  67. return clipboardData
  68. }
  69. // 尝试解析剪贴板内容是否为Excel表格(或类似的)数据格式
  70. export const pasteExcelClipboardString = (text: string): string[][] | null => {
  71. const lines: string[] = text.split('\r\n')
  72. if (lines[lines.length - 1] === '') lines.pop()
  73. let colCount = -1
  74. const data: string[][] = []
  75. for (const index in lines) {
  76. data[index] = lines[index].split('\t')
  77. if (data[index].length === 1) return null
  78. if (colCount === -1) colCount = data[index].length
  79. else if (colCount !== data[index].length) return null
  80. }
  81. return data
  82. }
  83. // 尝试解析剪贴板内容是否为HTML table代码
  84. export const pasteHTMLTableClipboardString = (text: string): string[][] | null => {
  85. const parser = new DOMParser()
  86. const doc = parser.parseFromString(text, 'text/html')
  87. const table = doc.querySelector('table')
  88. const data: string[][] = []
  89. if (!table) return data
  90. const rows = table.querySelectorAll('tr')
  91. for (const row of rows) {
  92. const rowData = []
  93. const cells = row.querySelectorAll('td, th')
  94. for (const cell of cells) {
  95. const text = cell.textContent ? cell.textContent.trim() : ''
  96. const colspan = parseInt(cell.getAttribute('colspan') || '1', 10)
  97. for (let i = 0; i < colspan; i++) {
  98. rowData.push(text)
  99. }
  100. }
  101. data.push(rowData)
  102. }
  103. return data
  104. }