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
0a57dc1b
Commit
0a57dc1b
authored
Jul 06, 2026
by
pangchong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
style: 优化REFBLOCK子项的分隔符,隐藏重复的ALL适用性标签并优化HTML清理逻辑
parent
082136f7
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
392 additions
and
296 deletions
+392
-296
src/configs/xmlTags.ts
+4
-1
src/layouts/components/SettingsDrawer.vue
+7
-5
src/store/editor/index.ts
+27
-1
src/views/editor/components/DocNodeRenderer/functionals/index.ts
+54
-59
src/views/editor/components/DocNodeRenderer/index.vue
+73
-33
src/views/editor/components/EditorToolbar/constants/index.ts
+3
-2
src/views/editor/components/EditorToolbar/index.vue
+209
-168
src/views/editor/components/NodeTree/components/ViewXmlModal/index.vue
+0
-7
src/views/editor/components/TableEditor/functionals/index.ts
+1
-18
src/views/editor/components/TableEditor/index.vue
+2
-2
src/views/editor/functionals/index.ts
+12
-0
No files found.
src/configs/xmlTags.ts
View file @
0a57dc1b
...
@@ -70,7 +70,10 @@ export const INLINE_ELEMENTS = [
...
@@ -70,7 +70,10 @@ export const INLINE_ELEMENTS = [
'REVEND'
,
'REVEND'
,
'EXPD'
,
'EXPD'
,
'EXPDNAME'
,
'EXPDNAME'
,
'ITEMNBR'
'ITEMNBR'
,
'RECORD'
,
'RECORD-ITEMS'
,
'RECORD-ITEM'
]
]
// 段落类节点标签(PARA 为英文段落,PARAC 为中文段落)
// 段落类节点标签(PARA 为英文段落,PARAC 为中文段落)
...
...
src/layouts/components/SettingsDrawer.vue
View file @
0a57dc1b
<
template
>
<
template
>
<!-- 触发按钮 -->
<!-- 触发按钮 -->
<CommonButton
quaternary
circle
@
click=
"appStore.settingsOpen = true"
>
<slot
name=
"trigger"
:open=
"() => appStore.settingsOpen = true"
>
<template
#
icon
>
<CommonButton
quaternary
circle
@
click=
"appStore.settingsOpen = true"
>
<n-icon><settings-outline
/></n-icon>
<template
#
icon
>
</
template
>
<n-icon><settings-outline
/></n-icon>
</CommonButton>
</
template
>
</CommonButton>
</slot>
<!-- 偏好设置抽屉 -->
<!-- 偏好设置抽屉 -->
<n-drawer
<n-drawer
...
...
src/store/editor/index.ts
View file @
0a57dc1b
...
@@ -284,8 +284,34 @@ const createDefaultNodeStructure = (tagName: string): XmlNode => {
...
@@ -284,8 +284,34 @@ const createDefaultNodeStructure = (tagName: string): XmlNode => {
}
}
}
}
if
(
tagName
===
'RECORD-LINE'
)
{
const
recordId
=
crypto
.
randomUUID
()
return
{
id
,
tagName
:
'RECORD-LINE'
,
attributes
:
{
...
attributes
},
children
:
[
{
id
:
recordId
,
tagName
:
'RECORD'
,
attributes
:
{
MULTI
:
'N'
,
MANDATORY
:
'Y'
,
'MES-TYPE'
:
'TEXT'
},
children
:
[],
textContent
:
''
,
mixedContent
:
[],
parentId
:
id
}
],
textContent
:
''
,
mixedContent
:
[],
parentId
:
null
}
}
let
defaultText
=
''
let
defaultText
=
''
if
(
tagName
===
'RECORD-LINE'
)
defaultText
=
'记录项:__________________'
if
(
tagName
===
'DATE'
)
defaultText
=
new
Date
().
toISOString
().
split
(
'T'
)[
0
]
if
(
tagName
===
'DATE'
)
defaultText
=
new
Date
().
toISOString
().
split
(
'T'
)[
0
]
if
(
tagName
===
'UNIT-RECORD'
)
defaultText
=
'测量值:____ 毫米'
if
(
tagName
===
'UNIT-RECORD'
)
defaultText
=
'测量值:____ 毫米'
...
...
src/views/editor/components/DocNodeRenderer/functionals/index.ts
View file @
0a57dc1b
...
@@ -56,64 +56,6 @@ export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | nu
...
@@ -56,64 +56,6 @@ export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | nu
provide
(
'insideChinese'
,
true
)
provide
(
'insideChinese'
,
true
)
}
}
const
shouldHideDuplicate
=
computed
(()
=>
{
const
node
=
props
.
node
const
parent
=
props
.
parent
if
(
!
parent
)
return
false
const
isParaPair
=
node
.
tagName
===
'PARA'
||
node
.
tagName
===
'PARAC'
const
isTitlePair
=
node
.
tagName
===
'TITLE'
||
node
.
tagName
===
'TITLEC'
if
(
!
isParaPair
&&
!
isTitlePair
)
return
false
const
siblingTagName
=
node
.
tagName
===
'PARA'
?
'PARAC'
:
node
.
tagName
===
'PARAC'
?
'PARA'
:
node
.
tagName
===
'TITLE'
?
'TITLEC'
:
'TITLE'
const
siblings
=
parent
.
children
.
filter
((
c
)
=>
c
.
tagName
===
siblingTagName
)
const
getEffectiveText
=
(
n
:
XmlNode
):
string
=>
{
if
(
n
.
textContent
)
return
n
.
textContent
.
trim
()
if
(
n
.
mixedContent
&&
n
.
mixedContent
.
length
>
0
)
{
return
n
.
mixedContent
.
filter
((
item
)
=>
item
.
type
===
'text'
)
.
map
((
item
)
=>
item
.
text
||
''
)
.
join
(
''
)
.
trim
()
}
if
(
n
.
children
&&
n
.
children
.
length
>
0
)
{
const
getDeep
=
(
nn
:
XmlNode
):
string
=>
{
if
(
nn
.
textContent
)
return
nn
.
textContent
.
trim
()
if
(
nn
.
mixedContent
&&
nn
.
mixedContent
.
length
>
0
)
{
return
nn
.
mixedContent
.
filter
((
item
)
=>
item
.
type
===
'text'
)
.
map
((
item
)
=>
item
.
text
||
''
)
.
join
(
''
)
.
trim
()
}
if
(
nn
.
children
&&
nn
.
children
.
length
>
0
)
{
return
nn
.
children
.
map
(
getDeep
).
join
(
''
).
trim
()
}
return
''
}
return
getDeep
(
n
)
}
return
''
}
const
currentEffectiveText
=
getEffectiveText
(
node
)
if
(
!
currentEffectiveText
)
return
false
const
hasDuplicateSibling
=
siblings
.
some
((
sib
)
=>
{
return
getEffectiveText
(
sib
)
===
currentEffectiveText
})
if
(
hasDuplicateSibling
)
{
// Hide the English tag (PARA or TITLE) and keep the Chinese one (PARAC or TITLEC)
if
(
node
.
tagName
===
'PARA'
||
node
.
tagName
===
'TITLE'
)
{
return
true
}
}
return
false
})
const
renderInline
=
computed
(()
=>
{
const
renderInline
=
computed
(()
=>
{
return
props
.
isInline
||
INLINE_ELEMENTS_SET
.
has
(
props
.
node
.
tagName
)
return
props
.
isInline
||
INLINE_ELEMENTS_SET
.
has
(
props
.
node
.
tagName
)
...
@@ -158,12 +100,65 @@ export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | nu
...
@@ -158,12 +100,65 @@ export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | nu
if
(
!
eff
)
return
'ALL'
if
(
!
eff
)
return
'ALL'
const
cleaned
=
eff
.
replace
(
/
\s
+/g
,
''
)
const
cleaned
=
eff
.
replace
(
/
\s
+/g
,
''
)
if
(
cleaned
===
'001999'
)
return
'ALL'
if
(
cleaned
===
'001999'
)
return
'ALL'
// 如果是纯数字,且长度是6的倍数
if
(
/^
\d
+$/
.
test
(
cleaned
)
&&
cleaned
.
length
%
6
===
0
)
{
const
parts
:
string
[]
=
[]
for
(
let
i
=
0
;
i
<
cleaned
.
length
;
i
+=
6
)
{
const
block
=
cleaned
.
substring
(
i
,
i
+
6
)
if
(
block
===
'001999'
)
{
parts
.
push
(
'ALL'
)
continue
}
const
start
=
block
.
substring
(
0
,
3
)
const
end
=
block
.
substring
(
3
)
if
(
start
===
end
)
{
parts
.
push
(
start
)
}
else
{
parts
.
push
(
`
${
start
}
-
${
end
}
`
)
}
}
return
parts
.
join
(
', '
)
}
// 如果是纯数字且长度是3位(单个飞机)
if
(
/^
\d{3}
$/
.
test
(
cleaned
))
{
return
cleaned
}
// 回退逻辑:如果长度为6
if
(
cleaned
.
length
===
6
)
{
if
(
cleaned
.
length
===
6
)
{
return
`
${
cleaned
.
substring
(
0
,
3
)}
-
${
cleaned
.
substring
(
3
)}
`
return
`
${
cleaned
.
substring
(
0
,
3
)}
-
${
cleaned
.
substring
(
3
)}
`
}
}
return
cleaned
return
cleaned
}
}
// 判断是否需要显示参考/引用前缀,若内容本身以引用词汇开头则不显示前缀
const
shouldShowPrefix
=
(
text
:
string
|
undefined
):
boolean
=>
{
if
(
!
text
)
return
true
const
trimmed
=
text
.
trim
().
toLowerCase
()
if
(
trimmed
.
startsWith
(
'refer'
)
||
trimmed
.
startsWith
(
'ref'
)
||
trimmed
.
startsWith
(
'see'
)
||
trimmed
.
startsWith
(
'fig'
)
||
trimmed
.
startsWith
(
'view'
)
)
{
return
false
}
if
(
trimmed
.
startsWith
(
'参考'
)
||
trimmed
.
startsWith
(
'参阅'
)
||
trimmed
.
startsWith
(
'参见'
)
||
trimmed
.
startsWith
(
'见'
)
||
trimmed
.
startsWith
(
'图'
)
)
{
return
false
}
return
true
}
// 罗马数字转换辅助
// 罗马数字转换辅助
const
romanize
=
(
num
:
number
):
string
=>
{
const
romanize
=
(
num
:
number
):
string
=>
{
let
roman
=
''
let
roman
=
''
...
@@ -434,8 +429,8 @@ export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | nu
...
@@ -434,8 +429,8 @@ export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | nu
isHeaderTag
,
isHeaderTag
,
isInsideAlert
,
isInsideAlert
,
isChineseContext
,
isChineseContext
,
shouldHideDuplicate
,
formatEff
,
formatEff
,
shouldShowPrefix
,
getListBullet
,
getListBullet
,
getTopicSeqNum
,
getTopicSeqNum
,
getTopicTitleNodes
,
getTopicTitleNodes
,
...
...
src/views/editor/components/DocNodeRenderer/index.vue
View file @
0a57dc1b
<
template
>
<
template
>
<component
<component
v-if=
"!shouldHideDuplicate"
:is=
"renderInline ? 'span' : 'div'"
:is=
"renderInline ? 'span' : 'div'"
:data-node-id=
"node.id"
:data-node-id=
"node.id"
:data-tag-name=
"node.tagName"
class=
"doc-node-wrapper relative transition-all"
class=
"doc-node-wrapper relative transition-all"
:class=
"[
:class=
"[
renderInline ? 'inline align-baseline mx-0.5' : 'block my-1',
renderInline ? 'inline align-baseline mx-0.5' : 'block my-1',
...
@@ -30,7 +30,7 @@
...
@@ -30,7 +30,7 @@
}"
}"
>
>
<span
<span
class=
"absolute left-0 top-0 font-bold underline select-none"
class=
"absolute left-0 top-0
pt-1 leading-relaxed
font-bold underline select-none"
:style=
"
{
:style=
"
{
color: node.tagName === 'WARNING' ? 'red' : node.tagName === 'CAUTION' ? '#ff6a00' : 'blue'
color: node.tagName === 'WARNING' ? 'red' : node.tagName === 'CAUTION' ? '#ff6a00' : 'blue'
}"
}"
...
@@ -45,16 +45,18 @@
...
@@ -45,16 +45,18 @@
<!-- 2. EFFECT / CONEFFECT (适用性) 特殊处理 -->
<!-- 2. EFFECT / CONEFFECT (适用性) 特殊处理 -->
<
template
v-else-if=
"node.tagName === 'EFFECT' || node.tagName === 'CONEFFECT'"
>
<
template
v-else-if=
"node.tagName === 'EFFECT' || node.tagName === 'CONEFFECT'"
>
<span
v-if=
"renderInline"
class=
"font-bold text-xs select-none py-1 uppercase mx-1 italic"
style=
"color: red"
>
<template
v-if=
"formatEff(node.attributes.EFFRG || node.textContent) !== 'ALL' || isSelected"
>
**
{{
node
.
tagName
===
'EFFECT'
?
'ON A/C'
:
'CONF'
}}
:
{{
formatEff
(
node
.
attributes
.
EFFRG
||
node
.
textContent
)
}}
<span
v-if=
"renderInline"
class=
"font-bold text-xs select-none py-1 uppercase mx-1 italic"
style=
"color: red"
>
</span>
**
{{
node
.
tagName
===
'EFFECT'
?
'ON A/C'
:
'CONF'
}}
:
{{
formatEff
(
node
.
attributes
.
EFFRG
||
node
.
textContent
)
}}
<div
</span>
v-else
<div
class=
"font-bold text-xs select-none py-1 uppercase my-1 border-t border-b border-dashed pl-1 italic"
v-else
style=
"color: red; border-color: rgba(255, 0, 0, 0.3)"
class=
"font-bold text-xs select-none py-1 uppercase my-1 border-t border-b border-dashed pl-1 italic"
>
style=
"color: red; border-color: rgba(255, 0, 0, 0.3)"
**
{{
node
.
tagName
===
'EFFECT'
?
'ON A/C'
:
'CONF'
}}
:
{{
formatEff
(
node
.
attributes
.
EFFRG
||
node
.
textContent
)
}}
>
</div>
**
{{
node
.
tagName
===
'EFFECT'
?
'ON A/C'
:
'CONF'
}}
:
{{
formatEff
(
node
.
attributes
.
EFFRG
||
node
.
textContent
)
}}
</div>
</
template
>
</template>
</template>
<!-- 3. SBEFF / SBEFFC (服务通告适用性) 处理 -->
<!-- 3. SBEFF / SBEFFC (服务通告适用性) 处理 -->
...
@@ -95,9 +97,13 @@
...
@@ -95,9 +97,13 @@
<!-- 6. PARA / PARAC 段落文本处理 -->
<!-- 6. PARA / PARAC 段落文本处理 -->
<
template
v-else-if=
"PARA_SET.has(node.tagName)"
>
<
template
v-else-if=
"PARA_SET.has(node.tagName)"
>
<div
<component
:is=
"renderInline ? 'span' : 'div'"
class=
"text-sm leading-relaxed py-1 flex flex-wrap items-center"
class=
"text-sm leading-relaxed py-1 flex flex-wrap items-center"
:class=
"[isInsideAlert ? 'text-inherit' : 'text-color2']"
:class=
"[
isInsideAlert ? 'text-inherit' : 'text-color2',
renderInline ? 'inline mx-0.5' : 'flex'
]"
:style=
"
{ justifyContent: 'var(--cell-align, flex-start)' }"
:style=
"
{ justifyContent: 'var(--cell-align, flex-start)' }"
>
>
<template
v-if=
"node.mixedContent && node.mixedContent.length > 0"
>
<template
v-if=
"node.mixedContent && node.mixedContent.length > 0"
>
...
@@ -138,23 +144,26 @@
...
@@ -138,23 +144,26 @@
</
template
>
</
template
>
<!-- 否则,如果是一个纯文本的段落,直接展示和编辑文本 -->
<!-- 否则,如果是一个纯文本的段落,直接展示和编辑文本 -->
<
template
v-else
>
<
template
v-else
>
<div
<component
:is=
"renderInline ? 'span' : 'div'"
contenteditable=
"true"
contenteditable=
"true"
class=
"w-full focus:outline-none focus:bg-fill-3 px-1 rounded transition-colors"
class=
"focus:outline-none focus:bg-fill-3 px-1 rounded transition-colors"
:class=
"[renderInline ? 'inline mx-0.5' : 'w-full']"
@
blur=
"handleTextBlur"
@
blur=
"handleTextBlur"
v-text=
"node.textContent"
v-text=
"node.textContent"
></
div
>
></
component
>
</
template
>
</
template
>
</
div
>
</
component
>
</template>
</template>
<!-- 7. REFBLOCK 特殊处理 -->
<!-- 7. REFBLOCK 特殊处理 -->
<
template
v-else-if=
"node.tagName === 'REFBLOCK'"
>
<
template
v-else-if=
"node.tagName === 'REFBLOCK'"
>
<span
class=
"inline-ref-block font-medium"
style=
"color: blue"
>
<span
class=
"inline-ref-block font-medium"
style=
"color: blue"
>
{{
isChineseContext
?
'(参考: '
:
'(Ref: '
}}
<template
v-if=
"node.mixedContent && node.mixedContent.length > 0"
>
<template
v-if=
"node.mixedContent && node.mixedContent.length > 0"
>
<template
v-for=
"(item, index) in node.mixedContent"
:key=
"index"
>
<template
v-for=
"(item, index) in node.mixedContent"
:key=
"index"
>
<span
<span
v-if=
"item.type === 'text'"
v-if=
"item.type === 'text'
&& item.text && item.text.trim()
"
:data-node-id=
"`$
{node.id}-txt-${index}`"
:data-node-id=
"`$
{node.id}-txt-${index}`"
contenteditable="true"
contenteditable="true"
class="focus:outline-none px-0.5 rounded inline transition-all"
class="focus:outline-none px-0.5 rounded inline transition-all"
...
@@ -187,6 +196,7 @@
...
@@ -187,6 +196,7 @@
v-text=
"node.textContent"
v-text=
"node.textContent"
></span>
></span>
</
template
>
</
template
>
{{ ')' }}
</span>
</span>
</template>
</template>
...
@@ -198,7 +208,7 @@
...
@@ -198,7 +208,7 @@
@
click
.
stop=
"editorStore.setSelectedNodeId(node.id)"
@
click
.
stop=
"editorStore.setSelectedNodeId(node.id)"
>
>
<template
v-if=
"!insideRefBlock"
>
<template
v-if=
"!insideRefBlock"
>
{{
isChineseContext
?
'(参考: '
:
'(Ref:
'
}}
{{
shouldShowPrefix
(
node
.
textContent
)
?
(
isChineseContext
?
'(参考: '
:
'(Ref: '
)
:
'(
'
}}
<template
v-if=
"node.mixedContent && node.mixedContent.length > 0"
>
<template
v-if=
"node.mixedContent && node.mixedContent.length > 0"
>
<template
v-for=
"(item, index) in node.mixedContent"
:key=
"index"
>
<template
v-for=
"(item, index) in node.mixedContent"
:key=
"index"
>
<span
<span
...
@@ -288,7 +298,7 @@
...
@@ -288,7 +298,7 @@
@
click
.
stop=
"editorStore.setSelectedNodeId(node.id)"
@
click
.
stop=
"editorStore.setSelectedNodeId(node.id)"
>
>
<template
v-if=
"!insideRefBlock"
>
<template
v-if=
"!insideRefBlock"
>
{{
isChineseContext
?
'(参考: '
:
'(Ref:
'
}}
{{
shouldShowPrefix
(
node
.
textContent
||
node
.
attributes
.
REFLOC
)
?
(
isChineseContext
?
'(参考: '
:
'(Ref: '
)
:
'(
'
}}
<span
<span
contenteditable=
"true"
contenteditable=
"true"
class=
"focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
class=
"focus:outline-none focus:bg-fill-3 px-0.5 rounded inline"
...
@@ -316,7 +326,7 @@
...
@@ -316,7 +326,7 @@
@
click
.
stop=
"editorStore.setSelectedNodeId(node.id)"
@
click
.
stop=
"editorStore.setSelectedNodeId(node.id)"
>
>
<template
v-if=
"!insideRefBlock"
>
<template
v-if=
"!insideRefBlock"
>
{{
isChineseContext
?
'(参考: '
:
'(Ref:
'
}}
{{
shouldShowPrefix
(
node
.
textContent
)
?
(
isChineseContext
?
'(参考: '
:
'(Ref: '
)
:
'(
'
}}
<template
v-if=
"node.mixedContent && node.mixedContent.length > 0"
>
<template
v-if=
"node.mixedContent && node.mixedContent.length > 0"
>
<template
v-for=
"(item, index) in node.mixedContent"
:key=
"index"
>
<template
v-for=
"(item, index) in node.mixedContent"
:key=
"index"
>
<span
<span
...
@@ -667,18 +677,44 @@
...
@@ -667,18 +677,44 @@
<!-- 20. RECORD-LINE (记录项) 处理 -->
<!-- 20. RECORD-LINE (记录项) 处理 -->
<
template
v-else-if=
"node.tagName === 'RECORD-LINE'"
>
<
template
v-else-if=
"node.tagName === 'RECORD-LINE'"
>
<div
class=
"my-2 p-2 bg-fill-3 rounded border border-divider flex items-center space-x-2"
>
<div
class=
"my-1 py-1"
>
<CommonTag
type=
"info"
size=
"small"
round
>
记录项
</CommonTag>
<div
class=
"flex flex-wrap items-center gap-1.5 text-sm text-color2"
>
<div
<template
v-if=
"node.children && node.children.length > 0"
>
contenteditable=
"true"
<DocNodeRenderer
class=
"flex-1 text-sm text-color2 font-medium focus:outline-none focus:bg-fill-2 px-1 rounded"
v-for=
"child in node.children"
@
blur=
"handleTextBlur"
:key=
"child.id"
v-text=
"node.textContent"
:node=
"child"
></div>
:parent=
"node"
<div
class=
"text-xs text-color3 select-none"
>
[已预留输入线]
</div>
is-inline
/>
</
template
>
<
template
v-else-if=
"node.textContent && node.textContent.trim()"
>
<span
contenteditable=
"true"
class=
"focus:outline-none focus:bg-fill-3 px-0.5 rounded"
@
blur=
"handleTextBlur"
v-text=
"node.textContent"
></span>
</
template
>
<span
v-else
class=
"text-xs text-color3 select-none"
>
[空记录项,请从左侧树或右键菜单插入子节点]
</span>
</div>
</div>
</div>
</template>
</template>
<!-- 20.5 RECORD (记录输入框) 处理 -->
<
template
v-else-if=
"node.tagName === 'RECORD'"
>
<span
contenteditable=
"true"
class=
"inline-block border-b border-color3 hover:border-primary focus:border-primary min-w-[80px] text-center focus:outline-none focus:bg-fill-2 px-1 mx-1 text-sm font-mono text-primary font-bold transition-all"
:style=
"
{
width: node.attributes.WIDTH ? (node.attributes.WIDTH.includes('px') || node.attributes.WIDTH.includes('%') ? node.attributes.WIDTH : `${node.attributes.WIDTH}px`) : 'auto',
minWidth: node.attributes.WIDTH ? '0' : '80px'
}"
@blur="handleTextBlur"
v-text="node.textContent"
>
</span>
</
template
>
<!-- 21. UNIT-RECORD (单位记录项) 处理 -->
<!-- 21. UNIT-RECORD (单位记录项) 处理 -->
<
template
v-else-if=
"node.tagName === 'UNIT-RECORD'"
>
<
template
v-else-if=
"node.tagName === 'UNIT-RECORD'"
>
<div
class=
"my-2 p-2 bg-fill-3 rounded border border-divider flex items-center justify-between"
>
<div
class=
"my-2 p-2 bg-fill-3 rounded border border-divider flex items-center justify-between"
>
...
@@ -907,7 +943,7 @@
...
@@ -907,7 +943,7 @@
<!--
结构上等同于
NOTE
,但含义不同,用不同的标签文字区分
-->
<!--
结构上等同于
NOTE
,但含义不同,用不同的标签文字区分
-->
<
template
v
-
else
-
if
=
"NOTE_VARIANT_SET.has(node.tagName)"
>
<
template
v
-
else
-
if
=
"NOTE_VARIANT_SET.has(node.tagName)"
>
<
div
class
=
"relative my-3 text-sm"
style
=
"padding-left: 80px; color: blue"
>
<
div
class
=
"relative my-3 text-sm"
style
=
"padding-left: 80px; color: blue"
>
<
span
class
=
"absolute left-0 top-0 font-bold underline select-none"
style
=
"color: blue"
>
<
span
class
=
"absolute left-0 top-0
pt-1 leading-relaxed
font-bold underline select-none"
style
=
"color: blue"
>
{{
{{
node
.
tagName
===
'HNANOTE'
node
.
tagName
===
'HNANOTE'
?
'操作注意 HNANOTE:'
?
'操作注意 HNANOTE:'
...
@@ -1017,8 +1053,8 @@ const {
...
@@ -1017,8 +1053,8 @@ const {
isHeaderTag
,
isHeaderTag
,
isInsideAlert
,
isInsideAlert
,
isChineseContext
,
isChineseContext
,
shouldHideDuplicate
,
formatEff
,
formatEff
,
shouldShowPrefix
,
getListBullet
,
getListBullet
,
getTopicSeqNum
,
getTopicSeqNum
,
getTopicTitleNodes
,
getTopicTitleNodes
,
...
@@ -1039,4 +1075,8 @@ const {
...
@@ -1039,4 +1075,8 @@ const {
.
doc
-
node
-
wrapper
:
hover
{
.
doc
-
node
-
wrapper
:
hover
{
background
-
color
:
var
(
--
n
-
color
-
hover
);
background
-
color
:
var
(
--
n
-
color
-
hover
);
}
}
.
inline
-
ref
-
block
>
*
:
not
(:
first
-
child
)::
before
{
content
:
", "
;
color
:
inherit
;
}
<
/style
>
<
/style
>
src/views/editor/components/EditorToolbar/constants/index.ts
View file @
0a57dc1b
import
{
ImageOutline
,
GridOutline
,
DocumentTextOutline
,
CreateOutline
}
from
'@vicons/ionicons5'
import
{
ImageOutline
,
GridOutline
,
DocumentTextOutline
,
CreateOutline
,
RemoveOutline
}
from
'@vicons/ionicons5'
/**
/**
* EditorToolbar 组件级静态常量
* EditorToolbar 组件级静态常量
...
@@ -9,5 +9,6 @@ export const GREEN_BUTTONS: any[] = [
...
@@ -9,5 +9,6 @@ export const GREEN_BUTTONS: any[] = [
// { label: '插入图片', tag: 'GRAPHIC', icon: ImageOutline },
// { label: '插入图片', tag: 'GRAPHIC', icon: ImageOutline },
{
label
:
'插入表格'
,
tag
:
'TABLE'
,
icon
:
GridOutline
},
{
label
:
'插入表格'
,
tag
:
'TABLE'
,
icon
:
GridOutline
},
// { label: '插入模板', tag: 'PRETOPIC', icon: DocumentTextOutline },
// { label: '插入模板', tag: 'PRETOPIC', icon: DocumentTextOutline },
{
label
:
'插入签字点'
,
tag
:
'SIGNOFF'
,
icon
:
CreateOutline
}
{
label
:
'插入签字点'
,
tag
:
'SIGNOFF'
,
icon
:
CreateOutline
},
{
label
:
'插入下划线'
,
tag
:
'RECORD-LINE'
,
icon
:
RemoveOutline
}
]
]
src/views/editor/components/EditorToolbar/index.vue
View file @
0a57dc1b
<
template
>
<
template
>
<div
<div
class=
"editor-toolbar flex flex-nowrap items-center px-
4 py-2 border-b border-divider bg-card select-none w-full overflow-x-auto overflow-y-hidden h-[54px]
"
class=
"editor-toolbar flex flex-nowrap items-center px-
3 border-b border-divider bg-card select-none w-full overflow-x-auto overflow-y-hidden h-[42px] gap-1.5
"
>
>
<!-- 左侧:内容插入操作区 (强制不收缩) -->
<!-- 组 1:插入位置模式切换 (仿富文本编辑器对齐选择器) -->
<div
class=
"flex items-center gap-2 flex-shrink-0"
>
<div
class=
"flex items-center gap-1.5 flex-shrink-0"
>
<!-- 插入位置设置 -->
<span
class=
"text-xs text-color3 font-medium"
>
插入模式
</span>
<div
class=
"flex items-center gap-1.5 bg-fill-3 px-2.5 py-1 rounded-md border border-divider text-xs flex-shrink-0"
>
<div
class=
"flex items-center bg-fill-2 p-0.5 rounded border border-divider"
>
<span
class=
"font-medium text-color3"
>
插入
</span>
<button
<span
class=
"font-semibold text-color2 w-8 text-center"
:class=
"insertBelow ? 'text-primary' : 'text-color2'"
>
type=
"button"
{{
insertBelow
?
'下方'
:
'内部'
}}
class=
"px-2.5 h-6 text-xs font-semibold rounded transition-all select-none focus:outline-none"
</span>
:class=
"!insertBelow ? 'bg-card text-primary shadow-[0_1px_2px_rgba(0,0,0,0.06)]' : 'text-color2 hover:bg-fill-3'"
<n-switch
v-model:value=
"insertBelow"
size=
"small"
/>
@
click=
"insertBelow = false"
</div>
<n-divider
vertical
class=
"!mx-0 flex-shrink-0"
/>
<!-- 插入元素按钮组 (这里同为 flex-shrink-0) -->
<div
class=
"flex items-center gap-1 flex-shrink-0"
>
<CommonButton
v-for=
"btn in GREEN_BUTTONS"
:key=
"btn.tag"
secondary
size=
"small"
class=
"insert-btn flex-shrink-0"
@
click=
"handleInsert(btn.tag)"
>
>
<template
#
icon
>
内部
<n-icon><component
:is=
"btn.icon"
/></n-icon>
</button>
</
template
>
<button
{{ btn.label.replace('插入', '') }}
type=
"button"
</CommonButton>
class=
"px-2.5 h-6 text-xs font-semibold rounded transition-all 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'"
<!-- 新增:插入 XML 片段按钮 -->
@
click=
"insertBelow = true"
<CommonButton
secondary
size=
"small"
class=
"insert-btn flex-shrink-0"
@
click=
"insertFragmentModalRef?.open(insertBelow)"
>
<
template
#
icon
>
<n-icon><code-working-outline
/></n-icon>
</
template
>
XML片段
</CommonButton>
</div>
</div>
<!-- 右侧:全局工具与管理操作区 (固定宽度,ml-auto 靠右,强制不收缩) -->
<div
class=
"flex items-center gap-1.5 flex-shrink-0 ml-auto"
>
<!-- 翻译辅助组 -->
<div
class=
"flex items-center gap-1 bg-fill-3 px-1 py-0.5 rounded-md border border-divider flex-shrink-0"
>
<CommonButton
size=
"small"
quaternary
class=
"util-btn"
:type=
"batchTranslateModalRef?.completed ? 'success' : batchTranslateModalRef?.loading ? 'primary' : 'default'"
@
click=
"handleTranslate('batch')"
>
>
<
template
#
icon
>
下方
<n-icon>
</button>
<sync-outline
v-if=
"batchTranslateModalRef?.loading"
class=
"animate-spin text-primary"
/>
<checkmark-circle-outline
v-else-if=
"batchTranslateModalRef?.completed"
class=
"text-success"
/>
<language-outline
v-else
/>
</n-icon>
</
template
>
<span
v-if=
"batchTranslateModalRef?.loading"
>
批量翻译 (进行中...)
</span>
<span
v-else-if=
"batchTranslateModalRef?.completed"
>
批量翻译 (已完成)
</span>
<span
v-else
>
批量翻译
</span>
</CommonButton>
<CommonButton
size=
"small"
quaternary
class=
"util-btn"
@
click=
"handleTranslate('extract')"
>
<
template
#
icon
>
<n-icon><download-outline
/></n-icon>
</
template
>
提取翻译
</CommonButton>
<CommonButton
size=
"small"
quaternary
class=
"util-btn"
@
click=
"handleTranslate('search')"
>
<
template
#
icon
>
<n-icon><search-outline
/></n-icon>
</
template
>
搜索翻译
</CommonButton>
</div>
</div>
</div>
<n-divider
vertical
class=
"!mx-0 flex-shrink-0"
/>
<div
class=
"w-[1px] h-4 bg-divider mx-1 flex-shrink-0"
></div>
<CommonButton
size=
"small"
secondary
class=
"xml-btn flex-shrink-0"
:disabled=
"isUploading"
:loading=
"isUploading"
@
click=
"triggerUpload"
>
<!-- 组 2:插入元素按钮组 -->
<
template
#
icon
>
<div
class=
"flex items-center gap-0.5 flex-shrink-0"
>
<n-icon><cloud-upload-outline
/></n-icon>
<button
</
template
>
v-for=
"btn in GREEN_BUTTONS"
{{ isUploading ? '解析中…' : '导入 XML' }}
:key=
"btn.tag"
</CommonButton>
type=
"button"
class=
"toolbar-btn flex items-center gap-1.5 px-2.5 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 transition-colors select-none focus:outline-none flex-shrink-0"
@
click=
"handleInsert(btn.tag)"
>
<n-icon
class=
"text-base"
><component
:is=
"btn.icon"
/></n-icon>
<span
class=
"text-xs font-medium"
>
{{
btn
.
label
.
replace
(
'插入'
,
''
)
}}
</span>
</button>
<!-- 插入 XML 片段 -->
<button
type=
"button"
class=
"toolbar-btn flex items-center gap-1.5 px-2.5 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 transition-colors select-none focus:outline-none flex-shrink-0"
@
click=
"insertFragmentModalRef?.open(insertBelow)"
>
<n-icon
class=
"text-base"
><code-working-outline
/></n-icon>
<span
class=
"text-xs font-medium"
>
XML片段
</span>
</button>
</div>
<CommonButton
size=
"small"
secondary
class=
"xml-btn flex-shrink-0"
@
click=
"emit('export')"
>
<div
class=
"w-[1px] h-4 bg-divider mx-1 flex-shrink-0"
></div>
<
template
#
icon
>
<n-icon><cloud-download-outline
/></n-icon>
<!-- 组 3:翻译辅助组 -->
</
template
>
<div
class=
"flex items-center gap-0.5 flex-shrink-0"
>
导出 XML
<button
</CommonButton>
type=
"button"
class=
"toolbar-btn flex items-center gap-1.5 px-2.5 h-7 rounded transition-colors select-none focus:outline-none flex-shrink-0"
:class=
"[
batchTranslateModalRef?.completed
? 'text-success bg-success/10 hover:bg-success/15'
: batchTranslateModalRef?.loading
? 'text-primary bg-primary/10 hover:bg-primary/15'
: 'text-color2 hover:bg-fill-2 active:bg-fill-3'
]"
@
click=
"handleTranslate('batch')"
>
<n-icon
class=
"text-base"
>
<sync-outline
v-if=
"batchTranslateModalRef?.loading"
class=
"animate-spin text-primary"
/>
<checkmark-circle-outline
v-else-if=
"batchTranslateModalRef?.completed"
class=
"text-success"
/>
<language-outline
v-else
/>
</n-icon>
<span
class=
"text-xs font-medium"
>
{{
batchTranslateModalRef
?.
loading
?
'翻译中…'
:
batchTranslateModalRef
?.
completed
?
'翻译完成'
:
'批量翻译'
}}
</span>
</button>
<button
type=
"button"
class=
"toolbar-btn flex items-center gap-1.5 px-2.5 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 transition-colors select-none focus:outline-none flex-shrink-0"
@
click=
"handleTranslate('extract')"
>
<n-icon
class=
"text-base"
><download-outline
/></n-icon>
<span
class=
"text-xs font-medium"
>
提取翻译
</span>
</button>
<button
type=
"button"
class=
"toolbar-btn flex items-center gap-1.5 px-2.5 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 transition-colors select-none focus:outline-none flex-shrink-0"
@
click=
"handleTranslate('search')"
>
<n-icon
class=
"text-base"
><search-outline
/></n-icon>
<span
class=
"text-xs font-medium"
>
搜索翻译
</span>
</button>
</div>
<CommonButton
size=
"small"
type=
"primary"
class=
"preview-btn flex-shrink-0"
@
click=
"emit('download-html')"
>
<div
class=
"w-[1px] h-4 bg-divider mx-1 flex-shrink-0"
></div>
<
template
#
icon
>
<n-icon><eye-outline
/></n-icon>
<!-- 组 4:文件管理与导入导出 -->
</
template
>
<div
class=
"flex items-center gap-0.5 flex-shrink-0"
>
下载html
<button
</CommonButton>
type=
"button"
<CommonButton
size=
"small"
type=
"primary"
class=
"preview-btn flex-shrink-0"
@
click=
"emit('preview')"
>
class=
"toolbar-btn flex items-center gap-1.5 px-2.5 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 transition-colors select-none focus:outline-none flex-shrink-0 disabled:opacity-50"
<
template
#
icon
>
:disabled=
"isUploading"
<n-icon><eye-outline
/></n-icon>
@
click=
"triggerUpload"
</
template
>
>
预览工卡
<n-icon
class=
"text-base"
>
</CommonButton>
<sync-outline
v-if=
"isUploading"
class=
"animate-spin"
/>
<cloud-upload-outline
v-else
/>
</n-icon>
<span
class=
"text-xs font-medium"
>
{{
isUploading
?
'导入中…'
:
'导入 XML'
}}
</span>
</button>
<button
type=
"button"
class=
"toolbar-btn flex items-center gap-1.5 px-2.5 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 transition-colors select-none focus:outline-none flex-shrink-0"
@
click=
"emit('export')"
>
<n-icon
class=
"text-base"
><cloud-download-outline
/></n-icon>
<span
class=
"text-xs font-medium"
>
导出 XML
</span>
</button>
<button
type=
"button"
class=
"toolbar-btn flex items-center gap-1.5 px-2.5 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 transition-colors select-none focus:outline-none flex-shrink-0"
@
click=
"emit('download-html')"
>
<n-icon
class=
"text-base"
><eye-outline
/></n-icon>
<span
class=
"text-xs font-medium"
>
下载html
</span>
</button>
<button
type=
"button"
class=
"flex items-center gap-1 px-3 h-7 rounded bg-primary text-white hover:bg-primary-hover active:bg-primary-pressed transition-colors select-none focus:outline-none flex-shrink-0 shadow-[0_1px_2px_rgba(0,0,0,0.08)]"
@
click=
"emit('preview')"
>
<n-icon
class=
"text-base"
><eye-outline
/></n-icon>
<span
class=
"text-xs font-semibold"
>
预览工卡
</span>
</button>
</div>
<n-divider
vertical
class=
"!mx-0 flex-shrink-0"
/
>
<div
class=
"w-[1px] h-4 bg-divider mx-1 flex-shrink-0"
></div
>
<!-- 撤销 / 重做 -->
<!-- 组 5:历史操作 (撤销 / 重做) -->
<n-tooltip
trigger=
"hover"
>
<div
class=
"flex items-center gap-0.5 flex-shrink-0"
>
<n-tooltip
trigger=
"hover"
:show-delay=
"600"
>
<template
#
trigger
>
<template
#
trigger
>
<CommonButton
size=
"small"
quaternary
:disabled=
"!canUndo"
@
click=
"editorStore.undo()"
class=
"flex-shrink-0"
>
<button
<template
#
icon
>
type=
"button"
<n-icon><arrow-undo-outline
/></n-icon>
class=
"toolbar-btn flex items-center justify-center w-7 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 disabled:opacity-40 disabled:hover:bg-transparent transition-colors select-none focus:outline-none flex-shrink-0"
</
template
>
:disabled=
"!canUndo"
后退
@
click=
"editorStore.undo()"
</CommonButton>
>
<n-icon
class=
"text-base"
><arrow-undo-outline
/></n-icon>
</button>
</
template
>
</
template
>
撤销 (Ctrl+Z)
撤销 (Ctrl+Z)
</n-tooltip>
</n-tooltip>
<n-tooltip
trigger=
"hover"
>
<n-tooltip
trigger=
"hover"
:show-delay=
"600"
>
<
template
#
trigger
>
<
template
#
trigger
>
<CommonButton
size=
"small"
quaternary
:disabled=
"!canRedo"
@
click=
"editorStore.redo()"
class=
"flex-shrink-0"
>
<button
<template
#
icon
>
type=
"button"
<n-icon><arrow-redo-outline
/></n-icon>
class=
"toolbar-btn flex items-center justify-center w-7 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 disabled:opacity-40 disabled:hover:bg-transparent transition-colors select-none focus:outline-none flex-shrink-0"
</
template
>
:disabled=
"!canRedo"
前进
@
click=
"editorStore.redo()"
</CommonButton>
>
<n-icon
class=
"text-base"
><arrow-redo-outline
/></n-icon>
</button>
</
template
>
</
template
>
重做 (Ctrl+Y)
重做 (Ctrl+Y)
</n-tooltip>
</n-tooltip>
</div>
<n-divider
vertical
class=
"!mx-0 flex-shrink-0"
/>
<!-- 组 6:系统工具与偏好设置 (靠右) -->
<div
class=
"flex items-center gap-1.5 flex-shrink-0 ml-auto"
>
<div
class=
"w-[1px] h-4 bg-divider mx-0.5 flex-shrink-0"
></div>
<!-- 主题切换 -->
<n-tooltip
trigger=
"hover"
:show-delay=
"600"
>
<n-tooltip
trigger=
"hover"
>
<
template
#
trigger
>
<
template
#
trigger
>
<CommonButton
size=
"small"
quaternary
circle
@
click=
"appStore.isDark = !appStore.isDark"
class=
"flex-shrink-0"
>
<button
<template
#
icon
>
type=
"button"
<n-icon>
class=
"toolbar-btn flex items-center justify-center w-7 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 transition-colors select-none focus:outline-none flex-shrink-0"
<sunny-outline
v-if=
"appStore.isDark"
/>
@
click=
"appStore.isDark = !appStore.isDark"
<moon-outline
v-else
/>
>
</n-icon>
<n-icon
class=
"text-base"
>
</
template
>
<sunny-outline
v-if=
"appStore.isDark"
/>
</CommonButton>
<moon-outline
v-else
/>
</n-icon>
</button>
</
template
>
</
template
>
{{ appStore.isDark ? '切换亮色主题' : '切换暗色主题' }}
{{ appStore.isDark ? '切换亮色主题' : '切换暗色主题' }}
</n-tooltip>
</n-tooltip>
<!-- 偏好设置 -->
<SettingsDrawer>
<div
class=
"flex-shrink-0 flex items-center"
>
<
template
#
trigger=
"{ open }"
>
<SettingsDrawer
/>
<n-tooltip
trigger=
"hover"
:show-delay=
"600"
>
</div>
<template
#
trigger
>
<button
type=
"button"
class=
"toolbar-btn flex items-center justify-center w-7 h-7 rounded text-color2 hover:bg-fill-2 active:bg-fill-3 transition-colors select-none focus:outline-none flex-shrink-0"
@
click=
"open"
>
<n-icon
class=
"text-base"
><settings-outline
/></n-icon>
</button>
</
template
>
偏好设置
</n-tooltip>
</template>
</SettingsDrawer>
</div>
</div>
<!-- 插入 XML 片段弹窗 -->
<!-- 插入 XML 片段弹窗 -->
...
@@ -190,7 +241,8 @@ import {
...
@@ -190,7 +241,8 @@ import {
MoonOutline
,
MoonOutline
,
CodeWorkingOutline
,
CodeWorkingOutline
,
SyncOutline
,
SyncOutline
,
CheckmarkCircleOutline
CheckmarkCircleOutline
,
SettingsOutline
}
from
'@vicons/ionicons5'
}
from
'@vicons/ionicons5'
import
{
useEditorToolbar
}
from
'./functionals'
import
{
useEditorToolbar
}
from
'./functionals'
import
{
GREEN_BUTTONS
}
from
'./constants'
import
{
GREEN_BUTTONS
}
from
'./constants'
...
@@ -228,7 +280,6 @@ const insertFragmentModalRef = ref<any>(null)
...
@@ -228,7 +280,6 @@ const insertFragmentModalRef = ref<any>(null)
</
script
>
</
script
>
<
style
scoped
>
<
style
scoped
>
/* 消除工具栏容器本身的 focus outline */
.editor-toolbar
{
.editor-toolbar
{
outline
:
none
;
outline
:
none
;
/* 允许横向滚动 */
/* 允许横向滚动 */
...
@@ -236,57 +287,47 @@ const insertFragmentModalRef = ref<any>(null)
...
@@ -236,57 +287,47 @@ const insertFragmentModalRef = ref<any>(null)
scrollbar-color
:
var
(
--n-scrollbar-color
,
rgba
(
0
,
0
,
0
,
0.2
))
transparent
;
scrollbar-color
:
var
(
--n-scrollbar-color
,
rgba
(
0
,
0
,
0
,
0.2
))
transparent
;
}
}
/* 整个工具栏滚动条自定义
:极其纤细,显眼且好看
*/
/* 整个工具栏滚动条自定义 */
.editor-toolbar
::-webkit-scrollbar
{
.editor-toolbar
::-webkit-scrollbar
{
height
:
5
px
;
height
:
4
px
;
}
}
.editor-toolbar
::-webkit-scrollbar-track
{
.editor-toolbar
::-webkit-scrollbar-track
{
background
:
color-mix
(
in
srgb
,
var
(
--divider-color
)
40%
,
transparent
);
background
:
transparent
;
border-radius
:
2.5px
;
}
}
.editor-toolbar
::-webkit-scrollbar-thumb
{
.editor-toolbar
::-webkit-scrollbar-thumb
{
background
:
var
(
--n-scrollbar-color
,
rgba
(
0
,
0
,
0
,
0.
2
));
background
:
var
(
--n-scrollbar-color
,
rgba
(
0
,
0
,
0
,
0.
15
));
border-radius
:
2
.5
px
;
border-radius
:
2px
;
}
}
.editor-toolbar
::-webkit-scrollbar-thumb:hover
{
.editor-toolbar
::-webkit-scrollbar-thumb:hover
{
background
:
var
(
--n-scrollbar-color-hover
,
rgba
(
0
,
0
,
0
,
0.4
));
background
:
var
(
--n-scrollbar-color-hover
,
rgba
(
0
,
0
,
0
,
0.35
));
}
/* 插入按钮:深色文字 + 更紧凑的圆角 */
.editor-toolbar
:deep
(
.insert-btn.n-button
)
{
--n-border-radius
:
6px
;
font-weight
:
600
;
font-size
:
12px
;
}
/* 翻译辅助按钮:去掉 margin,更紧凑 */
.editor-toolbar
:deep
(
.util-btn.n-button
)
{
--n-border-radius
:
6px
;
font-size
:
12px
;
}
}
/* XML 管理按钮 */
/* 消除工具栏中按钮聚焦或点击时由浏览器产生的原生高亮边框/外轮廓 */
.editor-toolbar
:deep
(
.xml-btn.n-button
)
{
.editor-toolbar
button
:focus
,
--n-border-radius
:
6px
;
.editor-toolbar
button
:focus-visible
,
font-size
:
12px
;
.editor-toolbar
button
:active
{
font-weight
:
600
;
outline
:
none
!important
;
border
:
none
!important
;
box-shadow
:
none
!important
;
}
}
/* 预览工卡按钮:突出主色 */
/* 富文本工具栏按钮通用样式 */
.editor-toolbar
:deep
(
.preview-btn.n-button
)
{
.toolbar-btn
{
--n-border-radius
:
6px
;
display
:
inline-flex
;
font-size
:
12px
;
align-items
:
center
;
font-weight
:
700
;
justify-content
:
center
;
background
:
transparent
;
border
:
none
;
cursor
:
pointer
;
transition
:
all
0.2s
cubic-bezier
(
0.4
,
0
,
0.2
,
1
);
}
}
/* 全局清除 n-button 间的默认 margin-right */
.toolbar-btn
:hover:not
(
:disabled
)
{
.editor-toolbar
:deep
(
.n-button
+
.n-button
)
{
background-color
:
var
(
--colorFill2
);
margin-right
:
0
!important
;
color
:
var
(
--colorText1
)
;
}
}
/* n-divider vertical 高度统一 */
.toolbar-btn
:active:not
(
:disabled
)
{
.editor-toolbar
:deep
(
.n-divider--vertical
)
{
background-color
:
var
(
--colorFill3
);
height
:
20px
;
margin
:
0
4px
;
}
}
</
style
>
</
style
>
src/views/editor/components/NodeTree/components/ViewXmlModal/index.vue
View file @
0a57dc1b
...
@@ -6,13 +6,6 @@
...
@@ -6,13 +6,6 @@
class=
"rounded-xl overflow-hidden border border-divider shadow-lg relative bg-fill-4"
class=
"rounded-xl overflow-hidden border border-divider shadow-lg relative bg-fill-4"
style=
"background-image: radial-gradient(circle, rgba(0, 0, 0, 0.02) 1px, transparent 1px); background-size: 16px 16px"
style=
"background-image: radial-gradient(circle, rgba(0, 0, 0, 0.02) 1px, transparent 1px); background-size: 16px 16px"
>
>
<!-- 磨砂玻璃质感的顶部装饰条 -->
<div
class=
"h-8 bg-fill-3 border-b border-divider flex items-center px-4 space-x-1.5 select-none shrink-0"
>
<div
class=
"w-3 h-3 rounded-full bg-danger/80"
></div>
<div
class=
"w-3 h-3 rounded-full bg-warning/80"
></div>
<div
class=
"w-3 h-3 rounded-full bg-success/80"
></div>
</div>
<div
class=
"p-6 font-mono text-sm leading-relaxed overflow-x-auto max-h-[500px] scrollbar-thin select-all"
>
<div
class=
"p-6 font-mono text-sm leading-relaxed overflow-x-auto max-h-[500px] scrollbar-thin select-all"
>
<pre
class=
"text-color1"
><code
class=
"xml-content-pre"
>
{{
viewXmlContent
}}
</code></pre>
<pre
class=
"text-color1"
><code
class=
"xml-content-pre"
>
{{
viewXmlContent
}}
</code></pre>
</div>
</div>
...
...
src/views/editor/components/TableEditor/functionals/index.ts
View file @
0a57dc1b
...
@@ -210,7 +210,7 @@ export function useTableEditor(props: { node: XmlNode }) {
...
@@ -210,7 +210,7 @@ export function useTableEditor(props: { node: XmlNode }) {
})
})
// 解析文字
// 解析文字
le
t
paragraphs
:
CellParagraphModel
[]
=
entryNode
.
children
cons
t
paragraphs
:
CellParagraphModel
[]
=
entryNode
.
children
.
filter
((
c
)
=>
PARA_TAGS
.
includes
(
c
.
tagName
))
.
filter
((
c
)
=>
PARA_TAGS
.
includes
(
c
.
tagName
))
.
map
((
pNode
)
=>
({
.
map
((
pNode
)
=>
({
id
:
pNode
.
id
,
id
:
pNode
.
id
,
...
@@ -219,23 +219,6 @@ export function useTableEditor(props: { node: XmlNode }) {
...
@@ -219,23 +219,6 @@ export function useTableEditor(props: { node: XmlNode }) {
attributes
:
pNode
.
attributes
attributes
:
pNode
.
attributes
}))
}))
const
filteredParagraphs
:
CellParagraphModel
[]
=
[]
for
(
let
i
=
0
;
i
<
paragraphs
.
length
;
i
++
)
{
const
current
=
paragraphs
[
i
]
const
isParaPair
=
current
.
tagName
===
'PARA'
||
current
.
tagName
===
'PARAC'
if
(
isParaPair
)
{
const
siblingTagName
=
current
.
tagName
===
'PARA'
?
'PARAC'
:
'PARA'
const
hasDuplicate
=
paragraphs
.
some
((
p
)
=>
{
return
p
.
tagName
===
siblingTagName
&&
p
.
text
.
trim
()
===
current
.
text
.
trim
()
})
if
(
hasDuplicate
&&
current
.
tagName
===
'PARA'
)
{
continue
}
}
filteredParagraphs
.
push
(
current
)
}
paragraphs
=
filteredParagraphs
if
(
paragraphs
.
length
===
0
&&
!
hasComplexChildren
)
{
if
(
paragraphs
.
length
===
0
&&
!
hasComplexChildren
)
{
paragraphs
.
push
({
paragraphs
.
push
({
id
:
entryNode
.
id
,
id
:
entryNode
.
id
,
...
...
src/views/editor/components/TableEditor/index.vue
View file @
0a57dc1b
...
@@ -78,7 +78,7 @@
...
@@ -78,7 +78,7 @@
@
click
.
stop=
"handleCellClick(cell, $event)"
@
click
.
stop=
"handleCellClick(cell, $event)"
@
contextmenu
.
prevent=
"handleCellContextMenu(cell, row.id, $event)"
@
contextmenu
.
prevent=
"handleCellContextMenu(cell, row.id, $event)"
>
>
<template
v-if=
"cell.
hasComplexChildren && cell.rawNode
"
>
<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"
/>
<DocNodeRenderer
v-for=
"child in cell.rawNode!.children"
:key=
"child.id"
:node=
"child"
:parent=
"cell.rawNode"
/>
</
template
>
</
template
>
<
template
v-else
>
<
template
v-else
>
...
@@ -169,7 +169,7 @@
...
@@ -169,7 +169,7 @@
@
click
.
stop=
"handleCellClick(cell, $event)"
@
click
.
stop=
"handleCellClick(cell, $event)"
@
contextmenu
.
prevent=
"handleCellContextMenu(cell, row.id, $event)"
@
contextmenu
.
prevent=
"handleCellContextMenu(cell, row.id, $event)"
>
>
<template
v-if=
"cell.
hasComplexChildren && cell.rawNode
"
>
<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"
/>
<DocNodeRenderer
v-for=
"child in cell.rawNode!.children"
:key=
"child.id"
:node=
"child"
:parent=
"cell.rawNode"
/>
</
template
>
</
template
>
<
template
v-else
>
<
template
v-else
>
...
...
src/views/editor/functionals/index.ts
View file @
0a57dc1b
...
@@ -119,6 +119,13 @@ export function useEditor() {
...
@@ -119,6 +119,13 @@ export function useEditor() {
el
.
remove
()
el
.
remove
()
})
})
// 0.5. 移除所有评估为 'ALL' 且非必填显示的 applicability 标签 (避免在导出的 HTML 中重复出现大量的 ** ON A/C: ALL)
clone
.
querySelectorAll
(
'[data-tag-name="EFFECT"], [data-tag-name="CONEFFECT"]'
).
forEach
((
el
)
=>
{
if
(
el
.
textContent
?.
includes
(
'ALL'
))
{
el
.
remove
()
}
})
// 1. 去除 contenteditable 属性,防止打印时显示光标或被编辑
// 1. 去除 contenteditable 属性,防止打印时显示光标或被编辑
clone
.
querySelectorAll
(
'[contenteditable]'
).
forEach
((
el
)
=>
{
clone
.
querySelectorAll
(
'[contenteditable]'
).
forEach
((
el
)
=>
{
el
.
removeAttribute
(
'contenteditable'
)
el
.
removeAttribute
(
'contenteditable'
)
...
@@ -198,6 +205,11 @@ export function useEditor() {
...
@@ -198,6 +205,11 @@ export function useEditor() {
}
}
})
})
// 移除 RECORD-LINE / UNIT-RECORD 外层的边框与背景色类名
clone
.
querySelectorAll
(
'[data-tag-name="RECORD-LINE"] > div, [data-tag-name="UNIT-RECORD"] > div'
).
forEach
((
el
)
=>
{
el
.
classList
.
remove
(
'bg-fill-3'
,
'border'
,
'border-divider'
,
'p-2'
,
'rounded'
)
})
// 5. 移除表格顶部的快捷操作工具栏
// 5. 移除表格顶部的快捷操作工具栏
clone
.
querySelectorAll
(
'.flex.items-center.pb-2.border-b'
).
forEach
((
el
)
=>
{
clone
.
querySelectorAll
(
'.flex.items-center.pb-2.border-b'
).
forEach
((
el
)
=>
{
el
.
remove
()
el
.
remove
()
...
...
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