Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
I
Ifar-Xml-Edtor
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
pangchong
Ifar-Xml-Edtor
Commits
5af0d0fb
Commit
5af0d0fb
authored
Jul 28, 2026
by
pangchong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 完善对比工卡逻辑
parent
d278bc3b
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
159 additions
and
12 deletions
+159
-12
src/components/CommonXmlEditor.vue
+57
-11
src/types/xmlNode.ts
+4
-0
src/views/editor/components/DocNodeRenderer/index.vue
+47
-0
src/views/editor/components/EditorToolbar/components/CompareModal/constants/index.ts
+1
-1
src/views/editor/components/EditorToolbar/components/CompareModal/functionals/index.ts
+0
-0
src/views/editor/components/EditorToolbar/components/CompareModal/index.vue
+0
-0
src/views/editor/components/TableEditor/index.vue
+50
-0
No files found.
src/components/CommonXmlEditor.vue
View file @
5af0d0fb
...
...
@@ -34,6 +34,13 @@ import { history, defaultKeymap, historyKeymap } from '@codemirror/commands'
import
{
searchKeymap
,
highlightSelectionMatches
,
openSearchPanel
,
search
,
SearchQuery
,
getSearchQuery
}
from
'@codemirror/search'
import
{
lintKeymap
}
from
'@codemirror/lint'
interface
InlineDiff
{
line
:
number
from
:
number
to
:
number
class
:
string
}
interface
Props
{
modelValue
:
string
disabled
?:
boolean
...
...
@@ -42,6 +49,7 @@ interface Props {
minHeight
?:
string
|
number
lineClasses
?:
string
[]
lineNumbers
?:
(
string
|
number
)[]
inlineDiffs
?:
InlineDiff
[]
}
const
props
=
withDefaults
(
defineProps
<
Props
>
(),
{
...
...
@@ -49,19 +57,33 @@ const props = withDefaults(defineProps<Props>(), {
disabled
:
false
,
minHeight
:
200
,
lineClasses
:
()
=>
[],
lineNumbers
:
()
=>
[]
lineNumbers
:
()
=>
[],
inlineDiffs
:
()
=>
[]
})
// ── 差异高亮与自定义行号的 CodeMirror 6 拓展实现 ──
const
setLineClassesEffect
=
StateEffect
.
define
<
string
[]
>
()
const
setLineClassesEffect
=
StateEffect
.
define
<
{
lineClasses
:
string
[];
inlineDiffs
:
InlineDiff
[]
}
>
()
function
getLineDecorations
(
lineClasses
:
string
[],
doc
:
any
)
{
function
getLineDecorations
(
lineClasses
:
string
[],
inlineDiffs
:
InlineDiff
[],
doc
:
any
)
{
const
builder
=
new
RangeSetBuilder
<
Decoration
>
()
// 将 inlineDiffs 按 line 分组
const
diffsByLine
=
new
Map
<
number
,
InlineDiff
[]
>
()
if
(
inlineDiffs
&&
inlineDiffs
.
length
>
0
)
{
for
(
const
item
of
inlineDiffs
)
{
if
(
!
diffsByLine
.
has
(
item
.
line
))
{
diffsByLine
.
set
(
item
.
line
,
[])
}
diffsByLine
.
get
(
item
.
line
)
!
.
push
(
item
)
}
}
for
(
let
i
=
1
;
i
<=
doc
.
lines
;
i
++
)
{
const
cls
=
lineClasses
[
i
-
1
]
const
line
=
doc
.
line
(
i
)
if
(
cls
)
{
const
line
=
doc
.
line
(
i
)
builder
.
add
(
line
.
from
,
line
.
from
,
...
...
@@ -70,19 +92,37 @@ function getLineDecorations(lineClasses: string[], doc: any) {
})
)
}
const
lineDiffs
=
diffsByLine
.
get
(
i
)
if
(
lineDiffs
&&
lineDiffs
.
length
>
0
)
{
lineDiffs
.
sort
((
a
,
b
)
=>
a
.
from
-
b
.
from
)
for
(
const
diff
of
lineDiffs
)
{
const
startPos
=
Math
.
min
(
line
.
from
+
diff
.
from
,
line
.
to
)
const
endPos
=
Math
.
min
(
line
.
from
+
diff
.
to
,
line
.
to
)
if
(
startPos
<
endPos
)
{
builder
.
add
(
startPos
,
endPos
,
Decoration
.
mark
({
class
:
diff
.
class
})
)
}
}
}
}
return
builder
.
finish
()
}
const
lineClassesField
=
StateField
.
define
<
DecorationSet
>
({
create
(
state
)
{
return
getLineDecorations
(
props
.
lineClasses
||
[],
state
.
doc
)
return
getLineDecorations
(
props
.
lineClasses
||
[],
props
.
inlineDiffs
||
[],
state
.
doc
)
},
update
(
deco
,
tr
)
{
deco
=
deco
.
map
(
tr
.
changes
)
for
(
const
effect
of
tr
.
effects
)
{
if
(
effect
.
is
(
setLineClassesEffect
))
{
deco
=
getLineDecorations
(
effect
.
value
,
tr
.
state
.
doc
)
deco
=
getLineDecorations
(
effect
.
value
.
lineClasses
,
effect
.
value
.
inlineDiffs
,
tr
.
state
.
doc
)
}
}
return
deco
...
...
@@ -675,7 +715,10 @@ watch(
})
if
(
props
.
lineClasses
&&
props
.
lineClasses
.
length
>
0
)
{
editorView
.
dispatch
({
effects
:
setLineClassesEffect
.
of
(
props
.
lineClasses
)
effects
:
setLineClassesEffect
.
of
({
lineClasses
:
props
.
lineClasses
,
inlineDiffs
:
props
.
inlineDiffs
||
[]
})
})
}
}
...
...
@@ -693,11 +736,14 @@ watch(
)
watch
(
()
=>
props
.
lineClasses
,
(
newClasses
)
=>
{
if
(
editorView
&&
newClasses
)
{
[()
=>
props
.
lineClasses
,
()
=>
props
.
inlineDiffs
]
,
(
[
newClasses
,
newDiffs
]
)
=>
{
if
(
editorView
)
{
editorView
.
dispatch
({
effects
:
setLineClassesEffect
.
of
(
newClasses
)
effects
:
setLineClassesEffect
.
of
({
lineClasses
:
newClasses
||
[],
inlineDiffs
:
newDiffs
||
[]
})
})
}
},
...
...
src/types/xmlNode.ts
View file @
5af0d0fb
...
...
@@ -19,6 +19,10 @@ export interface XmlNode {
parentId
:
string
|
null
/** 比对差异状态 */
diffStatus
?:
'added'
|
'removed'
|
'modified'
|
'none'
/** 是否包含属性变更 */
attrModified
?:
boolean
/** 具体变更的属性列表 */
attrDiffs
?:
{
key
:
string
;
oldVal
:
string
;
newVal
:
string
}[]
}
/**
...
...
src/views/editor/components/DocNodeRenderer/index.vue
View file @
5af0d0fb
...
...
@@ -4,6 +4,7 @@
:data-node-id=
"node.id"
:data-tag-name=
"node.tagName"
:data-diff-status=
"node.diffStatus"
:data-attr-modified=
"node.attrModified ? 'true' : undefined"
class=
"doc-node-wrapper relative transition-all"
:class=
"[
renderInline ? 'inline align-baseline mx-0.5' : 'block my-1',
...
...
@@ -27,6 +28,29 @@
</div>
</div>
</Transition>
<!-- 属性变更徽标:仅块级节点用 absolute 定位,行内节点已由黄色背景/diff边框标示 -->
<n-popover
v-if=
"!renderInline && node.attrDiffs && node.attrDiffs.length > 0"
trigger=
"hover"
placement=
"top-end"
style=
"max-width: 380px"
>
<template
#
trigger
>
<span
class=
"attr-diff-badge absolute -top-2 right-0 inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[10px] font-bold font-mono cursor-pointer select-none shadow-md z-20 pointer-events-auto transition-all hover:opacity-90"
style=
"background: #f59e0b; color: #fff; line-height: 1.2"
@
click
.
stop
>
🏷️
{{
node
.
tagName
}}
</span>
</
template
>
<div
class=
"p-2 space-y-1.5 text-xs font-mono select-none"
@
click
.
stop
>
<div
class=
"font-bold border-b border-divider pb-1 flex items-center gap-1"
>
<span>
🏷️
<
{{ node.tagName }}
>
属性变更明细
</span>
</div>
<div
v-for=
"diff in node.attrDiffs"
:key=
"diff.key"
class=
"flex items-center gap-1.5 flex-wrap"
>
<span
class=
"font-semibold"
>
{{ diff.key }}:
</span>
<span
class=
"line-through text-red-500 px-1 bg-red-500/10 rounded"
>
{{ diff.oldVal || '(空)' }}
</span>
<span
class=
"text-amber-500 font-bold"
>
➔
</span>
<span
class=
"text-emerald-600 dark:text-emerald-400 font-bold px-1 bg-emerald-500/10 rounded"
>
{{ diff.newVal || '(空)' }}
</span>
</div>
</div>
</n-popover>
<!-- 0. 各类 HEADER 节点特殊处理 (不展示) -->
<
template
v-if=
"isHeaderTag"
></
template
>
...
...
@@ -1326,6 +1350,29 @@
v
-
text
=
"node.textContent || node.tagName"
><
/div
>
<
/template
>
<!--
行内节点属性变更上标徽标:渲染在内容之后,不遮挡任何文字
-->
<
n
-
popover
v
-
if
=
"renderInline && node.attrDiffs && node.attrDiffs.length > 0"
trigger
=
"hover"
placement
=
"top"
style
=
"max-width: 380px"
>
<
template
#
trigger
>
<
sup
class
=
"inline-flex items-center justify-center cursor-pointer select-none rounded-full shadow-sm transition-transform hover:scale-125 ml-0.5"
style
=
"background: #f59e0b; color: #fff; font-size: 8px; width: 14px; height: 14px; vertical-align: super; line-height: 1"
@
click
.
stop
>
🏷
<
/sup
>
<
/template
>
<
div
class
=
"p-2 space-y-1.5 text-xs font-mono select-none"
@
click
.
stop
>
<
div
class
=
"font-bold border-b border-divider pb-1 flex items-center gap-1"
>
<
span
>
🏷️
&
lt
;{{
node
.
tagName
}}
&
gt
;
属性变更明细
<
/span
>
<
/div
>
<
div
v
-
for
=
"diff in node.attrDiffs"
:
key
=
"diff.key"
class
=
"flex items-center gap-1.5 flex-wrap"
>
<
span
class
=
"font-semibold"
>
{{
diff
.
key
}}
:
<
/span
>
<
span
class
=
"line-through text-red-500 px-1 bg-red-500/10 rounded"
>
{{
diff
.
oldVal
||
'(空)'
}}
<
/span
>
<
span
class
=
"text-amber-500 font-bold"
>
➔
<
/span
>
<
span
class
=
"text-emerald-600 dark:text-emerald-400 font-bold px-1 bg-emerald-500/10 rounded"
>
{{
diff
.
newVal
||
'(空)'
}}
<
/span
>
<
/div
>
<
/div
>
<
/n-popover
>
<
/component
>
<
/template
>
...
...
src/views/editor/components/EditorToolbar/components/CompareModal/constants/index.ts
View file @
5af0d0fb
...
...
@@ -15,6 +15,6 @@ export interface RenderDiffHunk {
// XML源码比对差异段样式模型
export
interface
XmlDiffHunk
{
type
:
'added'
|
'removed'
type
:
'added'
|
'removed'
|
'modified'
style
:
Record
<
string
,
string
>
}
src/views/editor/components/EditorToolbar/components/CompareModal/functionals/index.ts
View file @
5af0d0fb
This diff is collapsed.
Click to expand it.
src/views/editor/components/EditorToolbar/components/CompareModal/index.vue
View file @
5af0d0fb
This diff is collapsed.
Click to expand it.
src/views/editor/components/TableEditor/index.vue
View file @
5af0d0fb
...
...
@@ -78,6 +78,8 @@
:rowspan=
"cell.rowspan"
:data-node-id=
"cell.id"
data-tag-name=
"ENTRY"
:data-diff-status=
"cell.rawNode?.diffStatus"
:data-attr-modified=
"cell.rawNode?.attrModified ? 'true' : undefined"
class=
"p-2 text-left font-bold bg-fill-4 border border-divider text-color1 transition-colors relative"
:class=
"[
isDiffMode ? 'cursor-default' : 'cursor-pointer',
...
...
@@ -91,6 +93,29 @@
@
click
.
stop=
"isDiffMode ? null : handleCellClick(cell, $event)"
@
contextmenu
.
prevent=
"isDiffMode ? null : handleCellContextMenu(cell, row.id, $event)"
>
<!-- 属性变更徽标:absolute 定位在单元格右上角,不侵入内容流 -->
<n-popover
v-if=
"cell.rawNode?.attrDiffs && cell.rawNode.attrDiffs.length > 0"
trigger=
"hover"
placement=
"top-end"
style=
"max-width: 380px"
>
<template
#
trigger
>
<span
class=
"attr-diff-badge absolute -top-2 right-0 inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[10px] font-bold font-mono cursor-pointer select-none shadow-md z-20 pointer-events-auto transition-all hover:opacity-90"
style=
"background: #f59e0b; color: #fff; line-height: 1.2;"
@
click
.
stop
>
🏷️
{{
cell
.
rawNode
.
tagName
}}
</span>
</
template
>
<div
class=
"p-2 space-y-1.5 text-xs font-mono select-none"
@
click
.
stop
>
<div
class=
"font-bold border-b border-divider pb-1 flex items-center gap-1"
>
<span>
🏷️
<
{{ cell.rawNode.tagName }}
>
属性变更明细
</span>
</div>
<div
v-for=
"diff in cell.rawNode.attrDiffs"
:key=
"diff.key"
class=
"flex items-center gap-1.5 flex-wrap"
>
<span
class=
"font-semibold"
>
{{ diff.key }}:
</span>
<span
class=
"line-through text-red-500 px-1 bg-red-500/10 rounded"
>
{{ diff.oldVal || '(空)' }}
</span>
<span
class=
"text-amber-500 font-bold"
>
➔
</span>
<span
class=
"text-emerald-600 dark:text-emerald-400 font-bold px-1 bg-emerald-500/10 rounded"
>
{{ diff.newVal || '(空)' }}
</span>
</div>
</div>
</n-popover>
<
template
v-if=
"cell.rawNode && cell.rawNode.children && cell.rawNode.children.length > 0"
>
<DocNodeRenderer
v-for=
"child in cell.rawNode!.children"
:key=
"child.id"
:node=
"child"
:parent=
"cell.rawNode"
/>
</
template
>
...
...
@@ -190,6 +215,8 @@
:rowspan=
"cell.rowspan"
:data-node-id=
"cell.id"
data-tag-name=
"ENTRY"
:data-diff-status=
"cell.rawNode?.diffStatus"
:data-attr-modified=
"cell.rawNode?.attrModified ? 'true' : undefined"
class=
"p-2 border border-divider text-color2 transition-colors relative"
:class=
"[
isDiffMode ? 'cursor-default' : 'cursor-pointer',
...
...
@@ -203,6 +230,29 @@
@
click
.
stop=
"isDiffMode ? null : handleCellClick(cell, $event)"
@
contextmenu
.
prevent=
"isDiffMode ? null : handleCellContextMenu(cell, row.id, $event)"
>
<!-- 属性变更徽标:absolute 定位在单元格右上角,不侵入内容流 -->
<n-popover
v-if=
"cell.rawNode?.attrDiffs && cell.rawNode.attrDiffs.length > 0"
trigger=
"hover"
placement=
"top-end"
style=
"max-width: 380px"
>
<template
#
trigger
>
<span
class=
"attr-diff-badge absolute -top-2 right-0 inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded text-[10px] font-bold font-mono cursor-pointer select-none shadow-md z-20 pointer-events-auto transition-all hover:opacity-90"
style=
"background: #f59e0b; color: #fff; line-height: 1.2;"
@
click
.
stop
>
🏷️
{{
cell
.
rawNode
.
tagName
}}
</span>
</
template
>
<div
class=
"p-2 space-y-1.5 text-xs font-mono select-none"
@
click
.
stop
>
<div
class=
"font-bold border-b border-divider pb-1 flex items-center gap-1"
>
<span>
🏷️
<
{{ cell.rawNode.tagName }}
>
属性变更明细
</span>
</div>
<div
v-for=
"diff in cell.rawNode.attrDiffs"
:key=
"diff.key"
class=
"flex items-center gap-1.5 flex-wrap"
>
<span
class=
"font-semibold"
>
{{ diff.key }}:
</span>
<span
class=
"line-through text-red-500 px-1 bg-red-500/10 rounded"
>
{{ diff.oldVal || '(空)' }}
</span>
<span
class=
"text-amber-500 font-bold"
>
➔
</span>
<span
class=
"text-emerald-600 dark:text-emerald-400 font-bold px-1 bg-emerald-500/10 rounded"
>
{{ diff.newVal || '(空)' }}
</span>
</div>
</div>
</n-popover>
<
template
v-if=
"cell.rawNode && cell.rawNode.children && cell.rawNode.children.length > 0"
>
<DocNodeRenderer
v-for=
"child in cell.rawNode!.children"
:key=
"child.id"
:node=
"child"
:parent=
"cell.rawNode"
/>
</
template
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment