Commit 0a57dc1b by pangchong

style: 优化REFBLOCK子项的分隔符,隐藏重复的ALL适用性标签并优化HTML清理逻辑

parent 082136f7
...@@ -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 为中文段落)
......
<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
......
...@@ -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 = '测量值:____ 毫米'
......
...@@ -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,
......
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 }
] ]
...@@ -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>
......
...@@ -210,7 +210,7 @@ export function useTableEditor(props: { node: XmlNode }) { ...@@ -210,7 +210,7 @@ export function useTableEditor(props: { node: XmlNode }) {
}) })
// 解析文字 // 解析文字
let paragraphs: CellParagraphModel[] = entryNode.children const 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,
......
...@@ -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>
......
...@@ -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()
......
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