Commit b6df8dc3 by pangchong

feat(editor): 优化文档节点渲染及表格编辑器功能

- 新增 EXPD、EXPDNAME、ITEMNBR 标签及其复合容器映射
- 优化 dtdManager 子元素出现约束判断,支持父节点重复属性传递
- DocNodeRenderer 组件中新增 renderInline 计算属性替代 isInline
- 增强引用(REFINT、REFEXT、GRPHCREF)内容的混合渲染和编辑功能
- 为 EXPD 标签定制统一配置化渲染方案
- TableEditor 组件支持表头及表体行中非 ENTRY 子节点展示
- 调整行选中样式及操作按钮位置,提升行选择和删除体验
- TableEditor functionals 新增获取行非 ENTRY 子节点方法,优化逻辑复用
- NodeTree 组件删除节点时修正选中节点行为,防止选中错误
parent feaf36ba
......@@ -67,7 +67,10 @@ export const INLINE_ELEMENTS = [
'CON',
'NCON',
'REVST',
'REVEND'
'REVEND',
'EXPD',
'EXPDNAME',
'ITEMNBR'
]
// 段落类节点标签(PARA 为英文段落,PARAC 为中文段落)
......@@ -149,7 +152,8 @@ export const COMPOSITE_CONTAINER_MAP: Record<string, string[]> = {
TED: ['TOOLNAME', 'TOOLNBR'],
CON: ['CONNAME', 'CONNBR'],
GRAPHIC: ['TITLE'],
EINDATA: ['EIN']
EINDATA: ['EIN'],
EXPD: ['EXPDNAME', 'CSN', 'ITEMNBR']
}
// 动态派生出所有复合容器节点标签集
......
......@@ -226,14 +226,21 @@ export function getMinChildCount(parentTagName: string, childTagName: string): n
/**
* 在内容模型树中查找子元素的出现约束
*/
function findChildOccurrence(model: DtdContentModel, childTagName: string): string {
function findChildOccurrence(model: DtdContentModel, childTagName: string, parentRepeatable = false): string {
const isRepeatable = parentRepeatable || model.occurrence === 'zeroOrMore' || model.occurrence === 'oneOrMore'
if (model.type === 'elementRef' && model.name === childTagName) {
if (isRepeatable) {
return model.occurrence === 'oneOrMore' || model.occurrence === 'once'
? 'oneOrMore'
: 'zeroOrMore'
}
return model.occurrence
}
if (model.children) {
for (const child of model.children) {
const result = findChildOccurrence(child, childTagName)
const result = findChildOccurrence(child, childTagName, isRepeatable)
if (result !== 'unknown') return result
}
}
......
import type { XmlNode } from '@/types/xmlNode'
import { useEditorStore, nodeSelectedRefs } from '@/store/editor'
import { LIST_ITEM_TAGS, HEADER_TAGS, ROMAN_LOOKUP } from '../constants'
import { ALERT_AND_EFF_TAGS, ALERT_BLOCK_TAGS, COMPOSITE_CONTAINER_MAP } from '@/configs/xmlTags'
import { ALERT_AND_EFF_TAGS, ALERT_BLOCK_TAGS, COMPOSITE_CONTAINER_MAP, INLINE_ELEMENTS } from '@/configs/xmlTags'
import { useI18n } from 'vue-i18n'
// 警示块级节点集合(WARNING / CAUTION / NOTE),用于注入 insideAlert 上下文
const ALERT_ROOT_TAGS = new Set(ALERT_BLOCK_TAGS)
const INLINE_ELEMENTS_SET = new Set(INLINE_ELEMENTS)
export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | null; isInline?: boolean; insideRefBlock?: boolean }) {
const editorStore = useEditorStore()
......@@ -55,10 +56,14 @@ export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | nu
provide('insideChinese', true)
}
const renderInline = computed(() => {
return props.isInline || INLINE_ELEMENTS_SET.has(props.node.tagName)
})
// 判断是否是列表容器 (需要排除表格、主题、选项以及通过复合 Map 特殊渲染的各种复合父节点/容器)
const isContainer = computed(() => {
return (
!props.isInline &&
!renderInline.value &&
props.node.children &&
props.node.children.length > 0 &&
props.node.tagName !== 'TABLE' &&
......@@ -365,6 +370,7 @@ export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | nu
editorStore,
isSelected,
isContainer,
renderInline,
isListItem,
isHeaderTag,
isInsideAlert,
......
<template>
<component
:is="isInline ? 'span' : 'div'"
:is="renderInline ? 'span' : 'div'"
:data-node-id="node.id"
class="doc-node-wrapper relative transition-all"
:class="[
isInline ? 'inline align-baseline mx-0.5' : 'block my-1',
renderInline ? 'inline align-baseline mx-0.5' : 'block my-1',
isSelected ? 'ring-2 ring-primary ring-offset-1 rounded-sm bg-primary/5' : '',
!isInline && isContainer ? 'py-1 px-1' : ''
!renderInline && isContainer ? 'py-1 px-1' : ''
]"
:style="node.attributes?.MERGED === 'TRUE' ? { backgroundColor: 'yellow' } : {}"
@click.stop="editorStore.setSelectedNodeId(node.id)"
......@@ -44,7 +44,7 @@
<!-- 2. EFFECT / CONEFFECT (适用性) 特殊处理 -->
<template v-else-if="node.tagName === 'EFFECT' || node.tagName === 'CONEFFECT'">
<span v-if="isInline" class="font-bold text-xs select-none py-1 uppercase mx-1 italic" style="color: red">
<span v-if="renderInline" class="font-bold text-xs select-none py-1 uppercase mx-1 italic" style="color: red">
** {{ node.tagName === 'EFFECT' ? 'ON A/C' : 'CONF' }}: {{ formatEff(node.attributes.EFFRG || node.textContent) }}
</span>
<div
......@@ -58,7 +58,7 @@
<!-- 3. SBEFF / SBEFFC (服务通告适用性) 处理 -->
<template v-else-if="node.tagName === 'SBEFF' || node.tagName === 'SBEFFC'">
<span v-if="isInline" class="font-bold text-xs select-none py-1 uppercase mx-1 italic" style="color: red">
<span v-if="renderInline" class="font-bold text-xs select-none py-1 uppercase mx-1 italic" style="color: red">
** SB: {{ node.attributes.SBCOND }} SB {{ node.attributes.SBNBR }} for A/C {{ formatEff(node.attributes.EFFRG) }}
</span>
<div
......@@ -175,22 +175,124 @@
</span>
</template>
<!-- 8. REFINT / REFEXT (引用链接) 特殊处理 -->
<template v-else-if="node.tagName === 'REFINT' || node.tagName === 'REFEXT'">
<!-- 8. REFINT (内部引用链接) 特殊处理 -->
<template v-else-if="node.tagName === 'REFINT'">
<span
class="inline-ref font-mono font-medium hover:underline cursor-pointer select-all"
style="color: blue"
@click.stop="editorStore.setSelectedNodeId(node.id)"
>
<template v-if="!insideRefBlock">
{{ isChineseContext ? '(参考: ' : '(Ref: ' }}{{ node.textContent || node.attributes.REFLOC || '' }}{{ ')' }}
{{ isChineseContext ? '(参考: ' : '(Ref: ' }}
<template v-if="node.mixedContent && node.mixedContent.length > 0">
<template v-for="(item, index) in node.mixedContent" :key="index">
<span
v-if="item.type === 'text'"
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
style="color: blue"
@blur="handleMixedTextBlur(index, $event)"
v-text="item.text"
></span>
<DocNodeRenderer
v-else-if="item.type === 'element' && getChildNode(item.nodeId!)"
:node="getChildNode(item.nodeId!)!"
:parent="node"
is-inline
:inside-ref-block="insideRefBlock"
/>
</template>
</template>
<template v-else-if="node.children && node.children.length > 0">
<DocNodeRenderer
v-for="child in node.children"
:key="child.id"
:node="child"
:parent="node"
is-inline
:inside-ref-block="insideRefBlock"
/>
</template>
<template v-else>
<span
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
@blur="handleTextBlur"
v-text="node.textContent || node.attributes.REFID || '—'"
></span>
</template>
{{ ')' }}
</template>
<template v-else>
{{ node.textContent || node.attributes.REFLOC || '' }}
<template v-if="node.mixedContent && node.mixedContent.length > 0">
<template v-for="(item, index) in node.mixedContent" :key="index">
<span
v-if="item.type === 'text'"
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
style="color: blue"
@blur="handleMixedTextBlur(index, $event)"
v-text="item.text"
></span>
<DocNodeRenderer
v-else-if="item.type === 'element' && getChildNode(item.nodeId!)"
:node="getChildNode(item.nodeId!)!"
:parent="node"
is-inline
:inside-ref-block="insideRefBlock"
/>
</template>
</template>
<template v-else-if="node.children && node.children.length > 0">
<DocNodeRenderer
v-for="child in node.children"
:key="child.id"
:node="child"
:parent="node"
is-inline
:inside-ref-block="insideRefBlock"
/>
</template>
<template v-else>
<span
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
@blur="handleTextBlur"
v-text="node.textContent || node.attributes.REFID || '—'"
></span>
</template>
</template>
</span>
</template>
<!-- 8.2 REFEXT (外部引用链接) 特殊处理 -->
<template v-else-if="node.tagName === 'REFEXT'">
<span
class="inline-ref font-mono font-medium hover:underline cursor-pointer select-all"
style="color: blue"
@click.stop="editorStore.setSelectedNodeId(node.id)"
>
<template v-if="!insideRefBlock">
{{ isChineseContext ? '(参考: ' : '(Ref: '
}}<span
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
@blur="handleTextBlur"
v-text="node.textContent || node.attributes.REFLOC || '—'"
></span>{{ ')' }}
</template>
<template v-else>
<span
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
@blur="handleTextBlur"
v-text="node.textContent || node.attributes.REFLOC || '—'"
></span>
</template>
</span>
</template>
<!-- 8.5 GRPHCREF (图形交叉引用) 特殊处理 -->
<template v-else-if="node.tagName === 'GRPHCREF'">
<span
......@@ -199,37 +301,85 @@
@click.stop="editorStore.setSelectedNodeId(node.id)"
>
<template v-if="!insideRefBlock">
{{ isChineseContext ? '(参考: ' : '(Ref: '
}}{{
(node.mixedContent && node.mixedContent.length > 0
? node.mixedContent
.filter((m) => m.type === 'text')
.map((m) => m.text || '')
.join('')
.trim()
: node.textContent || ''
).trim() ||
node.attributes.REFID ||
node.attributes.STRUCTID ||
'—'
}}
{{ isChineseContext ? '(参考: ' : '(Ref: ' }}
<template v-if="node.mixedContent && node.mixedContent.length > 0">
<template v-for="(item, index) in node.mixedContent" :key="index">
<span
v-if="item.type === 'text'"
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
style="color: blue"
@blur="handleMixedTextBlur(index, $event)"
v-text="item.text"
></span>
<DocNodeRenderer
v-else-if="item.type === 'element' && getChildNode(item.nodeId!)"
:node="getChildNode(item.nodeId!)!"
:parent="node"
is-inline
:inside-ref-block="insideRefBlock"
/>
</template>
</template>
<template v-else-if="node.children && node.children.length > 0">
<DocNodeRenderer
v-for="child in node.children"
:key="child.id"
:node="child"
:parent="node"
is-inline
:inside-ref-block="insideRefBlock"
/>
</template>
<template v-else>
<span
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
@blur="handleTextBlur"
v-text="node.textContent || node.attributes.REFID || node.attributes.STRUCTID || '—'"
></span>
</template>
<template v-if="node.attributes.SHEETNBR">[Sh.{{ node.attributes.SHEETNBR }}]</template>
{{ ')' }}
</template>
<template v-else>
{{
(node.mixedContent && node.mixedContent.length > 0
? node.mixedContent
.filter((m) => m.type === 'text')
.map((m) => m.text || '')
.join('')
.trim()
: node.textContent || ''
).trim() ||
node.attributes.REFID ||
node.attributes.STRUCTID ||
'—'
}}
<template v-if="node.mixedContent && node.mixedContent.length > 0">
<template v-for="(item, index) in node.mixedContent" :key="index">
<span
v-if="item.type === 'text'"
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
style="color: blue"
@blur="handleMixedTextBlur(index, $event)"
v-text="item.text"
></span>
<DocNodeRenderer
v-else-if="item.type === 'element' && getChildNode(item.nodeId!)"
:node="getChildNode(item.nodeId!)!"
:parent="node"
is-inline
:inside-ref-block="insideRefBlock"
/>
</template>
</template>
<template v-else-if="node.children && node.children.length > 0">
<DocNodeRenderer
v-for="child in node.children"
:key="child.id"
:node="child"
:parent="node"
is-inline
:inside-ref-block="insideRefBlock"
/>
</template>
<template v-else>
<span
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
@blur="handleTextBlur"
v-text="node.textContent || node.attributes.REFID || node.attributes.STRUCTID || '—'"
></span>
</template>
<template v-if="node.attributes.SHEETNBR">[Sh.{{ node.attributes.SHEETNBR }}]</template>
</template>
</span>
......@@ -300,6 +450,32 @@
</span>
</template>
<!-- 13.5 EXPD (消耗品数据组合) 统一配置化渲染 -->
<template v-else-if="node.tagName === 'EXPD'">
<span
class="text-color1 cursor-pointer select-none inline-flex items-center space-x-1"
@click.stop="editorStore.setSelectedNodeId(node.id)"
>
<template v-if="node.children && node.children.length > 0">
<template v-for="childTag in COMPOSITE_CONTAINER_MAP[node.tagName]" :key="childTag">
<template v-if="node.children.find((c) => c.tagName === childTag)">
<span v-if="childTag === 'CSN'" class="text-color3 select-none mx-0.5">
(Fin:
</span>
<span v-else-if="childTag === 'ITEMNBR'" class="text-color3 select-none mx-0.5">
(Nbr:
</span>
<DocNodeRenderer :node="node.children.find((c) => c.tagName === childTag)!" :parent="node" is-inline />
<span v-if="childTag === 'CSN' || childTag === 'ITEMNBR'" class="text-color3 select-none mx-0.5">)</span>
</template>
</template>
</template>
<template v-else>
<span class="text-color3 italic">[EXPD]</span>
</template>
</span>
</template>
<!-- 15. CB / CBNAME / CBLOC (断路器信息) 特殊处理 -->
<template v-else-if="CB_COMPONENT_SET.has(node.tagName)">
<span
......@@ -749,7 +925,7 @@
<!-- 27. 兜底未匹配的普通文本标签 -->
<template v-else>
<span
v-if="isInline"
v-if="renderInline"
contenteditable="true"
class="focus:outline-none focus:bg-fill-3 px-0.5 rounded"
@blur="handleTextBlur"
......@@ -822,6 +998,7 @@ const {
editorStore,
isSelected,
isContainer,
renderInline,
isListItem,
isHeaderTag,
isInsideAlert,
......
......@@ -1145,6 +1145,7 @@ export function useNodeTree(
editorStore.setSelectedNodeId(parent.id)
editorStore.rebuildNodeMap()
} else {
editorStore.setSelectedNodeId(realNodeId)
editorStore.deleteSelectedNode()
}
window.$message?.success('删除成功')
......
......@@ -1699,6 +1699,12 @@ export function useTableEditor(props: { node: XmlNode }) {
return styles
}
const getRowNonEntryChildren = (rowId: string): XmlNode[] => {
const rowNodeObj = store.nodeMap.get(rowId)
if (!rowNodeObj || !rowNodeObj.node.children) return []
return rowNodeObj.node.children.filter((c) => c.tagName !== 'ENTRY')
}
return {
structure,
selectedCellIds,
......@@ -1739,6 +1745,7 @@ export function useTableEditor(props: { node: XmlNode }) {
splitCell,
insertedRowIds,
insertedCellIds,
getRowNonEntryChildren,
// 样式计算
colWidthStyles,
tableFrameClass,
......
......@@ -28,68 +28,83 @@
class="transition-all"
:class="[isNodeSelected(structure.theadId) ? 'outline outline-2 outline-primary outline-offset-[-2px] bg-primary/5' : '']"
>
<tr
v-for="(row, rIdx) in structure.theadRows"
:key="row.id"
:data-node-id="row.id"
class="border-b border-divider hover:bg-fill-3 group transition-colors cursor-pointer transition-all"
:class="[
isNodeSelected(row.id) ? 'outline outline-2 outline-primary outline-offset-[-2px] bg-primary/10' : '',
insertedRowIds.has(row.id) ? 'inserted-row-highlight' : ''
]"
@click.stop="editorStore.setSelectedNodeId(row.id)"
>
<!-- 表头行选择号 -->
<th
class="p-2 border border-divider text-center select-none cursor-pointer transition-colors bg-fill-4 text-color3 font-bold"
:class="[isNodeSelected(row.id) ? 'bg-primary text-white' : 'hover:bg-fill-3']"
@click.stop="editorStore.setSelectedNodeId(row.id)"
@contextmenu.prevent="handleRowContextMenu(row.id, $event)"
>
H{{ structure.theadRows.length > 1 ? rIdx + 1 : '' }}
</th>
<th
v-for="cell in row.cells.filter((c) => c.shouldRender !== false)"
:key="cell.id"
:colspan="cell.colspan"
:rowspan="cell.rowspan"
:data-node-id="cell.id"
class="p-2 text-left font-bold bg-fill-4 border border-divider text-color1 transition-colors cursor-pointer relative"
<template v-for="(row, rIdx) in structure.theadRows" :key="row.id">
<!-- 如果 ROW 拥有非 ENTRY 子节点 (如 EFFECT, CONEFFECT, REVST, REVEND) -->
<tr v-if="getRowNonEntryChildren(row.id).length > 0" class="bg-fill-1">
<th class="p-1 border border-divider bg-fill-4"></th>
<th :colspan="structure.cols" class="p-1 border border-divider text-left align-middle space-x-1 font-normal">
<DocNodeRenderer
v-for="child in getRowNonEntryChildren(row.id)"
:key="child.id"
:node="child"
:parent="editorStore.nodeMap.get(row.id)?.node"
is-inline
/>
</th>
<th class="p-1 border border-divider bg-fill-4"></th>
</tr>
<tr
:data-node-id="row.id"
class="border-b border-divider hover:bg-fill-3 group transition-colors cursor-pointer transition-all"
:class="[
isCellSelected(cell, cell.colIdx ?? 0) ? 'bg-link-1 outline outline-2 outline-link-6 outline-offset-[-2px]' : '',
isNodeSelected(structure.theadId) ? 'bg-primary/10 border-primary/40' : '',
insertedCellIds.has(cell.id) ? 'inserted-cell-highlight' : ''
isNodeSelected(row.id) ? 'outline outline-2 outline-primary outline-offset-[-2px] bg-primary/10' : '',
insertedRowIds.has(row.id) ? 'inserted-row-highlight' : ''
]"
:style="getCellStyle(cell)"
@click.stop="handleCellClick(cell, $event)"
@contextmenu.prevent="handleCellContextMenu(cell, row.id, $event)"
@click.stop="editorStore.setSelectedNodeId(row.id)"
>
<template v-if="cell.hasComplexChildren && cell.rawNode">
<DocNodeRenderer v-for="child in cell.rawNode!.children" :key="child.id" :node="child" :parent="cell.rawNode" />
</template>
<template v-else>
<div
v-for="para in cell.paragraphs"
:key="para.id"
:data-node-id="para.id"
contenteditable="true"
:class="[isNodeSelected(para.id) ? 'ring-2 ring-primary bg-primary/5 font-bold' : 'hover:bg-fill-3']"
:style="para.attributes?.MERGED === 'TRUE' ? { backgroundColor: 'yellow' } : {}"
@click.stop="handleParaClick(para, cell, $event)"
@blur="(e) => handleCellBlur(para.id, e)"
v-text="para.text"
></div>
</template>
</th>
<!-- 表头操作栏 -->
<th class="p-2 border border-divider bg-fill-4 text-center">
<CommonButton size="tiny" quaternary circle type="error" @click.stop="handleRowDelete(row.id)">
<template #icon>
<n-icon><trash-outline /></n-icon>
<!-- 表头行选择号 -->
<th
class="p-2 border border-divider text-center select-none cursor-pointer transition-colors bg-fill-4 text-color3 font-bold"
:class="[isNodeSelected(row.id) ? 'bg-primary text-white' : 'hover:bg-fill-3']"
@click.stop="editorStore.setSelectedNodeId(row.id)"
@contextmenu.prevent="handleRowContextMenu(row.id, $event)"
>
H{{ structure.theadRows.length > 1 ? rIdx + 1 : '' }}
</th>
<th
v-for="cell in row.cells.filter((c) => c.shouldRender !== false)"
:key="cell.id"
:colspan="cell.colspan"
:rowspan="cell.rowspan"
:data-node-id="cell.id"
class="p-2 text-left font-bold bg-fill-4 border border-divider text-color1 transition-colors cursor-pointer relative"
:class="[
isCellSelected(cell, cell.colIdx ?? 0) ? 'bg-link-1 outline outline-2 outline-link-6 outline-offset-[-2px]' : '',
isNodeSelected(structure.theadId) ? 'bg-primary/10 border-primary/40' : '',
insertedCellIds.has(cell.id) ? 'inserted-cell-highlight' : ''
]"
:style="getCellStyle(cell)"
@click.stop="handleCellClick(cell, $event)"
@contextmenu.prevent="handleCellContextMenu(cell, row.id, $event)"
>
<template v-if="cell.hasComplexChildren && cell.rawNode">
<DocNodeRenderer v-for="child in cell.rawNode!.children" :key="child.id" :node="child" :parent="cell.rawNode" />
</template>
</CommonButton>
</th>
</tr>
<template v-else>
<div
v-for="para in cell.paragraphs"
:key="para.id"
:data-node-id="para.id"
contenteditable="true"
:class="[isNodeSelected(para.id) ? 'ring-2 ring-primary bg-primary/5 font-bold' : 'hover:bg-fill-3']"
:style="para.attributes?.MERGED === 'TRUE' ? { backgroundColor: 'yellow' } : {}"
@click.stop="handleParaClick(para, cell, $event)"
@blur="(e) => handleCellBlur(para.id, e)"
v-text="para.text"
></div>
</template>
</th>
<!-- 表头操作栏 -->
<th class="p-2 border border-divider bg-fill-4 text-center">
<CommonButton size="tiny" quaternary circle type="error" @click.stop="handleRowDelete(row.id)">
<template #icon>
<n-icon><trash-outline /></n-icon>
</template>
</CommonButton>
</th>
</tr>
</template>
</thead>
<!-- TBODY 渲染 -->
......@@ -99,68 +114,83 @@
class="transition-all"
:class="[isNodeSelected(structure.tbodyId) ? 'outline outline-2 outline-primary outline-offset-[-2px] bg-primary/5' : '']"
>
<tr
v-for="(row, rIdx) in structure.tbodyRows"
:key="row.id"
:data-node-id="row.id"
class="border-b border-divider hover:bg-fill-3 group transition-colors cursor-pointer transition-all"
:class="[
isNodeSelected(row.id) ? 'outline outline-2 outline-primary outline-offset-[-2px] bg-primary/10' : '',
insertedRowIds.has(row.id) ? 'inserted-row-highlight' : ''
]"
@click.stop="editorStore.setSelectedNodeId(row.id)"
>
<!-- 行号选择单元格 -->
<td
class="p-2 border border-divider text-center select-none cursor-pointer transition-colors font-bold"
:class="[isNodeSelected(row.id) ? 'bg-primary text-white' : 'bg-fill-4 text-color3 hover:bg-fill-3']"
@click.stop="editorStore.setSelectedNodeId(row.id)"
@contextmenu.prevent="handleRowContextMenu(row.id, $event)"
>
{{ rIdx + 1 }}
</td>
<td
v-for="cell in row.cells.filter((c) => c.shouldRender !== false)"
:key="cell.id"
:colspan="cell.colspan"
:rowspan="cell.rowspan"
:data-node-id="cell.id"
class="p-2 border border-divider text-color2 transition-colors cursor-pointer relative"
<template v-for="(row, rIdx) in structure.tbodyRows" :key="row.id">
<!-- 如果 ROW 拥有非 ENTRY 子节点 (如 EFFECT, CONEFFECT, REVST, REVEND) -->
<tr v-if="getRowNonEntryChildren(row.id).length > 0" class="bg-fill-1">
<td class="p-1 border border-divider bg-fill-4"></td>
<td :colspan="structure.cols" class="p-1 border border-divider text-left align-middle space-x-1">
<DocNodeRenderer
v-for="child in getRowNonEntryChildren(row.id)"
:key="child.id"
:node="child"
:parent="editorStore.nodeMap.get(row.id)?.node"
is-inline
/>
</td>
<td class="p-1 border border-divider"></td>
</tr>
<tr
:data-node-id="row.id"
class="border-b border-divider hover:bg-fill-3 group transition-colors cursor-pointer transition-all"
:class="[
isCellSelected(cell, cell.colIdx ?? 0) ? 'bg-link-1 outline outline-2 outline-link-6 outline-offset-[-2px]' : '',
isNodeSelected(structure.tbodyId) ? 'bg-primary/5 border-primary/30' : '',
insertedCellIds.has(cell.id) ? 'inserted-cell-highlight' : ''
isNodeSelected(row.id) ? 'outline outline-2 outline-primary outline-offset-[-2px] bg-primary/10' : '',
insertedRowIds.has(row.id) ? 'inserted-row-highlight' : ''
]"
:style="getCellStyle(cell)"
@click.stop="handleCellClick(cell, $event)"
@contextmenu.prevent="handleCellContextMenu(cell, row.id, $event)"
@click.stop="editorStore.setSelectedNodeId(row.id)"
>
<template v-if="cell.hasComplexChildren && cell.rawNode">
<DocNodeRenderer v-for="child in cell.rawNode!.children" :key="child.id" :node="child" :parent="cell.rawNode" />
</template>
<template v-else>
<div
v-for="para in cell.paragraphs"
:key="para.id"
:data-node-id="para.id"
contenteditable="true"
:class="[isNodeSelected(para.id) ? 'ring-2 ring-primary bg-primary/5' : 'hover:bg-fill-3']"
:style="para.attributes?.MERGED === 'TRUE' ? { backgroundColor: 'yellow' } : {}"
@click.stop="handleParaClick(para, cell, $event)"
@blur="(e) => handleCellBlur(para.id, e)"
v-text="para.text"
></div>
</template>
</td>
<!-- 行删除按钮 -->
<td class="p-2 border border-divider text-center">
<CommonButton size="tiny" quaternary circle type="error" @click.stop="handleRowDelete(row.id)">
<template #icon>
<n-icon><trash-outline /></n-icon>
<!-- 行号选择单元格 -->
<td
class="p-2 border border-divider text-center select-none cursor-pointer transition-colors font-bold"
:class="[isNodeSelected(row.id) ? 'bg-primary text-white' : 'bg-fill-4 text-color3 hover:bg-fill-3']"
@click.stop="editorStore.setSelectedNodeId(row.id)"
@contextmenu.prevent="handleRowContextMenu(row.id, $event)"
>
{{ rIdx + 1 }}
</td>
<td
v-for="cell in row.cells.filter((c) => c.shouldRender !== false)"
:key="cell.id"
:colspan="cell.colspan"
:rowspan="cell.rowspan"
:data-node-id="cell.id"
class="p-2 border border-divider text-color2 transition-colors cursor-pointer relative"
:class="[
isCellSelected(cell, cell.colIdx ?? 0) ? 'bg-link-1 outline outline-2 outline-link-6 outline-offset-[-2px]' : '',
isNodeSelected(structure.tbodyId) ? 'bg-primary/5 border-primary/30' : '',
insertedCellIds.has(cell.id) ? 'inserted-cell-highlight' : ''
]"
:style="getCellStyle(cell)"
@click.stop="handleCellClick(cell, $event)"
@contextmenu.prevent="handleCellContextMenu(cell, row.id, $event)"
>
<template v-if="cell.hasComplexChildren && cell.rawNode">
<DocNodeRenderer v-for="child in cell.rawNode!.children" :key="child.id" :node="child" :parent="cell.rawNode" />
</template>
</CommonButton>
</td>
</tr>
<template v-else>
<div
v-for="para in cell.paragraphs"
:key="para.id"
:data-node-id="para.id"
contenteditable="true"
:class="[isNodeSelected(para.id) ? 'ring-2 ring-primary bg-primary/5' : 'hover:bg-fill-3']"
:style="para.attributes?.MERGED === 'TRUE' ? { backgroundColor: 'yellow' } : {}"
@click.stop="handleParaClick(para, cell, $event)"
@blur="(e) => handleCellBlur(para.id, e)"
v-text="para.text"
></div>
</template>
</td>
<!-- 行删除按钮 -->
<td class="p-2 border border-divider text-center">
<CommonButton size="tiny" quaternary circle type="error" @click.stop="handleRowDelete(row.id)">
<template #icon>
<n-icon><trash-outline /></n-icon>
</template>
</CommonButton>
</td>
</tr>
</template>
<!-- 列删除快捷按钮行 -->
<tr class="hover:bg-transparent">
......@@ -240,6 +270,7 @@ const {
batchDeleteColumns,
insertedRowIds,
insertedCellIds,
getRowNonEntryChildren,
colWidthStyles,
tableFrameClass,
getCellStyle
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment