Commit 71679edb by pangchong

feat(editor-compare): 增强 XML 对比编辑与显示功能

- 对 XML 大小实时计算并显示在编辑工具栏
- 在比较模态框中新增 XML 对比与渲染对比区域的展开/收起开关,保证至少保留一个区域
- 实现 XML 文本对比区域多行范围选中编辑,支持保存与取消操作
- 优化差异行的显示逻辑,支持编辑状态下只显示相关行
- 美化差异条颜色并统一颜色变量,提高视觉一致性
- 隐藏浏览器默认滚动条,使用自定义模拟滑块提升用户体验
- 增加对比弹框头部隐藏样式,简化界面
- 动态调整对比文本和渲染区尺寸与透明度,添加平滑过渡动画
- 在表格编辑器中根据对比模式调整行列选中和交互样式,禁用对比模式下的选中效果
- 优化表格编辑器行、列和单元格相关的鼠标交互逻辑,避免对比模式触发不必要操作
parent 862f9ad6
......@@ -21,6 +21,11 @@ export function useCompareModal() {
const oldDiffLines = ref<DiffLine[]>([])
const newDiffLines = ref<DiffLine[]>([])
const editingOldRange = ref<{ start: number; end: number } | null>(null)
const editingNewRange = ref<{ start: number; end: number } | null>(null)
const editingOldRangeText = ref('')
const editingNewRangeText = ref('')
const updateTextDiff = () => {
const { leftLines, rightLines } = generateDoubleFolderDiff(oldXmlText.value, newXmlText.value)
oldDiffLines.value = leftLines
......@@ -91,6 +96,8 @@ export function useCompareModal() {
const resetDiff = () => {
oldDiffTree.value = null
newDiffTree.value = null
editingOldRange.value = null
editingNewRange.value = null
}
const open = (currentXml?: string) => {
......@@ -104,6 +111,8 @@ export function useCompareModal() {
isEditingNew.value = true
oldDiffLines.value = []
newDiffLines.value = []
editingOldRange.value = null
editingNewRange.value = null
// 自动拉取当前编辑器内容填充为旧版本
if (editorStore.xmlTree) {
......@@ -269,6 +278,124 @@ export function useCompareModal() {
updateTextDiff()
}
const getSelectedLineRange = (isNewSide: boolean): { start: number; end: number } | null => {
const selection = window.getSelection()
if (!selection || selection.isCollapsed || selection.rangeCount === 0) return null
const range = selection.getRangeAt(0)
const getLineNumFromNode = (node: Node | null): number | null => {
let el = node as HTMLElement | null
if (node && node.nodeType === Node.TEXT_NODE) {
el = node.parentElement
}
while (el) {
if (el.classList && el.classList.contains('diff-line-row')) {
const numAttr = el.getAttribute('data-line-num')
if (numAttr) {
const num = parseInt(numAttr, 10)
if (!isNaN(num)) return num
}
}
el = el.parentElement
}
return null
}
const startNum = getLineNumFromNode(range.startContainer)
const endNum = getLineNumFromNode(range.endContainer)
if (startNum !== null && endNum !== null) {
return {
start: Math.min(startNum, endNum),
end: Math.max(startNum, endNum)
}
}
return null
}
const toggleEditOld = () => {
if (isEditingOld.value) {
isEditingOld.value = false
return
}
if (editingOldRange.value) {
saveEditRange(false)
} else {
const range = getSelectedLineRange(false)
if (range) {
editingOldRange.value = range
const lines = oldXmlText.value.split('\n')
editingOldRangeText.value = lines.slice(range.start - 1, range.end).join('\n')
nextTick(() => {
const el = document.getElementById(`old-range-input-${range.start}`)
el?.focus()
})
} else {
window.$message.warning('请先在上方 XML 文本对比区用鼠标划词选中需要编辑的区域')
}
}
}
const toggleEditNew = () => {
if (isEditingNew.value) {
isEditingNew.value = false
return
}
if (editingNewRange.value) {
saveEditRange(true)
} else {
const range = getSelectedLineRange(true)
if (range) {
editingNewRange.value = range
const lines = newXmlText.value.split('\n')
editingNewRangeText.value = lines.slice(range.start - 1, range.end).join('\n')
nextTick(() => {
const el = document.getElementById(`new-range-input-${range.start}`)
el?.focus()
})
} else {
window.$message.warning('请先在上方 XML 文本对比区用鼠标划词选中需要编辑的区域')
}
}
}
const saveEditRange = (isNewSide: boolean) => {
if (isNewSide) {
if (!editingNewRange.value) return
const lines = newXmlText.value.split('\n')
const startIdx = editingNewRange.value.start - 1
const endIdx = editingNewRange.value.end - 1
const newSubLines = editingNewRangeText.value.split('\n')
lines.splice(startIdx, endIdx - startIdx + 1, ...newSubLines)
newXmlText.value = lines.join('\n')
editingNewRange.value = null
updateTextDiff()
handleCompare()
} else {
if (!editingOldRange.value) return
const lines = oldXmlText.value.split('\n')
const startIdx = editingOldRange.value.start - 1
const endIdx = editingOldRange.value.end - 1
const newSubLines = editingOldRangeText.value.split('\n')
lines.splice(startIdx, endIdx - startIdx + 1, ...newSubLines)
oldXmlText.value = lines.join('\n')
editingOldRange.value = null
updateTextDiff()
handleCompare()
}
}
const cancelEditRange = (isNewSide: boolean) => {
if (isNewSide) {
editingNewRange.value = null
} else {
editingOldRange.value = null
}
}
// 实时防抖比对与状态切换侦听
const debounce = <T extends (...args: any[]) => any>(fn: T, delay: number) => {
let timer: any = null
......@@ -775,7 +902,15 @@ export function useCompareModal() {
onLeftTextScroll,
onRightTextScroll,
onLeftScroll,
onRightScroll
onRightScroll,
editingOldRange,
editingNewRange,
editingOldRangeText,
editingNewRangeText,
toggleEditOld,
toggleEditNew,
saveEditRange,
cancelEditRange
}
}
......
......@@ -29,7 +29,21 @@
<span class="text-warning-6 dark:text-warning-5">属性变更 / 空白占位</span>
</span>
</div>
<div class="text-xs text-color3 italic">上方为 XML 文本对比(双击可切回编辑,失焦自动格式化),下方为工卡实时渲染差异</div>
<div class="flex items-center gap-4">
<div class="text-xs text-color3 italic">上方为 XML 文本对比,下方为工卡实时渲染差异</div>
<div class="w-[1px] h-4 bg-divider"></div>
<div class="flex items-center gap-3">
<div class="flex items-center gap-1.5">
<span class="text-xs font-semibold text-color2">XML 对比</span>
<n-switch :value="isTextExpanded" size="small" @update:value="handleTextExpandedChange" />
</div>
<div class="w-[1px] h-3 bg-divider"></div>
<div class="flex items-center gap-1.5">
<span class="text-xs font-semibold text-color2">渲染对比</span>
<n-switch :value="isRenderExpanded" size="small" @update:value="handleRenderExpandedChange" />
</div>
</div>
</div>
</div>
<!-- 主内容区:左右分栏 -->
......@@ -37,7 +51,13 @@
<!-- 左侧栏:旧工卡输入与渲染 -->
<div class="flex-1 flex flex-col min-w-0 min-h-0">
<!-- 输入区 -->
<div class="flex flex-col h-1/3 min-h-[180px] max-h-[320px] mb-3 shrink-0">
<div
class="flex flex-col shrink-0 compare-text-zone"
:class="{
'is-collapsed': !isTextExpanded,
'expanded-full': !isRenderExpanded
}"
>
<div class="flex items-center justify-between mb-2 shrink-0">
<span class="text-sm font-bold text-color2 flex items-center space-x-1.5 whitespace-nowrap">
<span class="w-2 h-2 rounded-full bg-warning"></span>
......@@ -48,10 +68,10 @@
v-if="oldXmlText"
size="tiny"
secondary
:type="isEditingOld ? 'warning' : 'default'"
@click="isEditingOld = !isEditingOld"
:type="(isEditingOld || editingOldRange) ? 'warning' : 'default'"
@click="toggleEditOld"
>
{{ isEditingOld ? '查看对比' : '编辑 XML' }}
{{ (isEditingOld || editingOldRange) ? '查看对比' : '编辑 XML' }}
</CommonButton>
<CommonButton size="tiny" secondary type="warning" @click="loadCurrentXml">使用当前打开的工卡</CommonButton>
</div>
......@@ -76,19 +96,32 @@
<div
v-for="(line, idx) in oldDiffLines"
:key="idx"
class="flex w-full group hover:bg-fill-2 min-h-[20px] cursor-pointer"
v-show="!editingOldRange || !line.num || line.num < editingOldRange.start || line.num > editingOldRange.end || line.num === editingOldRange.start"
class="flex w-full group hover:bg-fill-2 min-h-[20px] cursor-pointer diff-line-row"
:class="{
'line-row-removed': line.type === 'removed',
'line-row-empty': line.type === 'empty'
}"
:data-line-num="line.num"
@click="highlightTargetNode(line, false)"
@dblclick.stop="startEditLine(line, false)"
>
<span class="line-num select-none w-10 text-right pr-2 border-r border-divider opacity-50">
{{ line.num || '' }}
</span>
<textarea
v-if="editingOldRange && line.num === editingOldRange.start"
:id="`old-range-input-${line.num}`"
v-model="editingOldRangeText"
:rows="Math.max(2, editingOldRange.end - editingOldRange.start + 1)"
class="flex-1 px-2 py-1 bg-fill-2 border border-primary text-[11px] font-mono text-color1 outline-none rounded resize-y"
@blur="saveEditRange(false)"
@keydown.enter.ctrl.stop="saveEditRange(false)"
@keydown.esc.stop="cancelEditRange(false)"
@click.stop
/>
<input
v-if="editingOldLineNum === line.num && line.num"
v-else-if="editingOldLineNum === line.num && line.num"
:id="`old-line-input-${line.num}`"
v-model="editingOldText"
class="flex-1 px-2 py-0.5 bg-fill-2 border border-primary text-[11px] font-mono text-color1 outline-none h-[18px] leading-[18px] rounded"
......@@ -100,7 +133,7 @@
<span v-else class="line-content pl-2 break-all whitespace-pre-wrap flex-1">{{ line.content }}</span>
</div>
</div>
<!-- 左侧文本差异指示条:放在滚动容器外部右侧,加宽并支持点击跳转模拟拖拽 -->
<!-- 左侧文本差异指示条:放在滚动容器外部右侧,加宽并支持点击跳转 and 模拟拖拽 -->
<div
v-if="!isEditingOld"
class="absolute right-[2px] top-0 bottom-0 w-[12px] bg-transparent hover:bg-black/[0.04] cursor-pointer z-50 select-none transition-colors"
......@@ -111,7 +144,7 @@
<div
v-for="(marker, mIdx) in leftTextDiffMarkers"
:key="mIdx"
class="absolute left-0 right-0 h-[3px] bg-[#e74c3c]"
class="absolute left-0 right-0 h-[3px] bg-danger-6"
:style="{ top: marker.top + '%' }"
></div>
<!-- 模拟滑块 -->
......@@ -127,7 +160,12 @@
</div>
</div>
<!-- 渲染区 -->
<div class="flex-1 flex flex-col min-h-0">
<div
class="flex-grow flex flex-col compare-render-zone"
:class="{
'is-collapsed': !isRenderExpanded
}"
>
<div class="text-xs font-bold text-color3 mb-2 px-1 flex items-center space-x-1.5 shrink-0">
<span class="w-2 h-2 rounded-full bg-danger"></span>
<span>源工卡渲染 (旧版本)</span>
......@@ -156,7 +194,7 @@
v-for="(marker, mIdx) in leftRenderDiffMarkers"
:key="mIdx"
class="absolute left-0 right-0 h-[3px]"
:class="marker.type === 'removed' ? 'bg-[#e74c3c]' : 'bg-[#f1c40f]'"
:class="marker.type === 'removed' ? 'bg-danger-6' : 'bg-warning-6'"
:style="{ top: marker.top + '%' }"
></div>
<!-- 模拟滑块 -->
......@@ -176,7 +214,13 @@
<!-- 右侧栏:新工卡输入与渲染 -->
<div class="flex-1 flex flex-col min-w-0 min-h-0">
<!-- 输入区 -->
<div class="flex flex-col h-1/3 min-h-[180px] max-h-[320px] mb-3 shrink-0">
<div
class="flex flex-col shrink-0 compare-text-zone"
:class="{
'is-collapsed': !isTextExpanded,
'expanded-full': !isRenderExpanded
}"
>
<div class="flex items-center justify-between mb-2 shrink-0">
<span class="text-sm font-bold text-color2 flex items-center space-x-1.5 whitespace-nowrap">
<span class="w-2 h-2 rounded-full bg-primary"></span>
......@@ -187,10 +231,10 @@
v-if="newXmlText"
size="tiny"
secondary
:type="isEditingNew ? 'primary' : 'default'"
@click="isEditingNew = !isEditingNew"
:type="(isEditingNew || editingNewRange) ? 'primary' : 'default'"
@click="toggleEditNew"
>
{{ isEditingNew ? '查看对比' : '编辑 XML' }}
{{ (isEditingNew || editingNewRange) ? '查看对比' : '编辑 XML' }}
</CommonButton>
<CommonButton size="tiny" secondary type="primary" @click="handleXmlImportClick">选择 XML 文件导入</CommonButton>
</div>
......@@ -215,19 +259,32 @@
<div
v-for="(line, idx) in newDiffLines"
:key="idx"
class="flex w-full group hover:bg-fill-2 min-h-[20px] cursor-pointer"
v-show="!editingNewRange || !line.num || line.num < editingNewRange.start || line.num > editingNewRange.end || line.num === editingNewRange.start"
class="flex w-full group hover:bg-fill-2 min-h-[20px] cursor-pointer diff-line-row"
:class="{
'line-row-added': line.type === 'added',
'line-row-empty': line.type === 'empty'
}"
:data-line-num="line.num"
@click="highlightTargetNode(line, true)"
@dblclick.stop="startEditLine(line, true)"
>
<span class="line-num select-none w-10 text-right pr-2 border-r border-divider opacity-50">
{{ line.num || '' }}
</span>
<textarea
v-if="editingNewRange && line.num === editingNewRange.start"
:id="`new-range-input-${line.num}`"
v-model="editingNewRangeText"
:rows="Math.max(2, editingNewRange.end - editingNewRange.start + 1)"
class="flex-1 px-2 py-1 bg-fill-2 border border-primary text-[11px] font-mono text-color1 outline-none rounded resize-y"
@blur="saveEditRange(true)"
@keydown.enter.ctrl.stop="saveEditRange(true)"
@keydown.esc.stop="cancelEditRange(true)"
@click.stop
/>
<input
v-if="editingNewLineNum === line.num && line.num"
v-else-if="editingNewLineNum === line.num && line.num"
:id="`new-line-input-${line.num}`"
v-model="editingNewText"
class="flex-1 px-2 py-0.5 bg-fill-2 border border-primary text-[11px] font-mono text-color1 outline-none h-[18px] leading-[18px] rounded"
......@@ -250,7 +307,7 @@
<div
v-for="(marker, mIdx) in rightTextDiffMarkers"
:key="mIdx"
class="absolute left-0 right-0 h-[3px] bg-[#2ecc71]"
class="absolute left-0 right-0 h-[3px] bg-success-6"
:style="{ top: marker.top + '%' }"
></div>
<!-- 模拟滑块 -->
......@@ -266,7 +323,12 @@
</div>
</div>
<!-- 渲染区 -->
<div class="flex-1 flex flex-col min-h-0">
<div
class="flex-grow flex flex-col compare-render-zone"
:class="{
'is-collapsed': !isRenderExpanded
}"
>
<div class="text-xs font-bold text-color3 mb-2 px-1 flex items-center space-x-1.5 shrink-0">
<span class="w-2 h-2 rounded-full bg-success"></span>
<span>目标工卡渲染 (新版本)</span>
......@@ -295,7 +357,7 @@
v-for="(marker, mIdx) in rightRenderDiffMarkers"
:key="mIdx"
class="absolute left-0 right-0 h-[3px]"
:class="marker.type === 'added' ? 'bg-[#2ecc71]' : 'bg-[#f1c40f]'"
:class="marker.type === 'added' ? 'bg-success-6' : 'bg-warning-6'"
:style="{ top: marker.top + '%' }"
></div>
<!-- 模拟滑块 -->
......@@ -317,6 +379,7 @@
</template>
<script setup lang="ts">
import { ref, provide } from 'vue'
import { useCompareModal } from './functionals'
import DocNodeRenderer from '../../../DocNodeRenderer/index.vue'
......@@ -378,9 +441,36 @@ const {
onLeftTextScroll,
onRightTextScroll,
onLeftScroll,
onRightScroll
onRightScroll,
editingOldRange,
editingNewRange,
editingOldRangeText,
editingNewRangeText,
toggleEditOld,
toggleEditNew,
saveEditRange,
cancelEditRange
} = useCompareModal()
const isTextExpanded = ref(true)
const isRenderExpanded = ref(true)
const handleTextExpandedChange = (val: boolean) => {
if (!val && !isRenderExpanded.value) {
window.$message.warning('请至少保留一个对比区域')
return
}
isTextExpanded.value = val
}
const handleRenderExpandedChange = (val: boolean) => {
if (!val && !isTextExpanded.value) {
window.$message.warning('请至少保留一个对比区域')
return
}
isRenderExpanded.value = val
}
// 注入 diffMode: true 到下级所有的 DocNodeRenderer 中
provide('diffMode', true)
......@@ -425,23 +515,15 @@ defineExpose({
background-color: transparent !important;
}
/* 隐藏对比弹窗内滚动容器的原生垂直滚动条,使用自定义指示条滑块代替;水平滚动条保留并美化 */
.scroll-container::-webkit-scrollbar {
width: 0 !important;
}
.scroll-container::-webkit-scrollbar:horizontal {
height: 6px !important;
display: block !important;
}
.scroll-container::-webkit-scrollbar-thumb:horizontal {
background-color: var(--divider-color, rgba(0, 0, 0, 0.15)) !important;
border-radius: 3px !important;
}
.scroll-container::-webkit-scrollbar-thumb:horizontal:hover {
background-color: var(--primary-color) !important;
/* 隐藏对比弹窗内滚动容器的原生垂直和水平滚动条,只保留我们自定义的模拟滑块 */
.compare-modal-content .scroll-container {
scrollbar-width: none !important; /* Firefox */
-ms-overflow-style: none !important; /* IE/Edge */
}
.scroll-container {
-ms-overflow-style: -ms-autohiding-scrollbar !important;
.compare-modal-content .scroll-container::-webkit-scrollbar {
width: 0 !important;
height: 0 !important;
display: none !important; /* Chrome/Safari */
}
/* 差异标记样式 */
......@@ -488,25 +570,31 @@ div.doc-node-wrapper[data-diff-status='modified'] {
color: color-mix(in srgb, var(--success-color) 90%, var(--text-color-1)) !important;
}
.line-row-empty {
background-image: repeating-linear-gradient(-45deg, var(--divider-color) 0, var(--divider-color) 1px, transparent 0, transparent 50%);
background-size: 8px 8px;
opacity: 0.4;
background-color: color-mix(in srgb, var(--warning-color) 5%, transparent) !important;
background-image: repeating-linear-gradient(
-45deg,
color-mix(in srgb, var(--warning-color) 15%, transparent) 0,
color-mix(in srgb, var(--warning-color) 15%, transparent) 1.5px,
transparent 0,
transparent 8px
) !important;
background-size: 12px 12px;
user-select: none !important;
}
/* XML行选中对应的工卡渲染节点高亮闪烁效果 */
@keyframes highlight-glow {
0% {
box-shadow: 0 0 0 0px rgba(255, 125, 0, 0.4);
background-color: rgba(255, 125, 0, 0.1) !important;
box-shadow: 0 0 0 0px color-mix(in srgb, var(--warning-color) 40%, transparent);
background-color: color-mix(in srgb, var(--warning-color) 10%, transparent) !important;
}
50% {
box-shadow: 0 0 10px 4px rgba(255, 125, 0, 0.6);
background-color: rgba(255, 125, 0, 0.25) !important;
box-shadow: 0 0 10px 4px color-mix(in srgb, var(--warning-color) 60%, transparent);
background-color: color-mix(in srgb, var(--warning-color) 25%, transparent) !important;
}
100% {
box-shadow: 0 0 0 0px rgba(255, 125, 0, 0.4);
background-color: rgba(255, 125, 0, 0.1) !important;
box-shadow: 0 0 0 0px color-mix(in srgb, var(--warning-color) 40%, transparent);
background-color: color-mix(in srgb, var(--warning-color) 10%, transparent) !important;
}
}
......@@ -520,4 +608,71 @@ div.doc-node-wrapper[data-diff-status='modified'] {
.app-modal.compare-modal-nobar > .n-card-header {
display: none !important;
}
/* XML 文本区默认样式与展开/收起过渡 */
.compare-text-zone {
flex: 1 1 0% !important;
min-height: 180px !important;
max-height: 320px !important;
margin-bottom: 12px !important;
opacity: 1 !important;
overflow: hidden;
/* 展开过渡:弹性分配比和外边距立即变化,不透明度延迟 0.15s 淡入 */
transition:
flex 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0s,
max-height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0s,
min-height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0s,
margin-bottom 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0s,
opacity 0.2s linear 0.15s !important;
}
/* 当渲染区收起时,文本区独占全部剩余高度 */
.compare-text-zone.expanded-full {
flex: 1 1 0% !important;
min-height: 0 !important;
max-height: 100% !important;
margin-bottom: 0 !important;
}
/* 当文本区收起时:不透明度立即淡出,尺寸延迟 0.08s 收折 */
.compare-text-zone.is-collapsed {
flex: 0 0 0% !important;
max-height: 0 !important;
min-height: 0 !important;
margin-bottom: 0 !important;
opacity: 0 !important;
pointer-events: none;
transition:
opacity 0.15s linear 0s,
flex 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0.08s,
max-height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0.08s,
min-height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0.08s,
margin-bottom 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0.08s !important;
}
/* 渲染区默认样式与展开/收起过渡 */
.compare-render-zone {
flex: 2 1 0% !important;
min-height: 0 !important;
max-height: 2000px !important;
opacity: 1 !important;
overflow: hidden;
/* 展开过渡:弹性大小和高度立即变化,不透明度延迟 0.15s 淡入 */
transition:
flex 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0s,
max-height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0s,
opacity 0.2s linear 0.15s !important;
}
/* 当渲染区收起时:不透明度立即淡出,弹性大小延迟 0.08s 收折 */
.compare-render-zone.is-collapsed {
flex: 0 0 0% !important;
max-height: 0 !important;
opacity: 0 !important;
pointer-events: none;
transition:
opacity 0.15s linear 0s,
flex 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0.08s,
max-height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) 0.08s !important;
}
</style>
import { useEditorStore, createTableStructure } from '@/store/editor'
import { useAppStore } from '@/store/app/index'
import type { XmlNode } from '@/types/xmlNode'
import { serializeTreeToXml } from '@/utils/xmlParser'
/**
* EditorToolbar 组件级业务逻辑 Hook
......@@ -121,6 +122,20 @@ export function useEditorToolbar(emit: any) {
})
}
const xmlSizeStr = computed(() => {
if (!editorStore.xmlTree) return '0 B'
try {
const xml = serializeTreeToXml(editorStore.xmlTree, 0, true)
const bytes = new TextEncoder().encode(xml).length
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
} catch (e) {
console.error('计算 XML 大小失败:', e)
return '0 B'
}
})
return {
editorStore,
appStore,
......@@ -139,6 +154,7 @@ export function useEditorToolbar(emit: any) {
batchTranslateModalRef,
extractTranslateModalRef,
searchTranslateModalRef,
xmlSizeStr,
handleFileUpload: () => {} // 保留空函数以防组件 template 尚未完全更新时报错
}
}
......@@ -178,6 +178,10 @@
<!-- 组 6:系统工具与偏好设置 (靠右) -->
<div class="flex items-center gap-1.5 flex-shrink-0 ml-auto">
<div class="text-xs text-color3 select-none flex items-center gap-1 mr-1">
<span>大小:</span>
<span class="font-mono font-medium text-color2">{{ xmlSizeStr }}</span>
</div>
<div class="w-[1px] h-4 bg-divider mx-0.5 flex-shrink-0"></div>
<n-tooltip trigger="hover" :show-delay="600">
......@@ -285,7 +289,8 @@ const {
handleCreateSignoffConfirm,
batchTranslateModalRef,
extractTranslateModalRef,
searchTranslateModalRef
searchTranslateModalRef,
xmlSizeStr
} = useEditorToolbar(emit)
const insertFragmentModalRef = ref<any>(null)
......
......@@ -50,8 +50,9 @@
<tr
:data-node-id="row.id"
data-tag-name="ROW"
class="border-b border-divider hover:bg-fill-3 group transition-colors cursor-pointer transition-all"
class="border-b border-divider group transition-colors transition-all"
:class="[
!isDiffMode ? 'hover:bg-fill-3 cursor-pointer' : 'cursor-default',
isNodeSelected(row.id) ? 'outline outline-2 outline-primary outline-offset-[-2px] bg-primary/10' : '',
insertedRowIds.has(row.id) ? 'inserted-row-highlight' : ''
]"
......@@ -59,8 +60,12 @@
>
<!-- 表头行选择号 -->
<th
class="p-2 border border-divider text-center select-none cursor-pointer transition-colors bg-fill-4 text-color3 font-bold"
:class="[isNodeSelected(row.id) ? 'bg-primary text-white' : 'hover:bg-fill-3']"
class="p-2 border border-divider text-center select-none transition-colors bg-fill-4 text-color3 font-bold"
:class="[
!isDiffMode ? 'cursor-pointer' : 'cursor-default',
isNodeSelected(row.id) ? 'bg-primary text-white' : '',
!isDiffMode && !isNodeSelected(row.id) ? 'hover:bg-fill-3' : ''
]"
@click.stop="editorStore.setSelectedNodeId(row.id)"
@contextmenu.prevent="isDiffMode ? null : handleRowContextMenu(row.id, $event)"
>
......@@ -155,8 +160,9 @@
<tr
:data-node-id="row.id"
data-tag-name="ROW"
class="border-b border-divider hover:bg-fill-3 group transition-colors cursor-pointer transition-all"
class="border-b border-divider group transition-colors transition-all"
:class="[
!isDiffMode ? 'hover:bg-fill-3 cursor-pointer' : 'cursor-default',
isNodeSelected(row.id) ? 'outline outline-2 outline-primary outline-offset-[-2px] bg-primary/10' : '',
insertedRowIds.has(row.id) ? 'inserted-row-highlight' : ''
]"
......@@ -164,8 +170,12 @@
>
<!-- 行号选择单元格 -->
<td
class="p-2 border border-divider text-center select-none cursor-pointer transition-colors font-bold"
:class="[isNodeSelected(row.id) ? 'bg-primary text-white' : 'bg-fill-4 text-color3 hover:bg-fill-3']"
class="p-2 border border-divider text-center select-none transition-colors font-bold"
:class="[
!isDiffMode ? 'cursor-pointer' : 'cursor-default',
isNodeSelected(row.id) ? 'bg-primary text-white' : 'bg-fill-4 text-color3',
!isDiffMode && !isNodeSelected(row.id) ? 'hover:bg-fill-3' : ''
]"
@click.stop="editorStore.setSelectedNodeId(row.id)"
@contextmenu.prevent="isDiffMode ? null : handleRowContextMenu(row.id, $event)"
>
......@@ -275,7 +285,8 @@
</template>
<script setup lang="ts">
import { inject } from 'vue'
import { inject, computed } from 'vue'
import type { Ref } from 'vue'
import { TrashOutline } from '@vicons/ionicons5'
import type { XmlNode } from '@/types/xmlNode'
import { useTableEditor, useTableBatchActions } from './functionals'
......@@ -289,7 +300,9 @@ const props = defineProps<{
}>()
const editorStore = useEditorStore()
const isDiffMode = inject('diffMode', false)
const injectedDiffMode = inject<boolean | Ref<boolean>>('diffMode', false)
const isDiffMode = computed(() => (typeof injectedDiffMode === 'boolean' ? injectedDiffMode : injectedDiffMode.value))
const {
structure,
selectedCellIds,
......@@ -302,8 +315,8 @@ const {
handleColAddSelect,
handleRowDelete,
handleColumnDelete,
isNodeSelected,
isCellSelected,
isNodeSelected: rawIsNodeSelected,
isCellSelected: rawIsCellSelected,
contextMenu,
contextMenuOptions,
handleCellContextMenu,
......@@ -321,6 +334,16 @@ const {
handleResizeStart
} = useTableEditor(props)
const isNodeSelected = (id: string) => {
if (isDiffMode.value) return false
return rawIsNodeSelected(id)
}
const isCellSelected = (cell: any, colIdx: number) => {
if (isDiffMode.value) return false
return rawIsCellSelected(cell, colIdx)
}
/** TableBatchModal 组件实例引用 */
const batchModalRef = ref<InstanceType<typeof TableBatchModal>>()
......
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