沙发管家TV版
· 2025-10-27
将 AI 智能写作能力无缝集成到富文本编辑器中,是提升内容生产效率的关键。本文聚焦于 Vue 3 + wangEditor 技术栈,为您提供一套架构清晰、代码可即用的实现方案。我们将重点展示如何通过分层解耦的设计思想,构建一个功能完整、用户体验优秀的 AI 助手,并保留所有核心实现代码。
为了确保系统的高可维护性和可扩展性,我们采用了清晰的分层架构:
NoteEditor.vue):编辑器宿主,负责 AI 弹窗的显示/隐藏、快捷键监听以及最终的 AI 服务调用与结果插入。AIWritingPopup.vue):弹窗组件,专注于用户输入、AI 操作类型选择和结果展示。AIToolManager):管理 AI 工具的生命周期和编辑器快捷键绑定。核心交互模式是事件驱动:编辑器操作(如快捷键触发)或弹窗操作(如点击“润色”)最终都派发事件,由 NoteEditor.vue 统一响应和处理。
NoteEditor.vue 的集成与事件处理NoteEditor.vue 是所有功能的汇聚点。它管理着编辑器实例,处理复杂的生命周期,以及响应来自不同来源的 AI 触发事件。
使用 shallowRef 记录编辑器实例,并在组件销毁时进行彻底清理,防止内存泄漏。同时,在创建时初始化 AIToolManager。
// ai_multimodal_web/src/components/NoteEditor.vue (核心JS/Setup 部分)
import { shallowRef, ref, onMounted, onBeforeUnmount } from 'vue'
import { AIToolManager } from '@/utils/definedMenu'
const editorRef = shallowRef(null) // 编辑器实例引用
const aiPopupVisible = ref(false) // AI弹窗显示状态
const aiPopupPosition = ref({ x: 0, y: 0 }) // AI弹窗位置
// AI工具管理器
const aiToolManager = new AIToolManager()
// 编辑器创建完成
const handleCreated = (editor) => {
    editorRef.value = editor // 记录 editor 实例
    aiToolManager.init(editor)
}
// 组件销毁时,清理所有资源
onBeforeUnmount(() => {
    // 移除 AI 事件监听
    document.removeEventListener('askAiClick', handleAskAiClick)
    document.removeEventListener('keydown', handleGlobalKeydown) // 移除快捷键监听
    
    const editor = editorRef.value
    if (editor != null) editor.destroy()
    aiToolManager.destroy()
})
设计 Ctrl + Alt 组合键作为全局触发器。在全局监听中,通过 editor.isFocused() 检查焦点状态,并精确计算弹窗位置。
// ai_multimodal_web/src/components/NoteEditor.vue (快捷键监听)
// 动态弹窗定位算法
const calculatePopupPosition = (selection) => {
    const range = selection.getRangeAt(0)
    const rect = range.getBoundingClientRect()
    // 计算弹窗位置(光标下方)
    let x = rect.left
    let y = rect.bottom + 10
    
    // 简单的边界检测和调整
    const popupWidth = 300 
    const viewportWidth = window.innerWidth 
    if (x + popupWidth > viewportWidth) {
        x = viewportWidth - popupWidth - 10
    }
    
    return { x: Math.max(10, x), y: Math.max(10, y) }
}
// 全局快捷键监听设计 (Ctrl + Alt)
const handleGlobalKeydown = (event) => {
    if (event.ctrlKey && event.altKey) {
        event.preventDefault()
        const editor = editorRef.value
        
        // 智能检测焦点
        if (editor && editor.isFocused()) {
            const selection = document.getSelection()
            if (selection.rangeCount > 0) {
                aiPopupPosition.value = calculatePopupPosition(selection)
                aiPopupVisible.value = true
            }
        }
    }
}
onMounted(() => {
    document.addEventListener('keydown', handleGlobalKeydown)
    // ... 其他监听
})
响应弹窗发出的 insert-text 事件,确保 AI 结果能可靠地被插入到编辑器中。
// ai_multimodal_web/src/components/NoteEditor.vue (事件处理器)
// 处理 AI 弹窗关闭事件
const handleAiPopupClose = () => {
    aiPopupVisible.value = false
}
// 智能文本插入算法
const handleAIInsertText = (text) => {
    const editor = editorRef.value
    if (editor && text) {
        try {
            editor.focus() // 强制聚焦
            // 异步插入处理,确保时序正确
            setTimeout(() => {
                editor.insertText(text)
                // 插入后,关闭弹窗
                aiPopupVisible.value = false
            }, 50)
        } catch (error) {
            console.error('插入文本失败:', error)
        }
    }
}
AIWritingPopup.vue 弹窗实现AIWritingPopup.vue 是一个模态组件,负责用户在选择操作和输入文本时的所有交互细节。
<template>
  <div v-show="visible" class="ai-writing-popup" :style="popupStyle">
    <div class="popup-content">
      
      <div class="dropdown-menu" v-show="showDropdown">
        <div 
          v-for="action in quickActions" 
          :key="action.value" 
          class="dropdown-item"
          @click="handleActionSelect(action.value)"
        >
          {{ action.label }}
        </div>
      </div>
      <div class="input-section" v-show="!showDropdown || aiResult">
        <input 
          ref="inputRef"
          type="text" 
          v-model="inputText" 
          placeholder="请输入内容或指令..."
          @keyup.enter="handleSubmit"
        />
        <button @click="handleSubmit" :disabled="isLoading">
          {{ isLoading ? '处理中...' : '发送' }}
        </button>
      </div>
      <div v-if="aiResult" class="result-section">
        <p>{{ aiResult }}</p>
        <button @click="handleInsert">插入</button>
        <button @click="handleClose">关闭</button>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, computed, watch, nextTick } from 'vue'
