Commit e2326b0f by pangchong

feat(editor): 新增文本选择悬浮工具栏及智能翻译加载提示

- 在 DocNodeRenderer 组件中添加智能翻译加载遮罩显示
- 在 EditorPanel 中集成 SelectionToolbar 组件,展示文本选择悬浮工具栏
- SelectionToolbar 支持节点属性编辑、规则查看、智能翻译等多种操作按钮
- 实现文本选区监测,动态显示和定位悬浮工具栏
- 智能翻译功能可对选中节点内容进行英译中自动翻译
- 处理鼠标键盘事件确保工具栏交互体验流畅
- 优化翻译相关逻辑,支持嵌套混合内容的递归翻译操作
- 添加相应的样式和动画效果提升视觉体验
parent 016289ee
...@@ -13,6 +13,19 @@ ...@@ -13,6 +13,19 @@
:style="!isDiffMode && node.attributes?.MERGED === 'TRUE' ? { backgroundColor: 'yellow' } : {}" :style="!isDiffMode && node.attributes?.MERGED === 'TRUE' ? { backgroundColor: 'yellow' } : {}"
@click.stop="editorStore.setSelectedNodeId(node.id)" @click.stop="editorStore.setSelectedNodeId(node.id)"
> >
<!-- 智能翻译加载遮罩 -->
<Transition name="fade">
<div
v-if="translatingNodeId === node.id"
class="absolute inset-0 z-10 flex items-center justify-center bg-card bg-opacity-75 backdrop-blur-[1px] select-none pointer-events-none rounded"
>
<div class="flex items-center space-x-1.5 bg-popover border border-divider shadow-md rounded-full px-3 py-1 text-primary text-xs font-medium">
<n-icon class="animate-spin"><SyncOutline /></n-icon>
<span>翻译中...</span>
</div>
</div>
</Transition>
<!-- 0. 各类 HEADER 节点特殊处理 (不展示) --> <!-- 0. 各类 HEADER 节点特殊处理 (不展示) -->
<template v-if="isHeaderTag"></template> <template v-if="isHeaderTag"></template>
...@@ -1285,10 +1298,11 @@ ...@@ -1285,10 +1298,11 @@
<script setup lang="ts"> <script setup lang="ts">
import DocNodeRenderer from './index.vue' import DocNodeRenderer from './index.vue'
import { ImageOutline, GridOutline } from '@vicons/ionicons5' import { ImageOutline, GridOutline, SyncOutline } from '@vicons/ionicons5'
import type { XmlNode } from '@/types/xmlNode' import type { XmlNode } from '@/types/xmlNode'
import TableEditor from '../TableEditor/index.vue' import TableEditor from '../TableEditor/index.vue'
import { useDocNodeRenderer, getSplitListChildren, getCepTaskNumber, isAllEinDataSameEffect, getDiffWordClass } from './functionals' import { useDocNodeRenderer, getSplitListChildren, getCepTaskNumber, isAllEinDataSameEffect, getDiffWordClass } from './functionals'
import { translatingNodeId } from '@/views/editor/components/NodeTree/functionals'
const getImgSrc = (gnbr: string) => { const getImgSrc = (gnbr: string) => {
if (!gnbr) return '' if (!gnbr) return ''
......
/**
* SelectionToolbar 专属常量与类型定义
*/
export interface SelectionToolbarProps {}
<template>
<Transition name="fade">
<div
v-if="visible && hasActiveNode"
ref="toolbarRef"
class="selection-floating-toolbar fixed z-50 flex items-center bg-card border border-divider shadow-xl rounded-lg px-2 py-1.5 space-x-1.5 backdrop-blur-md bg-opacity-95"
:style="{
top: `${top}px`,
left: `${left}px`,
transform: 'translateX(-50%)'
}"
@mousedown.prevent
>
<!-- 节点操作区域 -->
<div class="flex items-center space-x-1">
<!-- 编辑节点属性 -->
<n-tooltip trigger="hover">
<template #trigger>
<CommonButton
size="tiny"
quaternary
circle
:disabled="!hasActiveNode"
@click="handleEditNode"
>
<template #icon>
<n-icon><SettingsOutline /></n-icon>
</template>
</CommonButton>
</template>
编辑节点属性
</n-tooltip>
<!-- 查看规则 -->
<n-tooltip trigger="hover">
<template #trigger>
<CommonButton
size="tiny"
quaternary
circle
:disabled="!hasActiveNode || isVirtualText"
@click="handleViewRule"
>
<template #icon>
<n-icon><EyeOutline /></n-icon>
</template>
</CommonButton>
</template>
查看 DTD 规则
</n-tooltip>
<!-- 智能翻译 -->
<n-tooltip trigger="hover">
<template #trigger>
<CommonButton
size="tiny"
quaternary
circle
:disabled="!hasActiveNode || !canTranslateActive || isTranslating"
@click="handleTranslateNode"
>
<template #icon>
<n-icon v-if="isTranslating" class="animate-spin"><SyncOutline /></n-icon>
<n-icon v-else><LanguageOutline /></n-icon>
</template>
</CommonButton>
</template>
智能翻译
</n-tooltip>
<!-- 添加子节点 -->
<n-tooltip trigger="hover">
<template #trigger>
<CommonButton
size="tiny"
quaternary
circle
:disabled="!hasActiveNode || isVirtualText || !hasAllowedChildren"
@click="handleAddChild"
>
<template #icon>
<n-icon><AddCircleOutline /></n-icon>
</template>
</CommonButton>
</template>
添加子节点
</n-tooltip>
<!-- 插入到上方 -->
<n-tooltip trigger="hover">
<template #trigger>
<CommonButton
size="tiny"
quaternary
circle
:disabled="!hasActiveNode || !hasParent"
@click="handleInsertBefore"
>
<template #icon>
<n-icon><ArrowUpOutline /></n-icon>
</template>
</CommonButton>
</template>
插入到上方
</n-tooltip>
<!-- 插入到下方 -->
<n-tooltip trigger="hover">
<template #trigger>
<CommonButton
size="tiny"
quaternary
circle
:disabled="!hasActiveNode || !hasParent"
@click="handleInsertAfter"
>
<template #icon>
<n-icon><ArrowDownOutline /></n-icon>
</template>
</CommonButton>
</template>
插入到下方
</n-tooltip>
<!-- 删除节点 -->
<n-tooltip trigger="hover">
<template #trigger>
<CommonButton
size="tiny"
quaternary
circle
type="error"
:disabled="!hasActiveNode || !canDeleteCurrent"
@click="handleDeleteNode"
>
<template #icon>
<n-icon><TrashOutline /></n-icon>
</template>
</CommonButton>
</template>
删除节点
</n-tooltip>
</div>
</div>
</Transition>
</template>
<script setup lang="ts">
import {
SettingsOutline,
EyeOutline,
AddCircleOutline,
ArrowUpOutline,
ArrowDownOutline,
TrashOutline,
LanguageOutline,
SyncOutline
} from '@vicons/ionicons5'
import { useSelectionToolbar } from './functionals/index'
const {
visible,
top,
left,
hasActiveNode,
isVirtualText,
hasParent,
hasAllowedChildren,
canDeleteCurrent,
canTranslateActive,
isTranslating,
handleEditNode,
handleViewRule,
handleTranslateNode,
handleAddChild,
handleInsertBefore,
handleInsertAfter,
handleDeleteNode
} = useSelectionToolbar()
</script>
<style scoped>
.selection-floating-toolbar {
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
border-color: var(--divider-color, rgba(0, 0, 0, 0.08));
transition: opacity 0.15s ease, transform 0.15s ease;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.15s ease, transform 0.15s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateX(-50%) translateY(5px) scale(0.95);
}
</style>
...@@ -69,6 +69,9 @@ ...@@ -69,6 +69,9 @@
<!-- 编辑区非 Table 元素右键上下文菜单弹窗 --> <!-- 编辑区非 Table 元素右键上下文菜单弹窗 -->
<EditAreaContextMenuModal v-model="contextMenuVisible" :node-ids="contextMenuNodeIds" /> <EditAreaContextMenuModal v-model="contextMenuVisible" :node-ids="contextMenuNodeIds" />
<!-- 文本选择悬浮工具栏 -->
<SelectionToolbar />
<!-- 回到顶部悬浮球 --> <!-- 回到顶部悬浮球 -->
<Transition name="fade"> <Transition name="fade">
<div <div
...@@ -94,6 +97,7 @@ import { useEditorStore } from '@/store/editor' ...@@ -94,6 +97,7 @@ import { useEditorStore } from '@/store/editor'
import DocNodeRenderer from '../DocNodeRenderer/index.vue' import DocNodeRenderer from '../DocNodeRenderer/index.vue'
import FindReplacePanel from './components/FindReplacePanel/index.vue' import FindReplacePanel from './components/FindReplacePanel/index.vue'
import EditAreaContextMenuModal from './components/EditAreaContextMenuModal/index.vue' import EditAreaContextMenuModal from './components/EditAreaContextMenuModal/index.vue'
import SelectionToolbar from './components/SelectionToolbar/index.vue'
import { useEditorPanel } from './functionals' import { useEditorPanel } from './functionals'
import { addNodeVisible, addNodeMode, addNodeTargetId, addNodeAllowedTags } from '../NodeTree/functionals' import { addNodeVisible, addNodeMode, addNodeTargetId, addNodeAllowedTags } from '../NodeTree/functionals'
......
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