Files

187 lines
6.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 富文本表格操作与行内样式/属性注入工具
* 注入 border="1"、cellpadding="8"、cellspacing="0" 以及 style 属性
* 保证在 H5、App-plus 原生 editor、小程序以及 rich-text 中均能拥有清晰的表格边框与样式
*/
const TABLE_STYLE = 'border-collapse: collapse; width: 100%; border: 1px solid #dcdfe6; margin: 12px 0; table-layout: auto;'
const TR_STYLE = 'border-bottom: 1px solid #ebeef5;'
const TH_STYLE = 'border: 1px solid #dcdfe6; background-color: #f5f7fa; color: #303133; font-weight: 600; padding: 8px 12px; font-size: 14px; text-align: left; min-width: 44px; word-break: break-word;'
const TD_STYLE = 'border: 1px solid #dcdfe6; background-color: #ffffff; color: #303133; padding: 8px 12px; font-size: 14px; vertical-align: top; word-break: break-word; min-width: 44px;'
/**
* 给 HTML 字符串中的所有 table/tr/th/td 注入高优先级的行内样式与原生 HTML 边框属性
* (双重保障,即使 Quill 或原生组件重置了部分 CSS,border="1" 仍能强制渲染出边框)
*/
export function injectTableInlineStyles(html) {
if (!html || typeof html !== 'string') return ''
let res = html
// 注入 table 属性与样式
res = res.replace(/<table(\s+[^>]*)?>/gi, (match, attrs = '') => {
let cleanAttrs = attrs
cleanAttrs = cleanAttrs.replace(/\s+(border|cellspacing|cellpadding)\s*=\s*["'][^"']*["']/gi, '')
if (/style\s*=\s*["']/i.test(cleanAttrs)) {
cleanAttrs = cleanAttrs.replace(/style\s*=\s*["']([^"']*)["']/i, (m, s) => `style="${s}; ${TABLE_STYLE}"`)
} else {
cleanAttrs = ` style="${TABLE_STYLE}"` + cleanAttrs
}
return `<table border="1" cellpadding="8" cellspacing="0"${cleanAttrs}>`
})
// 注入 tr 样式
res = res.replace(/<tr(\s+[^>]*)?>/gi, (match, attrs = '') => {
if (/style\s*=\s*["']/i.test(attrs)) {
return `<tr${attrs.replace(/style\s*=\s*["']([^"']*)["']/i, (m, s) => `style="${s}; ${TR_STYLE}"`)}>`
}
return `<tr style="${TR_STYLE}"${attrs}>`
})
// 注入 th 属性与样式
res = res.replace(/<th(\s+[^>]*)?>/gi, (match, attrs = '') => {
let cleanAttrs = attrs.replace(/\s+border\s*=\s*["'][^"']*["']/gi, '')
if (/style\s*=\s*["']/i.test(cleanAttrs)) {
cleanAttrs = cleanAttrs.replace(/style\s*=\s*["']([^"']*)["']/i, (m, s) => `style="${s}; ${TH_STYLE}"`)
} else {
cleanAttrs = ` style="${TH_STYLE}"` + cleanAttrs
}
return `<th border="1"${cleanAttrs}>`
})
// 注入 td 属性与样式
res = res.replace(/<td(\s+[^>]*)?>/gi, (match, attrs = '') => {
let cleanAttrs = attrs.replace(/\s+border\s*=\s*["'][^"']*["']/gi, '')
if (/style\s*=\s*["']/i.test(cleanAttrs)) {
cleanAttrs = cleanAttrs.replace(/style\s*=\s*["']([^"']*)["']/i, (m, s) => `style="${s}; ${TD_STYLE}"`)
} else {
cleanAttrs = ` style="${TD_STYLE}"` + cleanAttrs
}
return `<td border="1"${cleanAttrs}>`
})
return res
}
export function createEmptyCell(tagName = 'td') {
const cell = document.createElement(tagName)
cell.innerHTML = '<p><br></p>'
cell.setAttribute('border', '1')
if (tagName === 'th') {
cell.style.cssText = TH_STYLE
} else {
cell.style.cssText = TD_STYLE
}
return cell
}
export function buildTableHtml(rows = 3, cols = 3) {
let html = `<table border="1" cellpadding="8" cellspacing="0" style="${TABLE_STYLE}"><tbody>`
for (let r = 0; r < rows; r += 1) {
html += `<tr style="${TR_STYLE}">`
for (let c = 0; c < cols; c += 1) {
if (r === 0) {
html += `<th border="1" style="${TH_STYLE}"><p><br></p></th>`
} else {
html += `<td border="1" style="${TD_STYLE}"><p><br></p></td>`
}
}
html += '</tr>'
}
html += '</tbody></table><p><br></p>'
return html
}
export function getCurrentCell(editorEl) {
const sel = window.getSelection()
if (!sel || sel.rangeCount === 0) return null
let node = sel.anchorNode
if (node?.nodeType === Node.TEXT_NODE) node = node.parentElement
const cell = node?.closest?.('td, th')
if (!cell || !editorEl?.contains(cell)) return null
return cell
}
export function addRowBefore(cell) {
const row = cell.closest('tr')
const newRow = row.cloneNode(true)
newRow.style.cssText = TR_STYLE
newRow.querySelectorAll('td, th').forEach((item) => {
item.innerHTML = '<p><br></p>'
item.removeAttribute('colspan')
item.removeAttribute('rowspan')
item.setAttribute('border', '1')
item.style.cssText = item.tagName.toLowerCase() === 'th' ? TH_STYLE : TD_STYLE
})
row.before(newRow)
return newRow.cells[cell.cellIndex] || newRow.cells[0]
}
export function addRowAfter(cell) {
const row = cell.closest('tr')
const newRow = row.cloneNode(true)
newRow.style.cssText = TR_STYLE
newRow.querySelectorAll('td, th').forEach((item) => {
item.innerHTML = '<p><br></p>'
item.removeAttribute('colspan')
item.removeAttribute('rowspan')
item.setAttribute('border', '1')
item.style.cssText = item.tagName.toLowerCase() === 'th' ? TH_STYLE : TD_STYLE
})
row.after(newRow)
return newRow.cells[cell.cellIndex] || newRow.cells[0]
}
export function deleteRow(cell) {
const row = cell.closest('tr')
const section = row?.parentElement
if (!row || !section || section.querySelectorAll('tr').length <= 1) return null
const focusCell =
row.nextElementSibling?.cells[cell.cellIndex] ||
row.previousElementSibling?.cells[cell.cellIndex] ||
null
row.remove()
return focusCell
}
export function addColBefore(cell) {
const index = cell.cellIndex
const table = cell.closest('table')
table.querySelectorAll('tr').forEach((row) => {
const refCell = row.cells[index]
if (!refCell) return
refCell.before(createEmptyCell(refCell.tagName.toLowerCase()))
})
return cell.parentElement.cells[index]
}
export function addColAfter(cell) {
const index = cell.cellIndex
const table = cell.closest('table')
table.querySelectorAll('tr').forEach((row) => {
const refCell = row.cells[index]
if (!refCell) return
refCell.after(createEmptyCell(refCell.tagName.toLowerCase()))
})
return cell.parentElement.cells[index + 1]
}
export function deleteCol(cell) {
const index = cell.cellIndex
const table = cell.closest('table')
if (cell.parentElement.cells.length <= 1) return null
let focusCell = null
table.querySelectorAll('tr').forEach((row) => {
const target = row.cells[index]
if (!target) return
if (row === cell.parentElement) {
focusCell = row.cells[index + 1] || row.cells[index - 1] || null
}
target.remove()
})
return focusCell
}
export function deleteTable(cell) {
cell.closest('table')?.remove()
}