const props = defineProps({
  visible: Boolean,
  position: Object,
  editor: Object // 接收编辑器实例,用于上下文获取
})
const emit = defineEmits(['close', 'insert-text', 'ai-action']) // insert-text 由 NoteEditor 响应
const inputRef = ref(null)
const inputText = ref('')
const isLoading = ref(false)
const aiResult = ref('')
const selectedAction = ref('continue') // 默认操作
const showDropdown = ref(true) // 是否显示操作菜单
// AI操作配置(可扩展)
const quickActions = [
    { label: 'AI 续写', value: 'continue' },
    { label: 'AI 润色', value: 'polish' },
    { label: 'AI 总结', value: 'summary' },
    { label: 'AI 翻译', value: 'translate' }
]
// 弹窗样式计算
const popupStyle = computed(() => ({
  left: `${props.position.x}px`,
  top: `${props.position.y}px`
}))
// 监听弹窗显示,自动聚焦输入框
watch(() => props.visible, (newVal) => {
  if (newVal) {
    // 重置状态
    inputText.value = props.editor?.getSelectionText() || '' // 自动带入选中内容
    aiResult.value = ''
    showDropdown.value = true
    nextTick(() => {
        inputRef.value?.focus()
    })
  }
})
// 处理操作选择
const handleActionSelect = (action) => {
    selectedAction.value = action
    showDropdown.value = false // 隐藏菜单
    nextTick(() => inputRef.value?.focus())
}
// 提交 AI 请求
const handleSubmit = async () => {
    if (!inputText.value.trim() || isLoading.value) return
    isLoading.value = true
    showDropdown.value = false
    aiResult.value = ''
    // 1. 触发 AI 动作事件 (由 NoteEditor 处理服务调用)
    emit('ai-action', {
        action: selectedAction.value,
        text: inputText.value,
    })
    
    // 2. 模拟/实际 API 调用 (此处为模拟,实际应调用真实的 AI API)
    await new Promise(resolve => setTimeout(resolve, 1500))
    
    // 3. 假设从 API 获得结果
    aiResult.value = `[AI ${selectedAction.value} 结果] 这是根据您的 "${inputText.value}" 生成的新内容。`
    isLoading.value = false
}
// 插入结果到编辑器
const handleInsert = () => {
    if (aiResult.value) {
        emit('insert-text', aiResult.value)
    }
    handleClose()
}
// 关闭弹窗
const handleClose = () => {
    emit('close')
    // 重置内部状态
    isLoading.value = false
    aiResult.value = ''
    showDropdown.value = true
}
</script>
<style scoped>
/* 样式设计(仅保留核心) */
.ai-writing-popup {
    position: fixed;
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    min-width: 300px;
    z-index: 9999;
    padding: 12px;
}
.dropdown-menu {
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    margin-bottom: 8px;
}
.dropdown-item {
    padding: 8px 12px;
    cursor: pointer;
    transition: background-color 0.2s;
}
.dropdown-item:hover {
    background-color: #f0f0f0;
}
.input-section {
    display: flex;
    gap: 8px;
    margin-bottom: 8px;
}
.input-section input {
    flex-grow: 1;
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
}
.result-section {
    border-top: 1px solid #eee;
    padding-top: 8px;
}
</style>
AIToolManager 与菜单注册为了在 wangEditor 工具栏中添加一个持久化的 AI 按钮(作为备用触发方式),我们需要一个专门的管理器来处理菜单的注册和生命周期。
// ai_multimodal_web/src/utils/definedMenu.js (AI 工具菜单定义与管理器)
import { Boot } from '@wangeditor/editor'
// 1. 定义自定义菜单栏的 class
class MyselectAiBar {
    constructor() {
        this.title = 'AI 工具'
        this.tag = 'button'
        this.showDropPanel = true
    }
    getValue() { return '' }
    isActive() { return false }
    isDisabled() { return false }
    exec() {
        // do nothing - 仅展示下拉面板
    }
    getPanelContentElem() {
        const ul = document.createElement('ul')
        ul.className = 'w-e-panel-my-list'
        const items = [
            { label: 'AI 总结', value: 'summary' },
            { label: 'AI 润色', value: 'polish' },
            { label: 'AI 翻译', value: 'translate' },
        ]
        items.forEach((item) => {
            const li = document.createElement('li')
            li.textContent = item.label
            // 关键:点击菜单项时,派发统一事件
            li.addEventListener('click', () => {
                const event = new CustomEvent('askAiClick', {
                    detail: { value: item.value, type: 'toolbar' },
                })
                document.dispatchEvent(event)
            })
            ul.appendChild(li)
        })
        return ul
    }
}
const myselectAiConf = {
    key: 'myselectAiBar',
    factory() {
        return new MyselectAiBar()
    },
}
// 2. 幂等性注册函数(只注册一次,防止 HMR 导致重复)
function registerMenusOnce() {
    if (globalThis.__aiMenusRegistered) return
    const module = { menus: [myselectAiConf] }
    Boot.registerModule(module)
    globalThis.__aiMenusRegistered = true
}
// 3. AIToolManager 封装
export class AIToolManager {
    constructor() {
        this.editor = null
    }
    // 初始化:注册菜单并记录 editor
    init(editor) {
        registerMenusOnce()
        this.editor = editor
        // 可以在这里绑定其他快捷键或编辑器相关事件
    }
    // 销毁:清理引用
    destroy() {
        this.editor = null
    }
}
NoteEditor.vue 中的服务调度最后,在 NoteEditor.vue 中,我们需要监听 AI 弹窗发出的 ai-action 事件,并进行真实的 AI 服务调用。
// ai_multimodal_web/src/components/NoteEditor.vue (新增 ai-action 事件处理器)
// 假设有一个封装好的 AI 服务
import aiService from '@/api/aiService' 
const handleAiAction = async (data) => {
    const editor = editorRef.value
    if (!editor) return
    const { action, text } = data
    
    // 1. 插入占位符(可选,但推荐提供即时反馈)
    const actionLabelMap = { summary: '总结', polish: '润色', translate: '翻译' }
    const label = actionLabelMap[action] || '处理'
    editor.insertText(`【AI${label}处理中…】`)
    try {
        // 2. 调用真实的后端服务
        const { resultText } = await aiService.process({ action, text }) 
        
        // 3. 替换占位符并插入结果
        // (复杂的替换逻辑此处省略,可直接插入新内容)
        editor.insertText(`n【AI${label}完成】n${resultText}n`) 
        // 4. 关闭弹窗(如果弹窗未自动关闭)
        aiPopupVisible.value = false
    } catch (error) {
        console.error("AI 服务调用失败:", error)
        editor.insertText(`n【AI${label}失败】请检查网络或重试。n`)
    }
}
// ... 确保在 <template> 中将 handleAiAction 绑定到 AIWritingPopup
好的,遵照您的要求,我将总结内容进一步精炼,只保留最核心的技术亮点和价值点。
该 AI 写作助手方案成功实现了 Vue 3 与 wangEditor 的深度集成和高可扩展性。其核心技术价值和亮点在于:
架构与解耦(高可维护性):
极致的用户体验(高可用性):
Ctrl + Alt 全局监听,并结合焦点判断,提供最高效的触发机制。工程实践(高可靠性):
这套方案为构建易于迭代、稳定可靠的智能富文本应用奠定了坚实基础。
好的,基于前面构建的清晰前端架构,封装 AI 模型接口以实现实际的 AI 写作功能是后续的关键一步。我将说明如何设计这个接口层,确保它与前端解耦并具备良好的可扩展性。
在前端架构已经完成解耦的基础上,下一步就是将之前前端代码中的“模拟调用”替换为真实的 AI 模型接口。
多模态Ai项目全流程开发中,从需求分析,到Ui设计,前后端开发,部署上线,感兴趣打开链接(带项目功能演示),多模态AI项目开发中...
 
                    