Commit 9c439bf5 by pangchong

refactor(editor): 优化标签排序及公共标签组件替换

- 统一使用 CHINESE_FIRST_PARA_TAGS 替代硬编码的 ['PARAC', 'PARA'] 标签顺序
- 在表格结构初始化和单元格子元素排序中使用 CHINESE_FIRST_PARA_TAGS
- 批量翻译模块采用 TRANSLATE_TARGET_TAGS 作为默认目标标签列表
- 替换多处 vue 组件中 n-tag 为 CommonTag 以统一样式和功能
- 添加编辑器相关弹窗组件的 ref 暴露,简化调用逻辑
- 清理和优化部分 API 成功判断逻辑,移除无效判断条件
- 统一 viewXmlModal 和 checkRuleModal 等弹窗的打开方法调用
- 修复滚动条样式使用 CSS 变量,增强主题适配
- 删除无用或多余的 import 及属性声明,提高代码整洁度
parent f837beb0
......@@ -53,9 +53,6 @@ const createService = (baseURL: string) => {
if (
json.code === 200 ||
json.code === '200' ||
json.code === 0 ||
json.code === '0' ||
json.success === true ||
(json.code === undefined && json.success === undefined)
) {
json.code = 200
......@@ -86,9 +83,6 @@ const createService = (baseURL: string) => {
if (
json.code === 200 ||
json.code === '200' ||
json.code === 0 ||
json.code === '0' ||
json.success === true ||
(json.code === undefined && json.success === undefined)
) {
json.code = 200
......@@ -307,7 +301,7 @@ const createService = (baseURL: string) => {
// 情况 B:返回的是 JSON 对象(预检查成功)
const resData = result as any
if (resData && typeof resData === 'object' && (resData.code === 200 || resData.code === 0 || resData.code === '200')) {
if (resData && typeof resData === 'object' && (resData.code === 200 || resData.code === '200')) {
// 如果已经是带 down=Y 的请求返回了 JSON(可能是某些特殊接口),则不再重试
if (data?.down === 'Y') return true
......
......@@ -86,7 +86,7 @@
</template>
<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
import { useEditorStore } from '@/store/editor'
import type { XmlNode } from '@/types/xmlNode'
......
......@@ -137,3 +137,10 @@ export const STRUCTURAL_TAGS = [
'WARNING', 'CAUTION', 'NOTE', 'HNANOTE',
'CBSUBLST', 'GRAPHIC', 'FTNOTE', 'APPEND'
]
// 中文段落优先的标签排序列表
export const CHINESE_FIRST_PARA_TAGS = ['PARAC', 'PARA']
// 默认翻译目标标签列表
export const TRANSLATE_TARGET_TAGS = ['PARAC', 'TITLEC']
import { markRaw, nextTick, type Ref } from 'vue'
import type { Ref } from 'vue'
import type { XmlNode } from '@/types/xmlNode'
import type { EditorState } from './types'
import { createDefaultAttributes, canAddChild } from '@/utils/dtdManager'
import { parseXmlToTree } from '@/utils/xmlParser'
import { useAppStore } from '@/store/app'
import { CHINESE_FIRST_PARA_TAGS } from '@/configs/xmlTags'
export const nodeSelectedRefs = new Map<string, Ref<boolean>>()
......@@ -41,7 +42,7 @@ export const createTableStructure = (rows: number, cols: number, cellChildTags:
if (cellChildTags && cellChildTags.length > 0) {
// 按照 PARAC -> PARA 的顺序插入,以确保 PARAC 始终在 PARA 节点的前面
const order = ['PARAC', 'PARA']
const order = CHINESE_FIRST_PARA_TAGS
for (const tag of order) {
if (cellChildTags.includes(tag)) {
const childId = crypto.randomUUID()
......@@ -167,7 +168,7 @@ const createDefaultNodeStructure = (tagName: string): XmlNode => {
const attributes = createDefaultAttributes(tagName)
if (tagName === 'TABLE') {
return createTableStructure(3, 3, ['PARAC', 'PARA'])
return createTableStructure(3, 3, CHINESE_FIRST_PARA_TAGS)
}
if (tagName === 'GRAPHIC') {
......
......@@ -409,7 +409,7 @@
<n-icon><image-outline /></n-icon>
<span>工卡附图 [GNBR: {{ node.children.find((c) => c.tagName === 'SHEET')?.attributes.GNBR || '无' }}]</span>
</span>
<n-tag size="small" type="primary">GRAPHIC</n-tag>
<CommonTag size="small" type="primary">GRAPHIC</CommonTag>
</div>
<!-- 拟物化卡片模拟设计图 -->
<div
......@@ -442,7 +442,7 @@
<!-- 20. 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">
<n-tag type="info" size="small" round>记录项</n-tag>
<CommonTag type="info" size="small" round>记录项</CommonTag>
<div
contenteditable="true"
class="flex-1 text-sm text-color2 font-medium focus:outline-none focus:bg-fill-2 px-1 rounded"
......@@ -457,7 +457,7 @@
<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="flex items-center space-x-2 flex-1">
<n-tag type="success" size="small" round>单位记录项</n-tag>
<CommonTag type="success" size="small" round>单位记录项</CommonTag>
<div
contenteditable="true"
class="flex-1 text-sm text-color2 font-medium focus:outline-none focus:bg-fill-2 px-1 rounded"
......
......@@ -162,10 +162,10 @@ const handleContextMenu = (e: MouseEvent) => {
background: transparent;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background: #e0e0e0;
background: var(--divider-color, #e0e0e0);
border-radius: 2px;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
background: #18a058;
background: var(--primary-color, #18a058);
}
</style>
import { type TranslateXmlResponse, type TranslationStatusResponse } from '../constants'
import { useEditorStore } from '@/store/editor'
import { TRANSLATE_TARGET_TAGS } from '@/configs/xmlTags'
export function useBatchTranslate() {
const editorStore = useEditorStore()
......@@ -20,7 +21,7 @@ export function useBatchTranslate() {
logs.value.push({ time, text })
}
const availableTags = ref<string[]>(['PARAC', 'TITLEC'])
const availableTags = ref<string[]>([...TRANSLATE_TARGET_TAGS])
const availableTagOptions = computed(() => {
return availableTags.value.map((tag) => ({ label: tag, value: tag }))
})
......@@ -28,7 +29,7 @@ export function useBatchTranslate() {
const form = ref({
model_name: 'glm-4-flash',
search_direction: 'en_to_zh',
target_tags: ['PARAC', 'TITLEC'],
target_tags: [...TRANSLATE_TARGET_TAGS],
suffix: '',
use_parallel_method: true,
save_to_database: false,
......
<template>
<CommonModal v-model="showModal" title="批量 XML 翻译" :width="600" :show-confirm="false" :show-cancel="false">
<template #header-extra>
<n-tag :type="statusType" size="small">{{ statusLabel }}</n-tag>
<CommonTag :type="statusType" size="small">{{ statusLabel }}</CommonTag>
</template>
<n-form v-if="!loading && !completed" label-placement="left" label-width="120" size="medium">
......@@ -99,7 +99,7 @@
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import { CheckmarkCircleOutline } from '@vicons/ionicons5'
import { useBatchTranslate } from './functionals'
......
......@@ -43,9 +43,8 @@
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import CommonCheckbox from '@/components/CommonCheckbox.vue'
import { CHINESE_FIRST_PARA_TAGS } from '@/configs/xmlTags'
const emit = defineEmits<{
confirm: [rows: number, cols: number, cellChildTags: string[]]
......@@ -62,7 +61,7 @@ const formRef = ref<FormInst | null>(null)
const form = reactive({
rows: 3,
cols: 3,
cellChildTags: ['PARAC', 'PARA']
cellChildTags: [...CHINESE_FIRST_PARA_TAGS]
})
const rules: FormRules = {
......@@ -73,7 +72,7 @@ const rules: FormRules = {
const open = () => {
form.rows = 3
form.cols = 3
form.cellChildTags = ['PARAC', 'PARA']
form.cellChildTags = [...CHINESE_FIRST_PARA_TAGS]
show.value = true
}
......
......@@ -12,7 +12,6 @@
placeholder="输入要查询的技术英语文本或中文词汇..."
size="large"
class="flex-1"
clearable
@keyup.enter="handleSearch"
>
<template #prefix>
......
......@@ -3,7 +3,7 @@
<!-- 顶部操作 -->
<div class="flex items-center justify-between pb-2 border-b border-divider">
<div class="flex items-center space-x-2">
<n-tag type="info" size="small">{{ node.tagName }}</n-tag>
<CommonTag type="info" size="small">{{ node.tagName }}</CommonTag>
<span class="text-xs text-color3">
{{ isOrdered ? '有序' : '无序' }}列表编辑器 (子项共: {{ listItems.length }} 个)
</span>
......
import type { FormInst } from 'naive-ui'
import { getElementAttributes, createDefaultAttributes, isTextOnlyElement, isMixedContentElement } from '@/utils/dtdManager'
import { nextTick } from 'vue'
import { useEditorStore } from '@/store/editor'
import { useAppStore } from '@/store/app'
import type { XmlNode, DtdAttribute } from '@/types/xmlNode'
......
......@@ -41,10 +41,19 @@
<script setup lang="ts">
import type { FormInst } from 'naive-ui'
import { addNodeVisible, addNodeMode } from '../../functionals'
import { addNodeVisible, addNodeMode, addNodeTargetId, addNodeAllowedTags } from '../../functionals'
import { useAddNodeModal } from './functionals'
const formRef = ref<FormInst | null>(null)
const { form, rules, saving, modalTitle, tagOptions, attributeDefs, showTextContentField, onTagChange, handleConfirm } = useAddNodeModal(formRef)
const open = (mode: 'child' | 'before' | 'after' | 'edit', targetId: string, allowedTags: string[]) => {
addNodeMode.value = mode
addNodeTargetId.value = targetId
addNodeAllowedTags.value = allowedTags
addNodeVisible.value = true
}
defineExpose({ open })
</script>
......@@ -7,7 +7,7 @@
confirm-text="确认删除选中节点"
@confirm="handleConfirm"
>
<div class="flex flex-col gap-4 py-1 text-xs max-h-[500px] overflow-y-auto pr-1">
<div class="flex flex-col gap-4 py-1 text-xs pr-1">
<span class="text-color2 text-sm">
系统检测到您选中的节点中,部分节点由于 DTD 规则要求无法删除。您可以选择勾选并删除其余合法节点:
</span>
......
......@@ -19,4 +19,17 @@ import { checkRuleVisible, checkRuleData } from '../../functionals'
import { useCheckRuleModal } from './functionals'
const { formattedRule } = useCheckRuleModal()
const open = (nodeName: string, rawModel: string, humanReadable: string, parsed: any) => {
checkRuleData.value = {
nodeName,
rawModel,
humanReadable,
parsed
}
checkRuleVisible.value = true
}
defineExpose({ open })
</script>
......@@ -8,9 +8,9 @@
>
<!-- 磨砂玻璃质感的顶部装饰条 -->
<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-red-500/80"></div>
<div class="w-3 h-3 rounded-full bg-yellow-500/80"></div>
<div class="w-3 h-3 rounded-full bg-green-500/80"></div>
<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">
......@@ -42,6 +42,14 @@ const handleCopyXml = async () => {
window.$message?.error('复制失败,请手动选择复制')
}
}
const open = (title: string, content: string) => {
viewXmlTitle.value = title
viewXmlContent.value = content
viewXmlVisible.value = true
}
defineExpose({ open })
</script>
<style scoped>
......
......@@ -28,12 +28,15 @@ import { STRUCTURAL_TAGS, WARNING_LIKE_TAGS, NOTE_AND_REF_TAGS, COLORED_TAGS } f
// ══════════════════════════════════════════════════════════
// 全局共享状态:查看规则弹窗
// ══════════════════════════════════════════════════════════
// 全局共享状态:查看规则弹窗
// ══════════════════════════════════════════════════════════
export const checkRuleVisible = ref(false)
export const checkRuleData = ref<CheckRuleData>({
nodeName: '',
rawModel: '',
humanReadable: ''
})
export const checkRuleModalRef = ref<any>(null)
// ══════════════════════════════════════════════════════════
// 全局共享状态:查看XML片段弹窗
......@@ -41,6 +44,7 @@ export const checkRuleData = ref<CheckRuleData>({
export const viewXmlVisible = ref(false)
export const viewXmlTitle = ref('')
export const viewXmlContent = ref('')
export const viewXmlModalRef = ref<any>(null)
// ══════════════════════════════════════════════════════════
// 全局共享状态:添加子节点 / 插入兄弟节点弹窗
......@@ -49,6 +53,7 @@ export const addNodeVisible = ref(false)
export const addNodeMode = ref<InsertMode>('child')
export const addNodeTargetId = ref<string>('')
export const addNodeAllowedTags = ref<string[]>([])
export const addNodeModalRef = ref<any>(null)
// 复制节点缓存
export const copyNodeCache = ref<XmlNode | null>(null)
......@@ -881,37 +886,27 @@ export function useNodeTree(
switch (key) {
// ── 查看XML ──
case 'viewXml': {
if (isVirtual) {
viewXmlTitle.value = `查看文本节点的 XML`
viewXmlContent.value = node.textContent
} else {
const xmlFragment = serializeTreeToXml(node)
viewXmlTitle.value = `查看节点 [${node.tagName}] 的 XML 片段`
viewXmlContent.value = xmlFragment
}
viewXmlVisible.value = true
const title = isVirtual ? `查看文本节点的 XML` : `查看节点 [${node.tagName}] 的 XML 片段`
const content = isVirtual ? node.textContent : serializeTreeToXml(node)
viewXmlModalRef.value?.open(title, content)
break
}
// ── 查看规则 ──
case 'checkRule': {
const rule = getElementRule(node.tagName)
checkRuleData.value = {
nodeName: node.tagName,
rawModel: rule?.contentModel.raw || '(#PCDATA)',
humanReadable: rule?.contentModel.humanReadable || '',
parsed: rule?.contentModel.parsed || null
}
checkRuleVisible.value = true
checkRuleModalRef.value?.open(
node.tagName,
rule?.contentModel.raw || '(#PCDATA)',
rule?.contentModel.humanReadable || '',
rule?.contentModel.parsed || null
)
break
}
// ── 编辑节点:打开属性编辑弹窗 ──
case 'editNode': {
addNodeTargetId.value = nodeId
addNodeMode.value = 'edit'
addNodeAllowedTags.value = [node.tagName]
addNodeVisible.value = true
addNodeModalRef.value?.open('edit', nodeId, [node.tagName])
break
}
......@@ -1169,10 +1164,7 @@ export function useNodeTree(
}
}
allowed = sortChildrenByDtd(node.tagName, allowed)
addNodeTargetId.value = nodeId
addNodeMode.value = 'child'
addNodeAllowedTags.value = allowed
addNodeVisible.value = true
addNodeModalRef.value?.open('child', nodeId, allowed)
break
}
......@@ -1189,10 +1181,7 @@ export function useNodeTree(
}
}
insertable = sortChildrenByDtd(parent.tagName, insertable)
addNodeTargetId.value = nodeId
addNodeMode.value = 'before'
addNodeAllowedTags.value = insertable
addNodeVisible.value = true
addNodeModalRef.value?.open('before', nodeId, insertable)
break
}
......@@ -1209,10 +1198,7 @@ export function useNodeTree(
}
}
insertable = sortChildrenByDtd(parent.tagName, insertable)
addNodeTargetId.value = nodeId
addNodeMode.value = 'after'
addNodeAllowedTags.value = insertable
addNodeVisible.value = true
addNodeModalRef.value?.open('after', nodeId, insertable)
break
}
......
......@@ -180,13 +180,13 @@
/>
<!-- 查看规则弹窗 -->
<CheckRuleModal />
<CheckRuleModal :ref="(el) => checkRuleModalRef = el" />
<!-- 查看XML片段弹窗 -->
<ViewXmlModal />
<ViewXmlModal :ref="(el) => viewXmlModalRef = el" />
<!-- 添加/插入节点弹窗 -->
<AddNodeModal />
<AddNodeModal :ref="(el) => addNodeModalRef = el" />
<!-- 插入 XML 片段弹窗 -->
<InsertFragmentModal ref="insertFragmentModalRef" />
......@@ -199,7 +199,7 @@
<script setup lang="ts">
import { SearchOutline, ListOutline, TrashOutline, CloseOutline, SyncOutline } from '@vicons/ionicons5'
import { useEditorStore } from '@/store/editor'
import { useNodeTree, checkRuleVisible, viewXmlVisible, addNodeVisible, translatingNodeId } from './functionals'
import { useNodeTree, checkRuleVisible, viewXmlVisible, addNodeVisible, translatingNodeId, checkRuleModalRef, viewXmlModalRef, addNodeModalRef } from './functionals'
import CheckRuleModal from './components/CheckRuleModal/index.vue'
import AddNodeModal from './components/AddNodeModal/index.vue'
import ViewXmlModal from './components/ViewXmlModal/index.vue'
......
......@@ -3,7 +3,7 @@ import { useAppStore } from '@/store/app'
import type { XmlNode } from '@/types/xmlNode'
import type { TableCellModel, TableRowModel, TableStructureModel, BatchModalRef, BatchActionMeta, CellParagraphModel } from '../constants'
import { ACTION_META } from '../constants'
import { COMPLEX_ENTRY_TAGS, PARA_TAGS } from '@/configs/xmlTags'
import { COMPLEX_ENTRY_TAGS, PARA_TAGS, CHINESE_FIRST_PARA_TAGS } from '@/configs/xmlTags'
const getDeepText = (node: XmlNode): string => {
if (node.mixedContent && node.mixedContent.length > 0) {
......@@ -304,7 +304,7 @@ export function useTableEditor(props: { node: XmlNode }) {
}
if (cellChildTags && cellChildTags.length > 0) {
// 按照 PARAC -> PARA 的顺序插入,以确保 PARAC 始终在 PARA 节点的前面
const order = ['PARAC', 'PARA']
const order = CHINESE_FIRST_PARA_TAGS
for (const tag of order) {
if (cellChildTags.includes(tag)) {
const childId = crypto.randomUUID()
......
......@@ -2,7 +2,7 @@
<div class="flex-1 flex flex-col p-4 space-y-4 overflow-auto bg-transparent min-h-0" @click.stop @click="closeContextMenu">
<!-- 表格工具栏 -->
<div class="flex items-center pb-2 border-b border-divider space-x-2">
<n-tag type="info" size="small">{{ node.tagName }}</n-tag>
<CommonTag type="info" size="small">{{ node.tagName }}</CommonTag>
<span class="text-xs text-color3">{{ CELL_EDIT_TIP }}</span>
<span class="text-xs text-color3 select-none">· 右键单元格可快速操作</span>
</div>
......
......@@ -3,7 +3,7 @@
<!-- 头部节点提示 -->
<div class="flex items-center justify-between pb-2 border-b border-divider">
<div class="flex items-center space-x-2">
<n-tag type="info" size="small">{{ node.tagName }}</n-tag>
<CommonTag type="info" size="small">{{ node.tagName }}</CommonTag>
<span class="text-xs text-color3">{{ dtdDescription }}</span>
</div>
<div v-if="isMixed" class="text-xs text-color3">混合内容节点(支持嵌入行内元素)</div>
......@@ -48,7 +48,7 @@
<!-- 1. 文本片段 -->
<div v-if="item.type === 'text'" class="flex-1 flex items-center space-x-2">
<n-tag size="small" type="success" class="flex-shrink-0">文本</n-tag>
<CommonTag size="small" type="success" class="flex-shrink-0">文本</CommonTag>
<div
contenteditable="true"
class="flex-1 min-h-[32px] px-2 py-1.5 rounded border border-divider focus:outline-none focus:ring-1 focus:ring-primary text-sm bg-fill-3 text-color2"
......@@ -61,9 +61,9 @@
<div v-else-if="item.type === 'element'" class="flex-1 flex flex-col space-y-2">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<n-tag size="small" type="warning" class="font-bold">
<CommonTag size="small" type="warning" class="font-bold">
{{ item.elementTagName }}
</n-tag>
</CommonTag>
<span class="text-xs text-color3">行内嵌入元素</span>
</div>
<div class="text-xs italic text-color3 max-w-md truncate">
......
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