Commit 13c42f53 by pangchong

feat(editor): 实现节点 MERGED 属性快速标记与高亮功能

- 新增 toggleNodeMerged 方法,支持快速切换节点 MERGED 属性状态
- 编辑器中渲染带 MERGED="TRUE" 节点时显示黄色背景
- 白名单标签(SIGNOFF、CBLST、TABLE)避免外层重复添加黄色背景
- 节点树、文档大纲中添加合并标记显示与样式高亮提示
- 在编辑器工具栏、选择工具条及右键菜单添加标记合并按钮与操作
- CBLST 表格新增 MERGED 状态下的黑色边框及相关样式调整
- 多处组件响应树版本变化,实现节点合并状态的动态更新与渲染
- FAQ 文档新增合并标记操作说明及使用指南
parent 2fd93a4b
...@@ -225,3 +225,10 @@ export const TRANSLATE_TARGET_TAGS = ['PARAC', 'TITLEC'] ...@@ -225,3 +225,10 @@ export const TRANSLATE_TARGET_TAGS = ['PARAC', 'TITLEC']
// 虚拟辅助排版标签集(不参与 DTD 规则约束,如打印换页符) // 虚拟辅助排版标签集(不参与 DTD 规则约束,如打印换页符)
export const VIRTUAL_LAYOUT_TAGS = ['PAGEBREAK'] export const VIRTUAL_LAYOUT_TAGS = ['PAGEBREAK']
/**
* 排除在通用外层容器(.doc-node-wrapper)施加 MERGED 黄色背景的标签列表
* 这些复合结构或特殊组件(如签字表 SIGNOFF、断路器列表 CBLST、CALS 表格 TABLE 等)
* 拥有自己的内部高亮与边框排版逻辑,避免在外层包装节点上重复添加黄色背景导致外边距漏底或打印多余边框。
*/
export const EXCLUDE_WRAPPER_MERGED_TAGS = ['SIGNOFF', 'CBLST', 'TABLE']
...@@ -610,6 +610,43 @@ export const FAQ_DATABASE: FaqItem[] = [ ...@@ -610,6 +610,43 @@ export const FAQ_DATABASE: FaqItem[] = [
isHot: true isHot: true
}, },
{ {
id: 'faq_node_merged_attr',
category: 'tree',
categoryName: '节点树与大纲',
title: '如何快速标记或取消节点的合并(MERGED)黄底高亮?',
keywords: [
'merged',
'合并',
'黄底',
'黄色背景',
'标记合并',
'取消合并',
'快捷标记',
'高亮合并',
'hb',
'hebing',
'hd',
'huangdi',
'bjhb'
],
synonyms: ['怎么给节点加黄底', 'MERGED属性怎么改', '为什么有黄色背景', '如何去掉黄色标记', '标记合并在哪里', '怎么标记MERGED'],
answer: '在工卡中,带有 MERGED="TRUE" 属性的节点会在编辑器中自动呈现黄色高亮背景。系统提供了一键式极速切换操作:',
steps: [
'【顶部主工具栏】:选中任意节点后,直接点击顶部主工具栏【标记合并 / 已标记合并】按钮(带 GitMerge 图标),一键赋予或移除该节点的 MERGED="TRUE" 属性。',
'【正文浮动工具条】:在正文中双击节点或选取文本弹出悬浮气泡条时,点击【标记/取消合并】图标按钮即可就地快速切换。',
'【右键上下文菜单】:在正文任意节点上点击右键,在菜单第二项直接选择【标记为合并】或【取消合并标记 (MERGED)】。',
'【安全撤销与重做】:所有 MERGED 标记切换操作均已自动接入历史记录,支持通过 Ctrl+Z 撤销或 Ctrl+Y 重做。'
],
highlights: [
'标记后编辑器即时呈现黄色背景,左侧节点树与 Word 大纲同步呈现黄底高亮、琥珀色指示边框与【合并】徽标,断路器表(CBLST)等复杂表格也会同步高亮内部行。',
'特殊复合组件(如签字表 SIGNOFF、断路器表 CBLST、CALS 表格 TABLE 等)已配置白名单,避免在外层包装节点上重复添加黄色背景导致外边距漏底或打印多余边框。'
],
actions: [{ label: '撤销上一步', type: 'event', payload: 'trigger_undo' }],
relatedQuestions: ['faq_node_tree_ops', 'faq_insert_mode', 'faq_keyboard_shortcuts'],
contextTags: ['PARA', 'PARAC', 'WARNING', 'CAUTION', 'NOTE', 'CBLST', 'TABLE', 'TITLE'],
isHot: true
},
{
id: 'faq_doc_outline_view', id: 'faq_doc_outline_view',
category: 'tree', category: 'tree',
categoryName: '节点树与大纲', categoryName: '节点树与大纲',
......
...@@ -373,7 +373,8 @@ export const useEditorStore = defineStore('editor', { ...@@ -373,7 +373,8 @@ export const useEditorStore = defineStore('editor', {
forceScrollSignal: 0, forceScrollSignal: 0,
editorZoom: 100, editorZoom: 100,
expandedKeys: [], expandedKeys: [],
skipExpandOnSelect: false skipExpandOnSelect: false,
treeVersion: 0
}), }),
getters: { getters: {
...@@ -432,6 +433,7 @@ export const useEditorStore = defineStore('editor', { ...@@ -432,6 +433,7 @@ export const useEditorStore = defineStore('editor', {
traverse(this.xmlTree, null) traverse(this.xmlTree, null)
} }
this.nodeMap = markRaw(map) this.nodeMap = markRaw(map)
this.treeVersion++
}, },
insertNode(tagName: string, insertBelow: boolean, customNode?: XmlNode, skipDtdCheck?: boolean) { insertNode(tagName: string, insertBelow: boolean, customNode?: XmlNode, skipDtdCheck?: boolean) {
...@@ -909,6 +911,32 @@ export const useEditorStore = defineStore('editor', { ...@@ -909,6 +911,32 @@ export const useEditorStore = defineStore('editor', {
}, },
/** /**
* 快速切换指定(或当前选中)节点的 MERGED 属性状态
* @param nodeId 可选节点 ID,默认使用当前 selectedNodeId
* @returns 切换后的状态:true 为已标记 MERGED,false 为已取消 MERGED,null 为无有效节点
*/
toggleNodeMerged(nodeId?: string): boolean | null {
const id = nodeId || this.selectedNodeId
if (!id) return null
const target = this.nodeMap.get(id)?.node
if (!target) return null
const currentAttrs = { ...(target.attributes || {}) }
const isMerged = currentAttrs.MERGED === 'TRUE'
this.saveSnapshot()
if (isMerged) {
delete currentAttrs.MERGED
} else {
currentAttrs.MERGED = 'TRUE'
}
target.attributes = currentAttrs
this.rebuildNodeMap()
return !isMerged
},
/**
* 更新当前选中节点的文本内容 * 更新当前选中节点的文本内容
*/ */
updateSelectedNodeText(text: string) { updateSelectedNodeText(text: string) {
......
...@@ -17,4 +17,5 @@ export interface EditorState { ...@@ -17,4 +17,5 @@ export interface EditorState {
editorZoom: number // 编辑器文字字号缩放比例 (80 - 200) editorZoom: number // 编辑器文字字号缩放比例 (80 - 200)
expandedKeys: string[] // 全局的节点展开状态 expandedKeys: string[] // 全局的节点展开状态
skipExpandOnSelect: boolean // 在选中时是否跳过祖先展开逻辑 skipExpandOnSelect: boolean // 在选中时是否跳过祖先展开逻辑
treeVersion: number // 树数据响应式版本计数器
} }
export { LIST_ITEM_TAGS, HEADER_TAGS, ROMAN_LOOKUP, ORDERED_LIST_ITEM_TAGS } from '@/configs/xmlTags' export { LIST_ITEM_TAGS, HEADER_TAGS, ROMAN_LOOKUP, ORDERED_LIST_ITEM_TAGS, EXCLUDE_WRAPPER_MERGED_TAGS } from '@/configs/xmlTags'
...@@ -16,7 +16,8 @@ import { ...@@ -16,7 +16,8 @@ import {
AddCircleOutline, AddCircleOutline,
ArrowUpOutline, ArrowUpOutline,
ArrowDownOutline, ArrowDownOutline,
LayersOutline LayersOutline,
GitMergeOutline
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { useEditorStore } from '@/store/editor' import { useEditorStore } from '@/store/editor'
import type { XmlNode } from '@/types/xmlNode' import type { XmlNode } from '@/types/xmlNode'
...@@ -247,6 +248,11 @@ export function useEditAreaContextMenu(props: EditAreaContextMenuProps, emit: (e ...@@ -247,6 +248,11 @@ export function useEditAreaContextMenu(props: EditAreaContextMenuProps, emit: (e
return activeNodeId.value.includes('-txt-') || activeNode.value?.tagName === '#text' return activeNodeId.value.includes('-txt-') || activeNode.value?.tagName === '#text'
}) })
// 当前节点是否已标记为合并 (MERGED="TRUE")
const isCurrentMerged = computed(() => {
return activeNode.value?.attributes?.MERGED === 'TRUE'
})
// 是否是虚拟排版节点(如分页符) // 是否是虚拟排版节点(如分页符)
const isVirtualLayoutNode = computed(() => { const isVirtualLayoutNode = computed(() => {
return activeNode.value ? VIRTUAL_LAYOUT_TAGS.includes(activeNode.value.tagName) : false return activeNode.value ? VIRTUAL_LAYOUT_TAGS.includes(activeNode.value.tagName) : false
...@@ -390,6 +396,16 @@ export function useEditAreaContextMenu(props: EditAreaContextMenuProps, emit: (e ...@@ -390,6 +396,16 @@ export function useEditAreaContextMenu(props: EditAreaContextMenuProps, emit: (e
addNodeVisible.value = true addNodeVisible.value = true
} }
// 快速标记/取消合并 (MERGED)
else if (actionName === 'toggleMerged') {
const nextState = editorStore.toggleNodeMerged(realId)
if (nextState !== null) {
window.$message.success(
nextState ? `已将 <${mapped.node.tagName}> 标记为合并 (MERGED="TRUE")` : `已取消 <${mapped.node.tagName}> 的合并标记`
)
}
}
// 2. 复制节点 // 2. 复制节点
else if (actionName === 'copyNode') { else if (actionName === 'copyNode') {
if (isVirtual) { if (isVirtual) {
...@@ -964,6 +980,15 @@ export function useEditAreaContextMenu(props: EditAreaContextMenuProps, emit: (e ...@@ -964,6 +980,15 @@ export function useEditAreaContextMenu(props: EditAreaContextMenuProps, emit: (e
icon: icon(CreateOutline) icon: icon(CreateOutline)
}) })
// 快捷标记/取消合并
if (!isTextNode.value) {
options.push({
label: isCurrentMerged.value ? '取消合并标记' : '标记为合并',
key: 'toggleMerged',
icon: icon(GitMergeOutline)
})
}
// 2. 结构调整 (子菜单) // 2. 结构调整 (子菜单)
options.push({ options.push({
label: '结构调整', label: '结构调整',
......
...@@ -423,6 +423,22 @@ export function useSelectionToolbar() { ...@@ -423,6 +423,22 @@ export function useSelectionToolbar() {
visible.value = false visible.value = false
} }
const isCurrentMerged = computed(() => {
return activeNode.value?.attributes?.MERGED === 'TRUE'
})
const handleToggleMerged = () => {
if (!activeNode.value) return
const realId = isVirtualText.value ? activeNodeId.value?.split('-txt-')[0] : activeNode.value.id
if (!realId) return
const res = store.toggleNodeMerged(realId)
if (res === true) {
window.$message?.success(`已将节点 <${activeNode.value.tagName}> 标记为合并`)
} else if (res === false) {
window.$message?.info(`已取消节点 <${activeNode.value.tagName}> 的合并标记`)
}
}
// 监听双击事件:双击任意 data-node-id 元素时,即使是 select-none 元素,也直接弹起工具栏并选中该节点 // 监听双击事件:双击任意 data-node-id 元素时,即使是 select-none 元素,也直接弹起工具栏并选中该节点
const handleDblclick = (e: MouseEvent) => { const handleDblclick = (e: MouseEvent) => {
if (mouseupTimeoutId) { if (mouseupTimeoutId) {
...@@ -499,6 +515,9 @@ export function useSelectionToolbar() { ...@@ -499,6 +515,9 @@ export function useSelectionToolbar() {
handleAddChild, handleAddChild,
handleInsertBefore, handleInsertBefore,
handleInsertAfter, handleInsertAfter,
handleDeleteNode handleDeleteNode,
isCurrentMerged,
handleToggleMerged
} }
} }
...@@ -26,6 +26,25 @@ ...@@ -26,6 +26,25 @@
编辑节点属性 编辑节点属性
</n-tooltip> </n-tooltip>
<!-- 标记/取消合并 (MERGED) -->
<n-tooltip trigger="hover">
<template #trigger>
<CommonButton
size="tiny"
quaternary
circle
:type="isCurrentMerged ? 'warning' : 'default'"
:disabled="!hasActiveNode || isVirtualText"
@click="handleToggleMerged"
>
<template #icon>
<n-icon><GitMergeOutline /></n-icon>
</template>
</CommonButton>
</template>
{{ isCurrentMerged ? '取消合并标记' : '标记为合并' }}
</n-tooltip>
<!-- 查看规则 --> <!-- 查看规则 -->
<n-tooltip trigger="hover"> <n-tooltip trigger="hover">
<template #trigger> <template #trigger>
...@@ -132,7 +151,8 @@ import { ...@@ -132,7 +151,8 @@ import {
ArrowDownOutline, ArrowDownOutline,
TrashOutline, TrashOutline,
LanguageOutline, LanguageOutline,
SyncOutline SyncOutline,
GitMergeOutline
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { useSelectionToolbar } from './functionals/index' import { useSelectionToolbar } from './functionals/index'
...@@ -154,7 +174,9 @@ const { ...@@ -154,7 +174,9 @@ const {
handleAddChild, handleAddChild,
handleInsertBefore, handleInsertBefore,
handleInsertAfter, handleInsertAfter,
handleDeleteNode handleDeleteNode,
isCurrentMerged,
handleToggleMerged
} = useSelectionToolbar() } = useSelectionToolbar()
</script> </script>
......
...@@ -249,6 +249,25 @@ export function useEditorToolbar(emit: any) { ...@@ -249,6 +249,25 @@ export function useEditorToolbar(emit: any) {
} }
}) })
const isCurrentMerged = computed(() => {
return editorStore.selectedNode?.attributes?.MERGED === 'TRUE'
})
const handleToggleMerged = () => {
const node = editorStore.selectedNode
if (!node) {
window.$message.warning('请先在树中或编辑器中选择一个目标节点!')
return
}
const res = editorStore.toggleNodeMerged(node.id)
if (res === true) {
window.$message.success(`已将节点 <${node.tagName}> 标记为合并`)
} else if (res === false) {
window.$message.info(`已取消节点 <${node.tagName}> 的合并标记`)
}
}
return { return {
editorStore, editorStore,
appStore, appStore,
...@@ -272,6 +291,8 @@ export function useEditorToolbar(emit: any) { ...@@ -272,6 +291,8 @@ export function useEditorToolbar(emit: any) {
extractTranslateModalRef, extractTranslateModalRef,
searchTranslateModalRef, searchTranslateModalRef,
xmlSizeStr, xmlSizeStr,
templateSelectModalRef templateSelectModalRef,
isCurrentMerged,
handleToggleMerged
} }
} }
...@@ -287,6 +287,35 @@ ...@@ -287,6 +287,35 @@
</n-popover> </n-popover>
</div> </div>
<div class="w-px h-5 flex-shrink-0 mx-2" style="background: #d0d0d0"></div>
<!-- 组 2.5:节点快速标记操作 -->
<div class="flex items-center gap-0.5 flex-shrink-0">
<n-tooltip trigger="hover" :show-delay="300">
<template #trigger>
<button
type="button"
class="toolbar-btn flex items-center gap-1.5 px-2.5 h-7 rounded transition-all select-none focus:outline-none flex-shrink-0 disabled:opacity-40 disabled:hover:bg-transparent"
:class="[
isCurrentMerged
? 'bg-warning/20 text-warning font-semibold border border-warning/40 shadow-sm'
: 'text-color2 hover:bg-fill-2 active:bg-fill-3'
]"
:disabled="!editorStore.selectedNode"
@click="handleToggleMerged"
>
<n-icon class="text-base"><git-merge-outline /></n-icon>
<span class="text-xs font-medium">{{ isCurrentMerged ? '已标记合并' : '标记合并' }}</span>
</button>
</template>
<div v-if="editorStore.selectedNode" class="text-xs space-y-0.5">
<div>当前节点: &lt;{{ editorStore.selectedNode.tagName }}&gt;</div>
<div>{{ isCurrentMerged ? '点击取消该节点的合并 (MERGED) 标记' : '快捷标记为合并,高亮显示黄色背景' }}</div>
</div>
<span v-else class="text-xs">请先选择节点</span>
</n-tooltip>
</div>
<div class="w-px h-5 flex-shrink-0 mx-3" style="background: #d0d0d0"></div> <div class="w-px h-5 flex-shrink-0 mx-3" style="background: #d0d0d0"></div>
<div class="flex items-center gap-0.5 flex-shrink-0"> <div class="flex items-center gap-0.5 flex-shrink-0">
...@@ -525,7 +554,8 @@ import { ...@@ -525,7 +554,8 @@ import {
CheckmarkCircleOutline, CheckmarkCircleOutline,
SettingsOutline, SettingsOutline,
GitCompareOutline, GitCompareOutline,
ArchiveOutline ArchiveOutline,
GitMergeOutline
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { useEditorToolbar } from './functionals' import { useEditorToolbar } from './functionals'
import { GREEN_BUTTONS } from './constants' import { GREEN_BUTTONS } from './constants'
...@@ -569,7 +599,9 @@ const { ...@@ -569,7 +599,9 @@ const {
extractTranslateModalRef, extractTranslateModalRef,
searchTranslateModalRef, searchTranslateModalRef,
xmlSizeStr, xmlSizeStr,
templateSelectModalRef templateSelectModalRef,
isCurrentMerged,
handleToggleMerged
} = useEditorToolbar(emit) } = useEditorToolbar(emit)
const insertFragmentModalRef = ref<any>(null) const insertFragmentModalRef = ref<any>(null)
......
...@@ -6,6 +6,7 @@ export interface OutlineItem { ...@@ -6,6 +6,7 @@ export interface OutlineItem {
text: string text: string
fullText: string fullText: string
level: number level: number
isMerged?: boolean
} }
// 文档大纲组件 Props 定义 // 文档大纲组件 Props 定义
......
...@@ -21,6 +21,7 @@ export function useDocOutlineView(props: DocOutlineProps) { ...@@ -21,6 +21,7 @@ export function useDocOutlineView(props: DocOutlineProps) {
const outlineList = computed<OutlineItem[]>(() => { const outlineList = computed<OutlineItem[]>(() => {
try { try {
if (!editorStore.xmlTree) return [] if (!editorStore.xmlTree) return []
const _ = editorStore.treeVersion
const outline: OutlineItem[] = [] const outline: OutlineItem[] = []
const getPreviewText = (n: any): string => { const getPreviewText = (n: any): string => {
...@@ -231,13 +232,15 @@ export function useDocOutlineView(props: DocOutlineProps) { ...@@ -231,13 +232,15 @@ export function useDocOutlineView(props: DocOutlineProps) {
} }
if (isOutline) { if (isOutline) {
const isMerged = node.attributes?.MERGED === 'TRUE'
outline.push({ outline.push({
id: node.id, id: node.id,
tagName: node.tagName, tagName: node.tagName,
seqNum, seqNum,
text: text, text: text,
fullText: `${seqNum}${text}`, fullText: `${seqNum}${text}`,
level level,
isMerged
}) })
} }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
:class="[ :class="[
activeOutlineId === item.id activeOutlineId === item.id
? 'bg-primary text-white font-semibold shadow-sm' ? 'bg-primary text-white font-semibold shadow-sm'
: item.isMerged
? 'bg-amber-100/90 text-amber-950 dark:bg-amber-500/25 dark:text-amber-200 border-l-2 border-amber-500 font-medium'
: item.level === 0 : item.level === 0
? 'bg-primary/10 dark:bg-primary/20 border border-primary/30 text-color1 dark:text-white hover:bg-primary/15' ? 'bg-primary/10 dark:bg-primary/20 border border-primary/30 text-color1 dark:text-white hover:bg-primary/15'
: item.level === 1 : item.level === 1
...@@ -80,6 +82,20 @@ ...@@ -80,6 +82,20 @@
> >
{{ item.text }} {{ item.text }}
</span> </span>
<!-- 合并标记 Badge -->
<span
v-if="item.isMerged"
class="text-[10px] font-semibold px-1.5 py-0.2 rounded shrink-0 leading-tight border flex items-center gap-0.5"
:class="[
activeOutlineId === item.id
? 'bg-amber-300 text-amber-950 border-amber-400 font-bold shadow-sm'
: 'bg-amber-200 text-amber-900 border-amber-400 dark:bg-amber-500/30 dark:text-amber-200 dark:border-amber-500/40'
]"
title="此节点已标记为合并 (MERGED='TRUE')"
>
<n-icon size="10"><GitMergeOutline /></n-icon>
<span>合并</span>
</span>
</div> </div>
<!-- 定位指示 / 悬浮跳转图标 --> <!-- 定位指示 / 悬浮跳转图标 -->
...@@ -131,7 +147,8 @@ import { ...@@ -131,7 +147,8 @@ import {
GridOutline, GridOutline,
WarningOutline, WarningOutline,
InformationCircleOutline, InformationCircleOutline,
NavigateOutline NavigateOutline,
GitMergeOutline
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import type { DocOutlineProps } from './constants' import type { DocOutlineProps } from './constants'
import { useDocOutlineView } from './functionals' import { useDocOutlineView } from './functionals'
......
...@@ -31,10 +31,13 @@ ...@@ -31,10 +31,13 @@
? 'bg-primary text-white tree-node-selected' ? 'bg-primary text-white tree-node-selected'
: (showDropdown || isAnyModalVisible) && contextNodeId === item.id : (showDropdown || isAnyModalVisible) && contextNodeId === item.id
? 'tree-node-context-active' ? 'tree-node-context-active'
: item.isMerged
? 'bg-amber-100/90 text-amber-950 dark:bg-amber-500/25 dark:text-amber-200 border-l-2 border-amber-500 hover:bg-amber-200/90 dark:hover:bg-amber-500/35 font-medium'
: hasCustomColor(item) : hasCustomColor(item)
? 'hover:bg-fill-3' ? 'hover:bg-fill-3'
: 'hover:bg-fill-3 text-color2', : 'hover:bg-fill-3 text-color2',
VIRTUAL_LAYOUT_TAGS.includes(item.tagName) ? 'tree-node-pagebreak' : '' VIRTUAL_LAYOUT_TAGS.includes(item.tagName) ? 'tree-node-pagebreak' : '',
item.isMerged ? 'tree-node-merged' : ''
]" ]"
:style="[ :style="[
{ {
...@@ -137,6 +140,20 @@ ...@@ -137,6 +140,20 @@
> >
<!-- 节点名称高亮 --> <!-- 节点名称高亮 -->
<span class="font-bold text-sm flex-shrink-0" v-html="highlightText(item.tagName, pattern)"></span> <span class="font-bold text-sm flex-shrink-0" v-html="highlightText(item.tagName, pattern)"></span>
<!-- 合并标记 Badge (黄底醒目标识) -->
<span
v-if="item.isMerged"
class="text-[10px] font-semibold px-1.5 py-0.5 rounded shrink-0 leading-none flex items-center gap-0.5 border"
:class="[
effectiveSelectedId === item.id
? 'bg-amber-300 text-amber-950 border-amber-400 font-bold shadow-sm'
: 'bg-amber-200/90 text-amber-900 border-amber-400/80 dark:bg-amber-500/35 dark:text-amber-100 dark:border-amber-400/40'
]"
title="此节点已标记为合并 (MERGED='TRUE')"
>
<n-icon size="10"><GitMergeOutline /></n-icon>
<span>合并</span>
</span>
<!-- 属性摘要 Badge (包含淡色背景与框线,与文本预览彻底区分) --> <!-- 属性摘要 Badge (包含淡色背景与框线,与文本预览彻底区分) -->
<span <span
v-if="item.attrSummary" v-if="item.attrSummary"
...@@ -194,7 +211,7 @@ ...@@ -194,7 +211,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { SyncOutline, NavigateOutline } from '@vicons/ionicons5' import { SyncOutline, NavigateOutline, GitMergeOutline } from '@vicons/ionicons5'
import { VIRTUAL_LAYOUT_TAGS } from '@/configs/xmlTags' import { VIRTUAL_LAYOUT_TAGS } from '@/configs/xmlTags'
import type { FlatNode, XmlTreeViewProps } from './constants' import type { FlatNode, XmlTreeViewProps } from './constants'
import { useXmlTreeView } from './functionals' import { useXmlTreeView } from './functionals'
...@@ -516,4 +533,9 @@ defineExpose({ ...@@ -516,4 +533,9 @@ defineExpose({
color: #ffffff !important; color: #ffffff !important;
border: 1px dashed color-mix(in srgb, var(--card-color, #ffffff) 40%, transparent) !important; border: 1px dashed color-mix(in srgb, var(--card-color, #ffffff) 40%, transparent) !important;
} }
/* ── 合并标记节点(MERGED="TRUE")在树中的黄底凸显样式 ── */
.tree-node-merged:not(.tree-node-selected) {
box-shadow: inset 0 0 0 1px rgba(245, 158, 11, 0.4);
}
</style> </style>
...@@ -34,6 +34,7 @@ export interface FlatNode { ...@@ -34,6 +34,7 @@ export interface FlatNode {
hasChildren: boolean hasChildren: boolean
isExpanded: boolean isExpanded: boolean
rawNode: XmlNode rawNode: XmlNode
isMerged?: boolean
} }
// DTD 批量删除被拦截节点的明细定义 // DTD 批量删除被拦截节点的明细定义
......
...@@ -14,7 +14,8 @@ import { ...@@ -14,7 +14,8 @@ import {
DocumentTextOutline, DocumentTextOutline,
CodeWorkingOutline, CodeWorkingOutline,
LanguageOutline, LanguageOutline,
ArchiveOutline ArchiveOutline,
GitMergeOutline
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { useEditorStore } from '@/store/editor' import { useEditorStore } from '@/store/editor'
import { useAppStore } from '@/store/app' import { useAppStore } from '@/store/app'
...@@ -229,6 +230,7 @@ export function useNodeTree( ...@@ -229,6 +230,7 @@ export function useNodeTree(
} }
} }
const isExpanded = expandedKeys.value.has(node.id) const isExpanded = expandedKeys.value.has(node.id)
const isMerged = node.attributes?.MERGED === 'TRUE'
let attrSummary = '' let attrSummary = ''
let subtitle = '' let subtitle = ''
...@@ -466,7 +468,8 @@ export function useNodeTree( ...@@ -466,7 +468,8 @@ export function useNodeTree(
depth, depth,
hasChildren, hasChildren,
isExpanded, isExpanded,
rawNode: node rawNode: node,
isMerged
}) })
// 如果处于搜索状态,强制展开展示搜索结果;否则根据 isExpanded 展开 // 如果处于搜索状态,强制展开展示搜索结果;否则根据 isExpanded 展开
...@@ -521,6 +524,7 @@ export function useNodeTree( ...@@ -521,6 +524,7 @@ export function useNodeTree(
// 扁平列表数据 // 扁平列表数据
const flatList = computed(() => { const flatList = computed(() => {
if (!editorStore.xmlTree) return [] if (!editorStore.xmlTree) return []
const _ = editorStore.treeVersion
return buildFlatList(editorStore.xmlTree, 0, pattern.value) return buildFlatList(editorStore.xmlTree, 0, pattern.value)
}) })
...@@ -1017,6 +1021,16 @@ export function useNodeTree( ...@@ -1017,6 +1021,16 @@ export function useNodeTree(
icon: icon(CreateOutline) icon: icon(CreateOutline)
}) })
// 快速标记/取消合并 (MERGED)
if (!isVirtual) {
const isMerged = node.attributes?.MERGED === 'TRUE'
options.push({
label: isMerged ? '取消合并标记' : '标记为合并',
key: 'toggleMerged',
icon: icon(GitMergeOutline)
})
}
// 9. 编辑结构 // 9. 编辑结构
const structureChildren: DropdownOption[] = [] const structureChildren: DropdownOption[] = []
...@@ -1179,6 +1193,17 @@ export function useNodeTree( ...@@ -1179,6 +1193,17 @@ export function useNodeTree(
break break
} }
// ── 快速切换合并标记 ──
case 'toggleMerged': {
const res = editorStore.toggleNodeMerged(node.id)
if (res === true) {
window.$message?.success(`已将节点 <${node.tagName}> 标记为合并`)
} else if (res === false) {
window.$message?.info(`已取消节点 <${node.tagName}> 的合并标记`)
}
break
}
// ── 智能翻译节点 ── // ── 智能翻译节点 ──
case 'translateNode': { case 'translateNode': {
const enSourceNode = getEnglishSourceNode(node, parent) const enSourceNode = getEnglishSourceNode(node, parent)
...@@ -1595,6 +1620,7 @@ export function useNodeTree( ...@@ -1595,6 +1620,7 @@ export function useNodeTree(
} }
const hasCustomColor = (item: FlatNode): boolean => { const hasCustomColor = (item: FlatNode): boolean => {
if (item.isMerged) return true
const tag = item.tagName.toUpperCase() const tag = item.tagName.toUpperCase()
return COLORED_TAGS.map((t) => t.toUpperCase()).includes(tag) return COLORED_TAGS.map((t) => t.toUpperCase()).includes(tag)
} }
......
...@@ -459,6 +459,7 @@ export function useEditor() { ...@@ -459,6 +459,7 @@ export function useEditor() {
margin-left: 0 !important; margin-left: 0 !important;
} }
/* 移除外层卡片及编辑容器的圆角边框、背景及阴影 */ /* 移除外层卡片及编辑容器的圆角边框、背景及阴影 */
.cblst-container,
.border.border-divider.rounded-lg, .border.border-divider.rounded-lg,
.rounded-lg.shadow-inner.bg-fill-2, .rounded-lg.shadow-inner.bg-fill-2,
.bg-card.p-3 { .bg-card.p-3 {
......
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