Commit dd486f2f by pangchong

feat(compareModal): 添加并显示比对内容变更数量

- 在CompareModal组件中增加了新增、删除和修改内容数量的显示
- 实现diffCounts计算属性统计新增、删除和修改的节点及文本项
- 优化xmlParser中state.currentLine递增逻辑,支持compact模式
- 在比对视图点击事件中添加调试日志输出来辅助排查问题
- 从StashModal中移除预览效果按钮,简化UI界面
parent af2fbfbe
...@@ -222,17 +222,23 @@ export function serializeTreeToXmlWithMapping( ...@@ -222,17 +222,23 @@ export function serializeTreeToXmlWithMapping(
} }
// 纯元素子节点 // 纯元素子节点
if (!compact) {
state.currentLine++ state.currentLine++
}
const childrenParts: string[] = [] const childrenParts: string[] = []
for (let i = 0; i < node.children.length; i++) { for (let i = 0; i < node.children.length; i++) {
const child = node.children[i] const child = node.children[i]
const res = serializeTreeToXmlWithMapping(child, indent + 1, compact, state, mapping) const res = serializeTreeToXmlWithMapping(child, indent + 1, compact, state, mapping)
childrenParts.push(res.xml) childrenParts.push(res.xml)
if (i < node.children.length - 1) { if (i < node.children.length - 1) {
if (!compact) {
state.currentLine++ state.currentLine++
} }
} }
}
if (!compact) {
state.currentLine++ state.currentLine++
}
const childrenXml = childrenParts.join(newline) const childrenXml = childrenParts.join(newline)
xml = `${pad}<${openTag}>${newline}${childrenXml}${newline}${pad}</${node.tagName}>` xml = `${pad}<${openTag}>${newline}${childrenXml}${newline}${pad}</${node.tagName}>`
......
import { openUploadModal } from '@/utils/render'
import { ref, computed, nextTick, watch, onBeforeUnmount } from 'vue'
import * as Diff from 'diff' import * as Diff from 'diff'
import { parseXmlToTree, serializeTreeToXml, serializeTreeToXmlWithMapping } from '@/utils/xmlParser'
import { useEditorStore } from '@/store/editor' import { useEditorStore } from '@/store/editor'
import type { XmlNode, MixedContentItem } from '@/types/xmlNode' import type { XmlNode, MixedContentItem } from '@/types/xmlNode'
import type { DiffStatusType, ScrollSide, RenderDiffHunk, XmlDiffHunk } from '../constants' import type { DiffStatusType, ScrollSide, RenderDiffHunk, XmlDiffHunk } from '../constants'
...@@ -376,6 +373,86 @@ export function useCompareModal() { ...@@ -376,6 +373,86 @@ export function useCompareModal() {
// 比对控制 // 比对控制
const compared = ref(false) const compared = ref(false)
const comparing = ref(false) // 比对进行中(加载状态) const comparing = ref(false) // 比对进行中(加载状态)
const diffCounts = computed(() => {
let added = 0
let removed = 0
let modified = 0
if (!compared.value) {
return { added, removed, modified }
}
// Traverse old tree for removed and modified
const visitOld = (node: XmlNode) => {
if (!node) return
if (node.diffStatus === 'removed') {
removed++
return // Do not recurse into children of removed node
}
if (node.diffStatus === 'modified') {
modified++
// Recurse into children of modified node
}
// Check mixedContent text items ONLY if the node itself is not modified/removed/added
if (node.diffStatus === 'none' && node.mixedContent) {
for (const item of node.mixedContent) {
if (item.type === 'text') {
if (item.diffStatus === 'removed') {
removed++
} else if (item.diffStatus === 'modified') {
modified++
}
}
}
}
if (node.children) {
for (const child of node.children) {
visitOld(child)
}
}
}
// Traverse new tree for added
const visitNew = (node: XmlNode) => {
if (!node) return
if (node.diffStatus === 'added') {
added++
return // Do not recurse into children of added node
}
// Check mixedContent text items ONLY if the node itself is not modified/removed/added
if (node.diffStatus === 'none' && node.mixedContent) {
for (const item of node.mixedContent) {
if (item.type === 'text') {
if (item.diffStatus === 'added') {
added++
}
}
}
}
if (node.children) {
for (const child of node.children) {
visitNew(child)
}
}
}
if (leftRenderTree.value) {
visitOld(leftRenderTree.value)
}
if (rightRenderTree.value) {
visitNew(rightRenderTree.value)
}
return { added, removed, modified }
})
const showXmlCompare = ref(true) const showXmlCompare = ref(true)
const showRenderCompare = ref(true) const showRenderCompare = ref(true)
...@@ -1173,6 +1250,13 @@ export function useCompareModal() { ...@@ -1173,6 +1250,13 @@ export function useCompareModal() {
} }
} }
console.log('[DEBUG Compare] handleXmlLineSelect:', {
side,
origLine,
targetNodeId,
minSpan
})
if (!targetNodeId) return if (!targetNodeId) return
activeNodeId.value = targetNodeId activeNodeId.value = targetNodeId
...@@ -1183,11 +1267,23 @@ export function useCompareModal() { ...@@ -1183,11 +1267,23 @@ export function useCompareModal() {
// 查找并高亮左侧与右侧排版区域中匹配的元素 // 查找并高亮左侧与右侧排版区域中匹配的元素
const targetLeft = leftRenderContainer.value?.querySelector(`[data-node-id^="${targetNodeId}"]`) as HTMLElement const targetLeft = leftRenderContainer.value?.querySelector(`[data-node-id^="${targetNodeId}"]`) as HTMLElement
console.log('[DEBUG Compare] targetLeft element:', targetLeft ? {
tagName: targetLeft.tagName,
nodeIdAttr: targetLeft.getAttribute('data-node-id'),
outerHTML: targetLeft.outerHTML.slice(0, 200)
} : 'null')
if (targetLeft) { if (targetLeft) {
targetLeft.classList.add('render-active-highlight') targetLeft.classList.add('render-active-highlight')
} }
const targetRight = rightRenderContainer.value?.querySelector(`[data-node-id^="${targetNodeId}"]`) as HTMLElement const targetRight = rightRenderContainer.value?.querySelector(`[data-node-id^="${targetNodeId}"]`) as HTMLElement
console.log('[DEBUG Compare] targetRight element:', targetRight ? {
tagName: targetRight.tagName,
nodeIdAttr: targetRight.getAttribute('data-node-id'),
outerHTML: targetRight.outerHTML.slice(0, 200)
} : 'null')
if (targetRight) { if (targetRight) {
targetRight.classList.add('render-active-highlight') targetRight.classList.add('render-active-highlight')
} }
...@@ -1338,6 +1434,7 @@ export function useCompareModal() { ...@@ -1338,6 +1434,7 @@ export function useCompareModal() {
rightOriginalToEditor, rightOriginalToEditor,
compared, compared,
comparing, comparing,
diffCounts,
showXmlCompare, showXmlCompare,
showRenderCompare, showRenderCompare,
leftRenderContainer, leftRenderContainer,
......
...@@ -22,6 +22,9 @@ ...@@ -22,6 +22,9 @@
+ +
</span> </span>
<span class="text-success font-medium">新增内容</span> <span class="text-success font-medium">新增内容</span>
<span v-if="compared" class="px-1.5 py-0.5 text-[10px] font-bold rounded bg-success/10 border border-success/30 text-success leading-none">
{{ diffCounts.added }}
</span>
</div> </div>
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
<span <span
...@@ -30,6 +33,9 @@ ...@@ -30,6 +33,9 @@
- -
</span> </span>
<span class="text-error font-medium line-through">删除内容</span> <span class="text-error font-medium line-through">删除内容</span>
<span v-if="compared" class="px-1.5 py-0.5 text-[10px] font-bold rounded bg-error/10 border border-error/30 text-error leading-none">
{{ diffCounts.removed }}
</span>
</div> </div>
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
<span <span
...@@ -38,6 +44,9 @@ ...@@ -38,6 +44,9 @@
~ ~
</span> </span>
<span class="text-warning font-medium">属性/文本变更</span> <span class="text-warning font-medium">属性/文本变更</span>
<span v-if="compared" class="px-1.5 py-0.5 text-[10px] font-bold rounded bg-warning/10 border border-warning/30 text-warning leading-none">
{{ diffCounts.modified }}
</span>
</div> </div>
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
<span class="w-3.5 h-3.5 rounded bg-fill-3 border border-divider/60 repeating-stripes"></span> <span class="w-3.5 h-3.5 rounded bg-fill-3 border border-divider/60 repeating-stripes"></span>
...@@ -365,6 +374,7 @@ const { ...@@ -365,6 +374,7 @@ const {
rightOriginalToEditor, rightOriginalToEditor,
compared, compared,
comparing, comparing,
diffCounts,
showXmlCompare, showXmlCompare,
showRenderCompare, showRenderCompare,
leftRenderContainer, leftRenderContainer,
......
...@@ -121,12 +121,6 @@ ...@@ -121,12 +121,6 @@
</div> </div>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<CommonButton size="small" type="info" secondary @click="handlePreviewEffect(activeItem)">
<template #icon>
<n-icon><EyeOutline /></n-icon>
</template>
预览效果
</CommonButton>
<CommonButton v-if="activeItem.type === 'full'" size="small" type="warning" secondary @click="handleExport(activeItem)"> <CommonButton v-if="activeItem.type === 'full'" size="small" type="warning" secondary @click="handleExport(activeItem)">
<template #icon> <template #icon>
<n-icon><DownloadOutline /></n-icon> <n-icon><DownloadOutline /></n-icon>
......
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