<script setup>
// import { h } from 'snabbdom'
import { onBeforeUnmount, ref, shallowRef } from 'vue'
// import { IButtonMenu } from '@wangeditor/core'
import { Boot } from '@wangeditor/editor'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
// 测试:第三方插件
// import withCtrlEnter from '@wangeditor/plugin-ctrl-enter'
// Boot.registerPlugin(withCtrlEnter)
// // 测试:多语言
// i18nChangeLanguage('en')
// // 测试:注册 renderElem
// function renderElemFn(elem, children) {
// const vnode = h('div', {}, children) // type: 'paragraph' 节点,即渲染为 <p> 标签
// return vnode
// }
// const renderElemConf = {
// type: 'paragraph', // 节点 type ,重要!!!
// renderElem: renderElemFn,
// }
// Boot.registerRenderElem(renderElemConf)
// // 测试:注册插件
// function withCtrlEnter(editor) {
// const { insertBreak } = editor
// setTimeout(() => {
// // beforeInput 事件不能识别 ctrl+enter ,所以自己绑定 DOM 事件
// const { $textArea } = DomEditor.getTextarea(newEditor)
// $textArea.on('keydown', e => {
// const isCtrl = e.ctrlKey || e.metaKey
// if (e.keyCode === 13 && isCtrl) {
// // ctrl+enter 触发换行
// editor.insertBreak()
// }
// })
// })
// const newEditor = editor
// newEditor.insertBreak = () => {
// const e = window.event
// const isCtrl = e.ctrlKey || e.metaKey
// // 只有 ctrl 才能换行
// if (isCtrl) {
// insertBreak()
// }
// }
// return newEditor
// }
// Boot.registerPlugin(withCtrlEnter)
// 测试:注册 button menu
// class MyButtonMenu {
// constructor() {
// // this.title = '',
// this.tag = 'button'
// }
// getValue() { return '' }
// isActive() { return false }
// isDisabled() { return false }
// exec(editor) {
// console.log(editor)
// // alert('menu1 exec')
// }
// }
// const menuConf = {
// key: 'my-menu-1', // menu key ,唯一。注册之后,需通过 toolbarKeys 配置到工具栏
// factory() {
// return new MyButtonMenu()
// },
// }
// // 检查菜单是否已注册,避免重复注册
// if (!Boot.getMenuConfig('my-menu-1')) {
// Boot.registerMenu(menuConf);
// console.log('菜单注册成功');
// } else {
// console.log('菜单已存在,跳过注册');
// }
// console.log(1111111111)
// 移到组件最外层,确保模块加载时只执行一次
class MyButtonMenu {
constructor() {
this.title = ''; // 必须有标题,否则按钮不显示
this.tag = 'button';
}
getValue() { return ''; }
isActive() { return false; }
isDisabled() { return false; }
exec(editor) {
// console.log('自定义菜单点击', editor);
// editor.insertText('这是自定义菜单插入的内容'); // 示例功能
}
}
const menuConf = {
key: 'my-menu-1',
factory() {
return new MyButtonMenu();
},
};
// 核心:用try-catch捕获重复注册错误
try {
Boot.registerMenu(menuConf);
console.log('菜单注册成功');
} catch (err) {
if (err.message.includes('Duplicated key')) {
console.log('菜单已注册,跳过重复注册');
} else {
console.error('菜单注册失败:', err);
}
}
// 编辑器实例,必须用 shallowRef ,重要!
const editorRef = shallowRef()
// 内容 HTML
const valueHtml = ref('<p>hello world</p>')
// 编辑器配置
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF: {
insertImage: {
checkImage(src) {
console.log('image src', src)
if (src.indexOf("http") !== 0) {
return "图片网址必须以 http/https 开头";
}
return true;
},
},
}
}
// 工具栏配置
const toolbarConfig = {
// toolbarKeys: ['headerSelect', 'bold', 'my-menu-1'],
// excludeKeys: [],
insertKeys: {
index: 0,
keys: 'my-menu-1'
}
}
// 编辑器回调函数
const handleCreated = (editor) => {
console.log("created", editor);
editorRef.value = editor // 记录 editor 实例,重要!
// window.editor = editor // 临时测试使用,用完删除
}
const handleChange = (editor) => {
console.log("change:", editor.children);
}
const handleDestroyed = (editor) => {
console.log('destroyed', editor)
}
const handleFocus = (editor) => {
console.log('focus', editor)
}
const handleBlur = (editor) => {
console.log('blur', editor)
}
const customAlert = (info, type) => {
alert(`【自定义提示】${type} - ${info}`)
}
const customPaste = (editor, event, callback) => {
console.log('ClipboardEvent 粘贴事件对象', event)
// 自定义插入内容
editor.insertText('xxx')
// 返回值(注意,vue 事件的返回值,不能用 return)
callback(false) // 返回 false ,阻止默认粘贴行为
// callback(true) // 返回 true ,继续默认的粘贴行为
}
// 及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
// 销毁,并移除 editor
editor.destroy()
})
const getHtml = () => {
const editor = editorRef.value
if (editor == null) return
console.log(editor.getHtml())
}
</script>
<template>
<!-- <div>-->
<!-- <button @click="getHtml">获取 html</button>-->
<!-- </div>-->
<!-- <div style="border: 1px solid #ccc">-->
<!-- <!– 工具栏 –>-->
<!-- <Toolbar-->
<!-- :editor="editorRef"-->
<!-- :defaultConfig="toolbarConfig"-->
<!-- style="border-bottom: 1px solid #ccc"-->
<!-- />-->
<!-- <!– 编辑器 –>-->
<!-- <Editor-->
<!-- v-model="valueHtml"-->
<!-- :defaultConfig="editorConfig"-->
<!-- @onChange="handleChange"-->
<!-- @onCreated="handleCreated"-->
<!-- @onDestroyed="handleDestroyed"-->
<!-- @onFocus="handleFocus"-->
<!-- @onBlur="handleBlur"-->
<!-- @customAlert="customAlert"-->
<!-- @customPaste="customPaste"-->
<!-- style="height: 500px"-->
<!-- />-->
<!-- </div>-->
<div class="editor-container"> <!-- 添加容器类名 -->
<!-- 工具栏 -->
<Toolbar
:editor="editorRef"
:defaultConfig="toolbarConfig"
style="border-bottom: 1px solid #e5e7eb"
/>
<!-- 编辑器 -->
<Editor
v-model="valueHtml"
:defaultConfig="editorConfig"
@onChange="handleChange"
@onCreated="handleCreated"
@onDestroyed="handleDestroyed"
@onFocus="handleFocus"
@onBlur="handleBlur"
@customAlert="customAlert"
@customPaste="customPaste"
/>
</div>
</template>
<style src="@wangeditor/editor/dist/css/style.css">
/* 编辑器整体容器 */
.editor-container {
border: 1px solid #e5e7eb; /* 浅灰色边框 */
border-radius: 8px; /* 圆角 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); /* 轻微阴影 */
overflow: hidden; /* 防止内部元素溢出 */
transition: box-shadow 0.2s ease; /* 阴影过渡效果 */
}
.editor-container:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); /* hover时增强阴影 */
}
/* 工具栏样式 */
.w-e-toolbar {
background-color: #f9fafb !important; /* 工具栏背景色 */
border-bottom: 1px solid #e5e7eb !important; /* 底部边框 */
padding: 8px 12px !important; /* 内边距 */
}
/* 工具栏按钮通用样式 */
.w-e-toolbar .w-e-menu {
margin: 0 3px !important; /* 按钮间距 */
border-radius: 4px !important; /* 按钮圆角 */
transition: all 0.2s ease !important; /* 过渡效果 */
}
.w-e-toolbar .w-e-menu:hover {
background-color: #eef2f5 !important; /* 悬停背景色 */
transform: translateY(-1px) !important; /* 轻微上浮效果 */
}
/* 自定义菜单按钮样式(my-menu-1) */
.w-e-toolbar [data-key="my-menu-1"] {
background-color: #4f46e5 !important; /* 紫色背景(突出自定义按钮) */
color: white !important; /* 白色文字 */
border: none !important;
}
.w-e-toolbar [data-key="my-menu-1"]:hover {
background-color: #4338ca !important; /* 深色hover效果 */
}
/* 编辑区域样式 */
.w-e-text-container {
height: 500px !important; /* 固定高度 */
padding: 16px !important; /* 内边距 */
background-color: white !important; /* 白色背景 */
}
/* 编辑区域内容样式 */
.w-e-text-container p {
margin: 0 0 12px 0 !important; /* 段落间距 */
line-height: 1.8 !important; /* 行高(提升可读性) */
font-size: 14px !important; /* 基础字体大小 */
color: #1f2937 !important; /* 文字颜色 */
}
/* 编辑区域聚焦样式 */
.w-e-text-container:focus-within {
outline: 2px solid rgba(79, 70, 229, 0.2) !important; /* 聚焦时边框高亮 */
}
/* 工具栏分割线样式 */
.w-e-toolbar .w-e-separator {
background-color: #e5e7eb !important; /* 分割线颜色 */
margin: 0 6px !important; /* 分割线间距 */
}
/* 响应式调整(小屏幕适配) */
@media (max-width: 768px) {
.editor-container {
border-radius: 4px; /* 小屏幕减小圆角 */
}
.w-e-toolbar {
padding: 4px 8px !important; /* 小屏幕减小内边距 */
}
.w-e-text-container {
height: 400px !important; /* 小屏幕减小编辑区高度 */
padding: 12px !important;
}
}
</style>改一改让工具栏悬浮在上面,不会随着页面移动,不论页面滚轮怎么移动都保持在页面上方,右侧添加滚轮使页面可以移动,工具栏上面再搞一个头部,最左边是返回键,中间是AI笔记的标题,最右边是一个头像,点击头像会跳转到个人用户管理界面,头像左边有一个历史记录按钮,点击后会显示一个侧边栏,里面有AI笔记历史,历史记录先留一个按钮就行,头像也先留一个位置就行