|
|
@@ -739,10 +739,11 @@ function stripValueKeyPrefix(valueElementKey) {
|
|
|
return idx >= 0 ? valueElementKey.substring(idx + 1) : valueElementKey
|
|
|
}
|
|
|
|
|
|
-// 构建要素值映射表,分为长文本和短文本两类
|
|
|
+// 构建要素值映射表,分为长文本、短文本、静态文本三类
|
|
|
function buildElementValueMap() {
|
|
|
- const longTexts = [] // paragraph/table 类型的长文本要素
|
|
|
- const shortTexts = [] // text 类型的短文本要素
|
|
|
+ const longTexts = [] // paragraph/table 类型的长文本要素
|
|
|
+ const shortTexts = [] // text 类型的短文本要素(动态)
|
|
|
+ const staticTexts = [] // static 类型的静态要素
|
|
|
const colors = [
|
|
|
'#fff3cd', '#cce5ff', '#d4edda', '#f8d7da', '#e2d5f1',
|
|
|
'#d1ecf1', '#ffeeba', '#c3e6cb', '#f5c6cb', '#d6d8db'
|
|
|
@@ -751,6 +752,7 @@ function buildElementValueMap() {
|
|
|
for (const elem of elements.value) {
|
|
|
const elemValues = values.value.filter(v => stripValueKeyPrefix(v.elementKey) === elem.elementKey)
|
|
|
const elemType = elem.elementType || 'text'
|
|
|
+ const isStatic = elemType === 'static'
|
|
|
for (const val of elemValues) {
|
|
|
const text = val.valueText
|
|
|
if (!text || text.length < 2) continue
|
|
|
@@ -761,29 +763,35 @@ function buildElementValueMap() {
|
|
|
elementName: elem.elementName,
|
|
|
valueId: val.valueId,
|
|
|
elemType,
|
|
|
- color: colors[colorIdx % colors.length]
|
|
|
+ isStatic,
|
|
|
+ color: isStatic ? '#e8e8e8' : colors[colorIdx % colors.length]
|
|
|
}
|
|
|
- // paragraph/table 类型或多行文本视为长文本
|
|
|
- if (elemType === 'paragraph' || elemType === 'table' || text.includes('\n') || text.length > 100) {
|
|
|
+ if (isStatic) {
|
|
|
+ staticTexts.push(entry)
|
|
|
+ } else if (elemType === 'paragraph' || elemType === 'table' || text.includes('\n') || text.length > 100) {
|
|
|
longTexts.push(entry)
|
|
|
} else {
|
|
|
shortTexts.push(entry)
|
|
|
}
|
|
|
}
|
|
|
- colorIdx++
|
|
|
+ if (!isStatic) colorIdx++
|
|
|
}
|
|
|
// 长文本按长度降序
|
|
|
longTexts.sort((a, b) => b.text.length - a.text.length)
|
|
|
// 短文本按长度降序
|
|
|
shortTexts.sort((a, b) => b.text.length - a.text.length)
|
|
|
- return { longTexts, shortTexts }
|
|
|
+ // 静态文本按长度降序
|
|
|
+ staticTexts.sort((a, b) => b.text.length - a.text.length)
|
|
|
+ return { longTexts, shortTexts, staticTexts }
|
|
|
}
|
|
|
|
|
|
// 将文档 blocks 渲染为 HTML 字符串(含要素高亮)
|
|
|
function renderDocHtml() {
|
|
|
if (!docContent.value?.blocks) { docHtml.value = ''; return }
|
|
|
const blocks = docContent.value.blocks
|
|
|
- const { longTexts, shortTexts } = highlightEnabled.value ? buildElementValueMap() : { longTexts: [], shortTexts: [] }
|
|
|
+ const { longTexts, shortTexts, staticTexts } = highlightEnabled.value ? buildElementValueMap() : { longTexts: [], shortTexts: [], staticTexts: [] }
|
|
|
+ // 合并短文本和静态文本用于 runs 级别匹配(动态优先,静态在后)
|
|
|
+ const allShortTexts = [...shortTexts, ...staticTexts]
|
|
|
let highlightCount = 0
|
|
|
const parts = []
|
|
|
|
|
|
@@ -849,7 +857,7 @@ function renderDocHtml() {
|
|
|
} else {
|
|
|
// 非长文本 block,先 flush 再正常渲染
|
|
|
flushLongGroup()
|
|
|
- parts.push(renderBlockHtml(block, shortTexts, null, (n) => { highlightCount += n }))
|
|
|
+ parts.push(renderBlockHtml(block, allShortTexts, null, (n) => { highlightCount += n }))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -1071,9 +1079,14 @@ function highlightRunsWithElements(runs, shortMap) {
|
|
|
}
|
|
|
|
|
|
if (seg.em) {
|
|
|
- // 高亮段:先开 highlight span,内部保留各 run 的样式
|
|
|
+ // 高亮段:静态要素用虚线淡色边框,动态要素用实线彩色边框
|
|
|
const em = seg.em
|
|
|
- html += `<span class="elem-highlight" data-elem-key="${em.elementKey}" data-value-id="${em.valueId || ''}" style="border:1.5px solid ${darkenColor(em.color)};border-radius:3px;padding:0 2px;cursor:pointer;" contenteditable="false" title="${escapeAttr(em.elementName)}">`
|
|
|
+ const isStatic = em.isStatic
|
|
|
+ const borderStyle = isStatic
|
|
|
+ ? 'border:1px dashed #ccc;border-radius:3px;padding:0 2px;cursor:pointer;opacity:0.7;'
|
|
|
+ : `border:1.5px solid ${darkenColor(em.color)};border-radius:3px;padding:0 2px;cursor:pointer;`
|
|
|
+ const hlClass = isStatic ? 'elem-highlight elem-highlight-static' : 'elem-highlight'
|
|
|
+ html += `<span class="${hlClass}" data-elem-key="${em.elementKey}" data-value-id="${em.valueId || ''}" style="${borderStyle}" contenteditable="false" title="${escapeAttr(em.elementName)}">`
|
|
|
for (const g of groups) {
|
|
|
const run = runs[g.runIdx]
|
|
|
const slice = escapeHtml(run.text.substring(g.startOffset, g.endOffset))
|
|
|
@@ -3802,5 +3815,17 @@ onMounted(async () => {
|
|
|
margin: 0 auto;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// 静态要素淡色高亮
|
|
|
+.elem-highlight-static {
|
|
|
+ opacity: 0.6;
|
|
|
+ transition: opacity 0.2s, border-color 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 1;
|
|
|
+ border-color: #999 !important;
|
|
|
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|
|
|
|