Commit e5785566 by pangchong

fix: 优化工卡对比弹窗滚动条显示、工具栏样式及右键菜单混合文本节点目标选择与撤回定位

parent 6be9711c
...@@ -306,3 +306,14 @@ export const COMPOSITE_CONTAINER_MAP: Record<string, string[]> = { ...@@ -306,3 +306,14 @@ export const COMPOSITE_CONTAINER_MAP: Record<string, string[]> = {
- **配置位置约束(强制)**:针对不同环境(如 `bx`、`xm` 等)的编辑器渲染或行为参数配置,**必须且仅允许**统一写入各自环境目录下的配置文件中(例如 `src/bx/config/index.ts`、`src/xm/config/index.ts` 等),并在 `src/configs/index.ts` 中进行中转与兜底导出。 - **配置位置约束(强制)**:针对不同环境(如 `bx`、`xm` 等)的编辑器渲染或行为参数配置,**必须且仅允许**统一写入各自环境目录下的配置文件中(例如 `src/bx/config/index.ts`、`src/xm/config/index.ts` 等),并在 `src/configs/index.ts` 中进行中转与兜底导出。
- **禁止在 `.env` 中编写渲染配置**:**绝对禁止**将任何与编辑器视图渲染、业务呈现逻辑或默认文件名/文件内容相关的配置项写入项目根目录下的各个 `.env.*`(环境变量)文件中。根目录下的 `.env` 配置文件应当只保留环境区分(如 `VITE_CUSTOM_CODE`)以及基础 API 接口服务地址等系统底层变量。 - **禁止在 `.env` 中编写渲染配置**:**绝对禁止**将任何与编辑器视图渲染、业务呈现逻辑或默认文件名/文件内容相关的配置项写入项目根目录下的各个 `.env.*`(环境变量)文件中。根目录下的 `.env` 配置文件应当只保留环境区分(如 `VITE_CUSTOM_CODE`)以及基础 API 接口服务地址等系统底层变量。
---
## 23. XML 右键上下文菜单与目标节点选择规范 (单文本与多文本区分规则)
- **区分单文本节点与多文本节点 (混合内容)**:
- **单文本节点**(节点 `mixedContent` 内部仅有 1 段有效纯文本):该文本即为父元素节点本身的内容,**严禁**在右键点击时将其推入 `-txt-` 虚拟 `#text` 节点。操作目标必须直接定位并归属于实体 XML 标签节点(如 `<REFBLOCK>`、`<PARA>` 等),右键菜单 Header 顶部展示 `目标: <REFBLOCK>`,下拉树列表中**不渲染**无意义的 `#text` 选项。
- **多文本节点 / 混合内容**(节点内部包含多个被子 Element 元素分隔的独立文本片段,如 `<REFBLOCK>文本1<REFINT/>文本2</REFBLOCK>`):**必须**保留 `-txt-` 虚拟文本节点,以便用户精确定位到具体的文本片段。右键点击具体文本片段时展示 `目标: #text`,并在下拉层级列表中展示并打勾该具体 `#text` 选项。
- **目标节点名称展示格式**:在右键菜单 Header 顶部及相关面板展示目标节点名称时,实体 XML 节点名称**必须**统一附带尖括号格式(如 `目标: <REFBLOCK>`),保持与大纲树和层级列表一致的视觉识别。
- **层级切换响应式刷新**:右键菜单 Header 选项的 Key 必须包含 `activeNodeId` 动态后缀(如 `key: `headerTarget:${activeNodeId}``),以保证用户在下拉层级列表中手动切换选择父/子/文本节点时,菜单顶部的 `目标: <XXX>` 标题能实时联动更新。
...@@ -773,6 +773,13 @@ export const useEditorStore = defineStore('editor', { ...@@ -773,6 +773,13 @@ export const useEditorStore = defineStore('editor', {
this.redoStack = [] this.redoStack = []
}, },
hasNode(id: string | null | undefined): boolean {
if (!id) return false
if (this.nodeMap.has(id)) return true
const realId = id.includes('-txt-') ? id.split('-txt-')[0] : id
return this.nodeMap.has(realId)
},
undo() { undo() {
if (this.undoStack.length === 0 || !this.xmlTree) return if (this.undoStack.length === 0 || !this.xmlTree) return
...@@ -787,7 +794,7 @@ export const useEditorStore = defineStore('editor', { ...@@ -787,7 +794,7 @@ export const useEditorStore = defineStore('editor', {
this.xmlTree = previousSnapshot.tree this.xmlTree = previousSnapshot.tree
this.rebuildNodeMap() this.rebuildNodeMap()
if (previousSnapshot.selectedNodeId && this.nodeMap.has(previousSnapshot.selectedNodeId)) { if (previousSnapshot.selectedNodeId && this.hasNode(previousSnapshot.selectedNodeId)) {
this.setSelectedNodeId(previousSnapshot.selectedNodeId) this.setSelectedNodeId(previousSnapshot.selectedNodeId)
} else { } else {
this.setSelectedNodeId(previousSnapshot.tree.id) this.setSelectedNodeId(previousSnapshot.tree.id)
...@@ -812,7 +819,7 @@ export const useEditorStore = defineStore('editor', { ...@@ -812,7 +819,7 @@ export const useEditorStore = defineStore('editor', {
this.xmlTree = nextSnapshot.tree this.xmlTree = nextSnapshot.tree
this.rebuildNodeMap() this.rebuildNodeMap()
if (nextSnapshot.selectedNodeId && this.nodeMap.has(nextSnapshot.selectedNodeId)) { if (nextSnapshot.selectedNodeId && this.hasNode(nextSnapshot.selectedNodeId)) {
this.setSelectedNodeId(nextSnapshot.selectedNodeId) this.setSelectedNodeId(nextSnapshot.selectedNodeId)
} else { } else {
this.setSelectedNodeId(nextSnapshot.tree.id) this.setSelectedNodeId(nextSnapshot.tree.id)
......
...@@ -451,7 +451,7 @@ export function useEditAreaContextMenu(props: EditAreaContextMenuProps, emit: (e ...@@ -451,7 +451,7 @@ export function useEditAreaContextMenu(props: EditAreaContextMenuProps, emit: (e
editorStore.deleteNode(realId) editorStore.deleteNode(realId)
window.$message.success('节点删除成功') window.$message.success('节点删除成功')
if (editorStore.selectedNodeId && editorStore.nodeMap.has(editorStore.selectedNodeId)) { if (editorStore.selectedNodeId && editorStore.hasNode(editorStore.selectedNodeId)) {
activeNodeId.value = editorStore.selectedNodeId activeNodeId.value = editorStore.selectedNodeId
} else { } else {
emit('update:visible', false) // 若没有存活选中节点,则主动关闭当前弹窗 emit('update:visible', false) // 若没有存活选中节点,则主动关闭当前弹窗
......
...@@ -168,9 +168,21 @@ const handleContextMenu = (e: MouseEvent) => { ...@@ -168,9 +168,21 @@ const handleContextMenu = (e: MouseEvent) => {
const isVirtualText = rawNodeId.includes('-txt-') const isVirtualText = rawNodeId.includes('-txt-')
const realId = isVirtualText ? rawNodeId.split('-txt-')[0] : rawNodeId const realId = isVirtualText ? rawNodeId.split('-txt-')[0] : rawNodeId
// 区分单文本节点与多文本节点(混合内容):仅当存在多个文本片段时,才保留 -txt- 虚拟文本节点
let shouldKeepVirtualText = false
if (isVirtualText) {
const mapped = editorStore.nodeMap.get(realId)
if (mapped?.node?.mixedContent) {
const textItems = mapped.node.mixedContent.filter((m) => m.type === 'text' && (m.text || '').trim())
if (textItems.length > 1) {
shouldKeepVirtualText = true
}
}
}
// 向上回溯构建祖先节点 ID 链 // 向上回溯构建祖先节点 ID 链
const ids: string[] = [] const ids: string[] = []
if (isVirtualText) { if (isVirtualText && shouldKeepVirtualText) {
ids.push(rawNodeId) ids.push(rawNodeId)
} }
......
...@@ -168,8 +168,8 @@ ...@@ -168,8 +168,8 @@
</div> </div>
<div class="flex-1 flex gap-2 min-h-0"> <div class="flex-1 flex gap-2 min-h-0">
<!-- 左:源工卡 XML(屏蔽原生竖向滚动条,用自定义滚动条替代) --> <!-- 左:源工卡 XML(对比后屏蔽原生竖向滚动条,用自定义滚动条替代) -->
<div class="flex-1 min-w-0 h-full xml-compare-left flex relative"> <div class="flex-1 min-w-0 h-full xml-compare-left flex relative" :class="{ 'is-compared': compared }">
<div class="flex-1 min-w-0 h-full"> <div class="flex-1 min-w-0 h-full">
<CommonXmlEditor <CommonXmlEditor
ref="leftEditorRef" ref="leftEditorRef"
...@@ -206,7 +206,7 @@ ...@@ -206,7 +206,7 @@
]" ]"
/> />
</template> </template>
<!-- 可拖拽视口 thumb(有内容可滚动时才显示) --> <!-- 可拖拽视口 thumb(对比后有内容可滚动时才显示) -->
<div <div
v-if="compared && leftScrollerBound && isScrollable" v-if="compared && leftScrollerBound && isScrollable"
class="absolute left-0 right-0 rounded-sm custom-scrollbar-thumb-v" class="absolute left-0 right-0 rounded-sm custom-scrollbar-thumb-v"
...@@ -233,7 +233,7 @@ ...@@ -233,7 +233,7 @@
</div> </div>
<!-- 右:目标工卡 XML(同上) --> <!-- 右:目标工卡 XML(同上) -->
<div class="flex-1 min-w-0 h-full xml-compare-right flex relative"> <div class="flex-1 min-w-0 h-full xml-compare-right flex relative" :class="{ 'is-compared': compared }">
<div class="flex-1 min-w-0 h-full"> <div class="flex-1 min-w-0 h-full">
<CommonXmlEditor <CommonXmlEditor
ref="rightEditorRef" ref="rightEditorRef"
...@@ -270,7 +270,7 @@ ...@@ -270,7 +270,7 @@
]" ]"
/> />
</template> </template>
<!-- 可拖拽视口 thumb(有内容可滚动时才显示) --> <!-- 可拖拽视口 thumb(对比后有内容可滚动时才显示) -->
<div <div
v-if="compared && leftScrollerBound && isScrollable" v-if="compared && leftScrollerBound && isScrollable"
class="absolute left-0 right-0 rounded-sm custom-scrollbar-thumb-v" class="absolute left-0 right-0 rounded-sm custom-scrollbar-thumb-v"
...@@ -603,9 +603,9 @@ defineExpose({ ...@@ -603,9 +603,9 @@ defineExpose({
border-color: var(--n-scrollbar-color-hover, rgba(128, 128, 128, 0.5)) !important; border-color: var(--n-scrollbar-color-hover, rgba(128, 128, 128, 0.5)) !important;
} }
// 隐藏 CodeMirror 原生竖向滚动条(用自定义滚动条替代) // 对比后隐藏 CodeMirror 原生竖向滚动条(用自定义滚动条替代),未对比时保留原生滚动条
.xml-compare-left, .xml-compare-left.is-compared,
.xml-compare-right { .xml-compare-right.is-compared {
:deep(.cm-scroller) { :deep(.cm-scroller) {
scrollbar-width: none; scrollbar-width: none;
&::-webkit-scrollbar { &::-webkit-scrollbar {
......
...@@ -8,16 +8,20 @@ ...@@ -8,16 +8,20 @@
<div class="flex items-center bg-fill-2 p-0.5 rounded border border-divider"> <div class="flex items-center bg-fill-2 p-0.5 rounded border border-divider">
<button <button
type="button" type="button"
class="px-2.5 h-6 text-xs font-semibold rounded transition-all select-none focus:outline-none" class="px-2.5 h-6 text-xs font-semibold rounded transition-[background-color,color] select-none focus:outline-none"
:class="!insertBelow ? 'bg-card text-primary shadow-[0_1px_2px_rgba(0,0,0,0.06)]' : 'text-color2 hover:bg-fill-3'" :style="!insertBelow
? 'background: var(--card-color); box-shadow: 0 1px 3px rgba(0,0,0,0.12); color: var(--colorText1)'
: 'background: transparent; color: var(--colorText3)'"
@click="insertBelow = false" @click="insertBelow = false"
> >
内部 内部
</button> </button>
<button <button
type="button" type="button"
class="px-2.5 h-6 text-xs font-semibold rounded transition-all select-none focus:outline-none" class="px-2.5 h-6 text-xs font-semibold rounded transition-[background-color,color] select-none focus:outline-none hover:bg-fill-3"
:class="insertBelow ? 'bg-card text-primary shadow-[0_1px_2px_rgba(0,0,0,0.06)]' : 'text-color2 hover:bg-fill-3'" :style="insertBelow
? 'background: var(--card-color); box-shadow: 0 1px 3px rgba(0,0,0,0.12); color: var(--colorText1)'
: 'background: transparent; color: var(--colorText3)'"
@click="insertBelow = true" @click="insertBelow = true"
> >
下方 下方
......
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