修复h5记事本不显示问题

This commit is contained in:
2026-08-18 09:25:51 +08:00
parent aed71d237d
commit 0e797c39f4
11 changed files with 911 additions and 452 deletions
+461 -333
View File
@@ -1,64 +1,104 @@
<template>
<!-- #ifdef H5 -->
<div class="note-editor-h5">
<div class="editor-toolbar">
<div class="toolbar-group">
<button type="button" class="tb-btn" title="加粗" @mousedown.prevent="execFormat('bold')">
<FaIcon name="bold" :size="14" color="#606266" />
</button>
<button type="button" class="tb-btn" title="斜体" @mousedown.prevent="execFormat('italic')">
<FaIcon name="italic" :size="14" color="#606266" />
</button>
<button type="button" class="tb-btn" title="下划线" @mousedown.prevent="execFormat('underline')">
<FaIcon name="underline" :size="14" color="#606266" />
</button>
<button type="button" class="tb-btn" title="左对齐" @mousedown.prevent="execFormat('justifyLeft')">
<FaIcon name="align-left" :size="14" color="#606266" />
</button>
<button type="button" class="tb-btn" title="居中对齐" @mousedown.prevent="execFormat('justifyCenter')">
<FaIcon name="align-center" :size="14" color="#606266" />
</button>
<button type="button" class="tb-btn" title="右对齐" @mousedown.prevent="execFormat('justifyRight')">
<FaIcon name="align-right" :size="14" color="#606266" />
</button>
</div>
<div class="toolbar-divider" />
<div class="toolbar-group">
<button type="button" class="tb-btn" title="插入表格" @mousedown.prevent="onInsertTable">
<FaIcon name="table" :size="14" color="#606266" />
</button>
<button type="button" class="tb-btn" title="上方插入行" @mousedown.prevent="onAddRowBefore">
<FaIcon name="arrow-up" :size="12" color="#606266" />
<span class="tb-label">行</span>
</button>
<button type="button" class="tb-btn" title="下方插入行" @mousedown.prevent="onAddRowAfter">
<FaIcon name="arrow-down" :size="12" color="#606266" />
<span class="tb-label">行</span>
</button>
<button type="button" class="tb-btn" title="删除行" @mousedown.prevent="onDeleteRow">
<FaIcon name="minus" :size="12" color="#606266" />
<span class="tb-label">行</span>
</button>
</div>
<div class="toolbar-divider" />
<div class="toolbar-group">
<button type="button" class="tb-btn" title="左侧插入列" @mousedown.prevent="onAddColBefore">
<FaIcon name="arrow-left" :size="12" color="#606266" />
<span class="tb-label">列</span>
</button>
<button type="button" class="tb-btn" title="右侧插入列" @mousedown.prevent="onAddColAfter">
<FaIcon name="arrow-right" :size="12" color="#606266" />
<span class="tb-label">列</span>
</button>
<button type="button" class="tb-btn" title="删除列" @mousedown.prevent="onDeleteCol">
<FaIcon name="minus" :size="12" color="#606266" />
<span class="tb-label">列</span>
</button>
<button type="button" class="tb-btn tb-btn-danger" title="删除表格" @mousedown.prevent="onDeleteTable">
<FaIcon name="trash-can" :size="14" color="#f56c6c" />
</button>
</div>
</div>
<view class="note-editor-wrapper">
<!-- 通用富文本工具栏(H5 与 Android/iOS App 手机端均完整支持) -->
<view class="editor-toolbar">
<view class="toolbar-group">
<view class="tb-btn" title="加粗" @tap="handleFormat('bold')">
<FaIcon name="bold" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="斜体" @tap="handleFormat('italic')">
<FaIcon name="italic" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="下划线" @tap="handleFormat('underline')">
<FaIcon name="underline" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="删除线" @tap="handleFormat('strikeThrough')">
<FaIcon name="strikethrough" :size="13" color="#606266" />
</view>
</view>
<view class="toolbar-divider" />
<view class="toolbar-group">
<view class="tb-btn" title="左对齐" @tap="handleFormat('justifyLeft')">
<FaIcon name="align-left" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="居中" @tap="handleFormat('justifyCenter')">
<FaIcon name="align-center" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="右对齐" @tap="handleFormat('justifyRight')">
<FaIcon name="align-right" :size="13" color="#606266" />
</view>
</view>
<view class="toolbar-divider" />
<view class="toolbar-group">
<view class="tb-btn" title="大标题" @tap="handleFormat('formatBlock', '<h2>')">
<FaIcon name="heading" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="无序列表" @tap="handleFormat('insertUnorderedList')">
<FaIcon name="list-ul" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="有序列表" @tap="handleFormat('insertOrderedList')">
<FaIcon name="list-ol" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="插入分割线" @tap="handleInsertHtml('<hr/>')">
<FaIcon name="minus" :size="13" color="#606266" />
</view>
</view>
<!-- 表格操作组(全端支持插入与行列增删) -->
<view class="toolbar-divider" />
<view class="toolbar-group">
<view class="tb-btn" title="插入表格" @tap="onInsertTable">
<FaIcon name="table" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="上方插入行" @tap="handleTableOp('addRowBefore')">
<FaIcon name="arrow-up" :size="11" color="#606266" />
<text class="tb-label">行</text>
</view>
<view class="tb-btn" title="下方插入行" @tap="handleTableOp('addRowAfter')">
<FaIcon name="arrow-down" :size="11" color="#606266" />
<text class="tb-label">行</text>
</view>
<view class="tb-btn" title="删除行" @tap="handleTableOp('deleteRow')">
<FaIcon name="minus" :size="11" color="#606266" />
<text class="tb-label">行</text>
</view>
<view class="tb-btn" title="左侧插入列" @tap="handleTableOp('addColBefore')">
<FaIcon name="arrow-left" :size="11" color="#606266" />
<text class="tb-label">列</text>
</view>
<view class="tb-btn" title="右侧插入列" @tap="handleTableOp('addColAfter')">
<FaIcon name="arrow-right" :size="11" color="#606266" />
<text class="tb-label">列</text>
</view>
<view class="tb-btn" title="删除列" @tap="handleTableOp('deleteCol')">
<FaIcon name="minus" :size="11" color="#606266" />
<text class="tb-label">列</text>
</view>
<view class="tb-btn tb-btn-danger" title="删除表格" @tap="handleTableOp('deleteTable')">
<FaIcon name="trash-can" :size="13" color="#f56c6c" />
</view>
</view>
<view class="toolbar-divider" />
<view class="toolbar-group">
<view class="tb-btn" title="撤销" @tap="handleFormat('undo')">
<FaIcon name="rotate-left" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="重做" @tap="handleFormat('redo')">
<FaIcon name="rotate-right" :size="13" color="#606266" />
</view>
<view class="tb-btn" title="清除格式" @tap="handleFormat('removeFormat')">
<FaIcon name="eraser" :size="13" color="#606266" />
</view>
</view>
</view>
<!-- 编辑区(H5 与 App-plus 均使用真正的 HTML5 Webview DOM 渲染与编辑表格) -->
<!-- #ifdef H5 -->
<div
ref="h5EditorRef"
class="note-rich-editor rich-content"
@@ -68,31 +108,35 @@
@input="onH5Input"
@blur="onH5Input"
/>
</div>
<!-- #endif -->
<!-- #ifndef H5 -->
<editor
:id="editorId"
class="note-rich-editor"
:placeholder="placeholder"
@ready="onEditorReady"
@input="onEditorInput"
/>
<!-- #endif -->
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view
id="richEditorDiv"
class="note-rich-editor rich-content"
:prop="modelValue"
:action="renderAction"
:change:prop="renderEditor.onPropChange"
:change:action="renderEditor.onActionChange"
/>
<!-- #endif -->
</view>
</template>
<script setup>
import { ref, watch, nextTick, getCurrentInstance, onMounted } from 'vue'
import { ref, watch, nextTick, onMounted } from 'vue'
import FaIcon from '@/components/FaIcon.vue'
import {
buildTableHtml,
injectTableInlineStyles,
getCurrentCell,
addRowBefore,
addRowAfter,
deleteRow,
addColBefore,
addColAfter,
deleteCol
deleteCol,
deleteTable
} from '@/utils/table-editor.js'
const props = defineProps({
@@ -102,112 +146,110 @@ const props = defineProps({
},
placeholder: {
type: String,
default: '请输入内容'
default: '记录你的想法...'
}
})
const emit = defineEmits(['update:modelValue'])
const editorId = 'noteRichEditor'
const h5EditorRef = ref(null)
const editorCtx = ref(null)
const instance = getCurrentInstance()
const isInternalUpdate = ref(false)
const lastAppliedHtml = ref('')
const renderAction = ref(null)
function normalizeEditorHtml(html) {
if (!html) return ''
const template = document.createElement('template')
template.innerHTML = html.trim()
const root = template.content
root.querySelectorAll('[contenteditable]').forEach((node) => {
node.removeAttribute('contenteditable')
// 响应 RenderJS 传递上来的内容更新
function onContentChange(html) {
lastAppliedHtml.value = html
isInternalUpdate.value = true
emit('update:modelValue', html)
nextTick(() => {
isInternalUpdate.value = false
})
}
root.querySelectorAll('.column-resize-handle, .selectedCell, .grip-column, .grip-row').forEach((node) => {
node.remove()
})
// 供 template 绑定的 Renderjs 桥接方法
function onRenderChange(html) {
onContentChange(html)
}
root.querySelectorAll('td, th').forEach((cell) => {
const text = cell.textContent || ''
if (!text.trim() && !cell.querySelector('img, br')) {
cell.innerHTML = '<p><br></p>'
function handleFormat(cmd, value = null) {
// #ifdef H5
execH5Format(cmd, value)
// #endif
// #ifdef APP-PLUS
renderAction.value = {
type: 'format',
cmd,
value,
t: Date.now()
}
// #endif
}
function handleInsertHtml(html) {
// #ifdef H5
insertH5Html(html)
// #endif
// #ifdef APP-PLUS
renderAction.value = {
type: 'insertHtml',
html: injectTableInlineStyles(html),
t: Date.now()
}
// #endif
}
function handleTableOp(opName) {
// #ifdef H5
execH5TableOp(opName)
// #endif
// #ifdef APP-PLUS
renderAction.value = {
type: 'tableOp',
op: opName,
t: Date.now()
}
// #endif
}
function onInsertTable() {
uni.showActionSheet({
itemList: ['3×3 表格', '4×4 表格', '5×3 表格'],
success: (res) => {
const sizes = [[3, 3], [4, 4], [5, 3]]
const [rows, cols] = sizes[res.tapIndex] || [3, 3]
const tableHtml = buildTableHtml(rows, cols)
handleInsertHtml(tableHtml)
}
})
return template.innerHTML
}
function unlockEditableTree(root) {
if (!root) return
root.querySelectorAll('[contenteditable="false"]').forEach((node) => {
node.removeAttribute('contenteditable')
})
}
function patchTables(root) {
if (!root) return
root.querySelectorAll('table').forEach((table) => {
table.removeAttribute('contenteditable')
table.style.borderCollapse = 'collapse'
table.style.border = '1px solid #e4e7ed'
table.style.width = '100%'
table.style.margin = '12px 0'
table.querySelectorAll('th, td').forEach((cell) => {
cell.removeAttribute('contenteditable')
cell.style.border = '1px solid #e4e7ed'
cell.style.padding = '8px 12px'
cell.style.verticalAlign = 'top'
cell.style.wordBreak = 'break-word'
cell.style.userSelect = 'text'
cell.style.cursor = 'text'
})
table.querySelectorAll('th').forEach((cell) => {
cell.style.backgroundColor = '#f5f7fa'
cell.style.fontWeight = '600'
})
})
}
function placeCaretInCell(cell, event) {
const el = h5EditorRef.value
if (!el || !cell) return
el.focus()
let range = null
if (event && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(event.clientX, event.clientY)
} else if (event && document.caretPositionFromPoint) {
const pos = document.caretPositionFromPoint(event.clientX, event.clientY)
if (pos) {
range = document.createRange()
range.setStart(pos.offsetNode, pos.offset)
range.collapse(true)
}
}
if (range && cell.contains(range.startContainer)) {
const sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
return
}
const fallback = document.createRange()
const target = cell.querySelector('p, div, span') || cell
fallback.selectNodeContents(target)
fallback.collapse(false)
const sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(fallback)
}
function syncContent() {
// ================= H5 专属 DOM 操作 =================
function execH5Format(cmd, value = null) {
const el = h5EditorRef.value
if (!el) return
unlockEditableTree(el)
patchTables(el)
el.focus()
document.execCommand(cmd, false, value)
syncH5Content()
}
function insertH5Html(html) {
const el = h5EditorRef.value
if (!el) return
el.focus()
const formatted = injectTableInlineStyles(html)
if (document.queryCommandSupported('insertHTML')) {
document.execCommand('insertHTML', false, formatted)
} else {
el.insertAdjacentHTML('beforeend', formatted)
}
syncH5Content()
}
function syncH5Content() {
const el = h5EditorRef.value
if (!el) return
patchH5Tables(el)
const html = el.innerHTML || ''
lastAppliedHtml.value = html
isInternalUpdate.value = true
@@ -217,114 +259,48 @@ function syncContent() {
})
}
function insertHtmlAtCursor(html) {
const el = h5EditorRef.value
if (!el) return
el.focus()
if (document.queryCommandSupported('insertHTML')) {
document.execCommand('insertHTML', false, html)
} else {
el.insertAdjacentHTML('beforeend', html)
}
syncContent()
}
function execFormat(cmd) {
const el = h5EditorRef.value
if (!el) return
el.focus()
document.execCommand(cmd, false, null)
syncContent()
}
function requireCell(message = '请先在表格中选中单元格') {
function execH5TableOp(opName) {
const cell = getCurrentCell(h5EditorRef.value)
if (!cell) {
uni.showToast({ title: message, icon: 'none' })
}
return cell
}
function onInsertTable() {
uni.showActionSheet({
itemList: ['3×3 表格', '4×4 表格', '5×3 表格'],
success: (res) => {
const sizes = [[3, 3], [4, 4], [5, 3]]
const [rows, cols] = sizes[res.tapIndex] || [3, 3]
insertHtmlAtCursor(buildTableHtml(rows, cols))
}
})
}
function onAddRowBefore() {
const cell = requireCell()
if (!cell) return
const target = addRowBefore(cell)
syncContent()
if (target) placeCaretInCell(target)
}
function onAddRowAfter() {
const cell = requireCell()
if (!cell) return
const target = addRowAfter(cell)
syncContent()
if (target) placeCaretInCell(target)
}
function onDeleteRow() {
const cell = requireCell()
if (!cell) return
const target = deleteRow(cell)
if (!target) {
uni.showToast({ title: '至少保留一行', icon: 'none' })
uni.showToast({ title: '请先在表格单元格中点击光标', icon: 'none' })
return
}
syncContent()
placeCaretInCell(target)
}
function onAddColBefore() {
const cell = requireCell()
if (!cell) return
const target = addColBefore(cell)
syncContent()
if (target) placeCaretInCell(target)
}
function onAddColAfter() {
const cell = requireCell()
if (!cell) return
const target = addColAfter(cell)
syncContent()
if (target) placeCaretInCell(target)
}
function onDeleteCol() {
const cell = requireCell()
if (!cell) return
const target = deleteCol(cell)
if (!target) {
uni.showToast({ title: '至少保留一列', icon: 'none' })
let target = null
if (opName === 'addRowBefore') target = addRowBefore(cell)
else if (opName === 'addRowAfter') target = addRowAfter(cell)
else if (opName === 'deleteRow') target = deleteRow(cell)
else if (opName === 'addColBefore') target = addColBefore(cell)
else if (opName === 'addColAfter') target = addColAfter(cell)
else if (opName === 'deleteCol') target = deleteCol(cell)
else if (opName === 'deleteTable') {
deleteTable(cell)
syncH5Content()
return
}
syncContent()
placeCaretInCell(target)
syncH5Content()
if (target && target.focus) target.focus()
}
function onDeleteTable() {
const cell = requireCell()
if (!cell) return
const table = cell.closest('table')
if (!table) return
uni.showModal({
title: '删除表格',
content: '确定删除当前表格吗?',
success: (res) => {
if (!res.confirm) return
table.remove()
syncContent()
}
function patchH5Tables(root) {
if (!root) return
root.querySelectorAll('table').forEach((table) => {
table.style.borderCollapse = 'collapse'
table.style.border = '1px solid #dcdfe6'
table.style.width = '100%'
table.style.margin = '12px 0'
table.setAttribute('border', '1')
table.querySelectorAll('th, td').forEach((cell) => {
cell.style.border = '1px solid #dcdfe6'
cell.style.padding = '8px 12px'
cell.style.verticalAlign = 'top'
cell.style.wordBreak = 'break-word'
cell.setAttribute('border', '1')
})
table.querySelectorAll('th').forEach((cell) => {
cell.style.backgroundColor = '#f5f7fa'
cell.style.fontWeight = '600'
})
})
}
@@ -333,11 +309,18 @@ function onH5MouseDown(e) {
if (!el) return
const cell = e.target.closest('td, th')
if (!cell || !el.contains(cell)) return
unlockEditableTree(el)
const evt = e
requestAnimationFrame(() => {
placeCaretInCell(cell, evt)
el.focus()
})
}
function onH5Input(e) {
const html = e.target.innerHTML || ''
lastAppliedHtml.value = html
isInternalUpdate.value = true
emit('update:modelValue', html)
nextTick(() => {
isInternalUpdate.value = false
})
}
@@ -346,54 +329,20 @@ function setH5Html(html) {
nextTick(() => {
const el = h5EditorRef.value
if (!el) return
const normalized = normalizeEditorHtml(html || '')
if (normalized === lastAppliedHtml.value && el.innerHTML === normalized) {
patchTables(el)
const formatted = injectTableInlineStyles(html || '')
if (formatted === lastAppliedHtml.value && el.innerHTML === formatted) {
patchH5Tables(el)
return
}
if (el.innerHTML !== normalized) {
el.innerHTML = normalized
if (el.innerHTML !== formatted) {
el.innerHTML = formatted
}
unlockEditableTree(el)
patchTables(el)
lastAppliedHtml.value = normalized
patchH5Tables(el)
lastAppliedHtml.value = formatted
})
// #endif
}
function onH5Input(e) {
// #ifdef H5
const html = e.target.innerHTML || ''
lastAppliedHtml.value = html
isInternalUpdate.value = true
emit('update:modelValue', html)
nextTick(() => {
isInternalUpdate.value = false
})
// #endif
}
function onEditorInput(e) {
emit('update:modelValue', e.detail.html || '')
}
function onEditorReady() {
// #ifndef H5
uni.createSelectorQuery()
.in(instance)
.select(`#${editorId}`)
.context((res) => {
if (res && res.context) {
editorCtx.value = res.context
if (props.modelValue) {
editorCtx.value.setContents({ html: props.modelValue })
}
}
})
.exec()
// #endif
}
watch(
() => props.modelValue,
(val) => {
@@ -401,11 +350,6 @@ watch(
// #ifdef H5
setH5Html(val)
// #endif
// #ifndef H5
if (editorCtx.value) {
editorCtx.value.setContents({ html: val || '' })
}
// #endif
}
)
@@ -414,60 +358,211 @@ onMounted(() => {
setH5Html(props.modelValue)
// #endif
})
// 将 onRenderChange 挂载到组件实例供 Renderjs 调用
defineExpose({
onRenderChange
})
</script>
<!-- #ifdef APP-PLUS -->
<script module="renderEditor" lang="renderjs">
export default {
mounted() {
this.initRenderEditor()
},
methods: {
initRenderEditor() {
const el = document.getElementById('richEditorDiv')
if (!el) {
setTimeout(() => this.initRenderEditor(), 50)
return
}
el.contentEditable = 'true'
el.setAttribute('data-placeholder', '记录你的想法...')
el.style.outline = 'none'
el.style.minHeight = '320rpx'
el.style.userSelect = 'text'
el.style.webkitUserSelect = 'text'
if (this.prop) {
el.innerHTML = this.formatHtml(this.prop)
}
el.addEventListener('input', () => {
this.notifyOwner(el.innerHTML || '')
})
el.addEventListener('blur', () => {
this.notifyOwner(el.innerHTML || '')
})
},
formatHtml(html) {
if (!html) return ''
return html
.replace(/<table(\s+[^>]*)?>/gi, '<table border="1" cellpadding="8" cellspacing="0" style="border-collapse:collapse;width:100%;border:1px solid #dcdfe6;margin:12px 0;"$1>')
.replace(/<th(\s+[^>]*)?>/gi, '<th border="1" style="border:1px solid #dcdfe6;background-color:#f5f7fa;font-weight:600;padding:8px 12px;color:#303133;"$1>')
.replace(/<td(\s+[^>]*)?>/gi, '<td border="1" style="border:1px solid #dcdfe6;background-color:#ffffff;padding:8px 12px;color:#303133;"$1>')
},
onPropChange(newVal) {
const el = document.getElementById('richEditorDiv')
if (!el) return
const formatted = this.formatHtml(newVal || '')
if (formatted !== el.innerHTML) {
el.innerHTML = formatted
}
},
onActionChange(act) {
if (!act || !act.type) return
const el = document.getElementById('richEditorDiv')
if (!el) return
el.focus()
if (act.type === 'format') {
document.execCommand(act.cmd, false, act.value || null)
this.notifyOwner(el.innerHTML || '')
} else if (act.type === 'insertHtml') {
const formatted = this.formatHtml(act.html || '')
if (document.queryCommandSupported('insertHTML')) {
document.execCommand('insertHTML', false, formatted)
} else {
el.insertAdjacentHTML('beforeend', formatted)
}
this.notifyOwner(el.innerHTML || '')
} else if (act.type === 'tableOp') {
this.handleRenderTableOp(act.op)
}
},
handleRenderTableOp(op) {
const el = document.getElementById('richEditorDiv')
if (!el) return
const sel = window.getSelection()
if (!sel || sel.rangeCount === 0) return
let node = sel.anchorNode
if (node && node.nodeType === 3) node = node.parentElement
const cell = node ? node.closest('td, th') : null
if (!cell || !el.contains(cell)) return
if (op === 'addRowBefore' || op === 'addRowAfter') {
const row = cell.closest('tr')
if (!row) return
const newRow = row.cloneNode(true)
newRow.querySelectorAll('td, th').forEach(c => {
c.innerHTML = '<p><br></p>'
c.style.border = '1px solid #dcdfe6'
c.style.padding = '8px 12px'
c.setAttribute('border', '1')
})
if (op === 'addRowBefore') row.before(newRow)
else row.after(newRow)
} else if (op === 'deleteRow') {
const row = cell.closest('tr')
const section = row ? row.parentElement : null
if (row && section && section.querySelectorAll('tr').length > 1) {
row.remove()
}
} else if (op === 'addColBefore' || op === 'addColAfter') {
const idx = cell.cellIndex
const table = cell.closest('table')
if (!table) return
table.querySelectorAll('tr').forEach(r => {
const refCell = r.cells[idx]
if (!refCell) return
const newCell = document.createElement(refCell.tagName.toLowerCase())
newCell.innerHTML = '<p><br></p>'
newCell.style.border = '1px solid #dcdfe6'
newCell.style.padding = '8px 12px'
newCell.setAttribute('border', '1')
if (newCell.tagName === 'TH') {
newCell.style.backgroundColor = '#f5f7fa'
newCell.style.fontWeight = '600'
}
if (op === 'addColBefore') refCell.before(newCell)
else refCell.after(newCell)
})
} else if (op === 'deleteCol') {
const idx = cell.cellIndex
const table = cell.closest('table')
if (table && cell.parentElement.cells.length > 1) {
table.querySelectorAll('tr').forEach(r => {
const c = r.cells[idx]
if (c) c.remove()
})
}
} else if (op === 'deleteTable') {
const table = cell.closest('table')
if (table) table.remove()
}
this.notifyOwner(el.innerHTML || '')
},
notifyOwner(html) {
this.$ownerInstance.callMethod('onRenderChange', html)
}
}
}
</script>
<!-- #endif -->
<style lang="scss" scoped>
.note-editor-h5 {
.note-editor-wrapper {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 360rpx;
flex: 1;
min-height: 400rpx;
box-sizing: border-box;
}
.editor-toolbar {
flex-shrink: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8rpx;
gap: 6rpx;
padding-bottom: 12rpx;
margin-bottom: 12rpx;
border-bottom: 1rpx solid #ebeef5;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
width: 100%;
box-sizing: border-box;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
}
.toolbar-group {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8rpx;
gap: 6rpx;
flex-shrink: 0;
}
.toolbar-divider {
width: 1rpx;
height: 28rpx;
height: 32rpx;
background: #ebeef5;
flex-shrink: 0;
margin: 0 4rpx;
}
.tb-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4rpx;
min-width: 56rpx;
height: 56rpx;
padding: 0 12rpx;
gap: 2rpx;
min-width: 54rpx;
height: 54rpx;
padding: 0 8rpx;
border: 1rpx solid #ebeef5;
border-radius: 8rpx;
background: #fff;
color: #606266;
font-size: 22rpx;
line-height: 1;
cursor: pointer;
flex-shrink: 0;
box-sizing: border-box;
&:active {
background: #f5f7fa;
@@ -480,14 +575,15 @@ onMounted(() => {
}
.tb-label {
font-size: 20rpx;
font-size: 18rpx;
color: #909399;
margin-left: 2rpx;
}
.note-rich-editor {
width: 100%;
flex: 1;
min-height: 0;
min-height: 320rpx;
box-sizing: border-box;
font-size: 28rpx;
line-height: 1.6;
@@ -498,33 +594,65 @@ onMounted(() => {
user-select: text;
}
/* #ifdef H5 */
.note-rich-editor:empty::before {
content: attr(data-placeholder);
color: #909399;
pointer-events: none;
}
.note-rich-editor:focus:empty::before {
content: '';
}
/* #endif */
</style>
<style lang="scss">
@import '@/src/styles/rich-content.scss';
.note-rich-editor.rich-content {
/* 全局表格规范:强制在所有终端与渲染层呈现清晰的表格边框与单元格背景 */
.note-rich-editor {
table {
td,
th {
display: table !important;
border-collapse: collapse !important;
border-spacing: 0 !important;
border: 1px solid #dcdfe6 !important;
width: 100% !important;
margin: 12px 0 !important;
tbody {
display: table-row-group !important;
}
tr {
display: table-row !important;
border-bottom: 1px solid #ebeef5 !important;
}
th,
td {
display: table-cell !important;
border: 1px solid #dcdfe6 !important;
background-color: #ffffff;
padding: 8px 12px !important;
font-size: 26rpx !important;
min-width: 44px !important;
min-height: 36px !important;
vertical-align: top !important;
word-break: break-word !important;
box-sizing: border-box !important;
user-select: text !important;
-webkit-user-select: text !important;
cursor: text !important;
}
th {
background-color: #f5f7fa !important;
font-weight: 600 !important;
color: #303133 !important;
}
p {
margin: 0;
min-height: 1.4em;
margin: 0 !important;
min-height: 1.4em !important;
}
}
}