| <template> | 
|   <div style="border: 1px solid #cccccc"> | 
|     <Toolbar | 
|       style="border-bottom: 1px solid #cccccc" | 
|       :editor="editorRef" | 
|       :defaultConfig="toolbarConfig" | 
|       :mode="mode" | 
|     /> | 
|     <Editor | 
|       style="overflow-y: hidden; height: 500px" | 
|       v-model="innerModelValue" | 
|       :defaultConfig="editorConfig" | 
|       @onCreated="handleCreated" | 
|       @customPaste="handleCustomPaste" | 
|       @onChange="handleChange" | 
|       @onBlur="handleBlur" | 
|       mode="simple" | 
|     /> | 
|   </div> | 
| </template> | 
| <script lang="ts"> | 
| export default { | 
|   name: 'RichEditor', | 
| }; | 
| </script> | 
|   | 
| <script setup lang="ts"> | 
| import '@wangeditor-next/editor/dist/css/style.css'; // 引入 css | 
| import { Editor, Toolbar } from '@wangeditor-next/editor-for-vue'; | 
| import { IEditorConfig, IDomEditor, IToolbarConfig, DomEditor } from '@wangeditor-next/editor'; | 
| import { OssRich } from '@/constants'; | 
| import { OssManager } from '@/utils'; | 
| import { RichEditorUtils } from '@bole-core/components'; | 
| import { useVModel } from '@/hooks'; | 
| import { useFormItem } from 'element-plus'; | 
|   | 
| type Props = { | 
|   modelValue: string; | 
|   placeholder?: string; | 
|   editorConfig?: Partial<IEditorConfig>; | 
|   mode?: 'default' | 'simple'; | 
| }; | 
|   | 
| const props = withDefaults(defineProps<Props>(), { | 
|   placeholder: '', | 
|   editorConfig: () => ({} as Partial<IEditorConfig>), | 
|   mode: 'default', | 
| }); | 
|   | 
| const emit = defineEmits<{ | 
|   (e: 'update:modelValue', val: string): void; | 
| }>(); | 
|   | 
| const innerModelValue = useVModel(props, 'modelValue', emit); | 
|   | 
| // 编辑器实例,必须用 shallowRef | 
| const editorRef = shallowRef<IDomEditor>(); | 
|   | 
| // onMounted(() => { | 
| //   setTimeout(() => { | 
| //     const editor = editorRef.value; | 
| //     const toolbar = DomEditor.getToolbar(editor); | 
|   | 
| //     const curToolbarConfig = toolbar.getConfig(); | 
| //     console.log(curToolbarConfig.toolbarKeys); | 
| //   }, 2000); | 
| // }); | 
|   | 
| // 组件销毁时,也及时销毁编辑器 | 
| onBeforeUnmount(() => { | 
|   const editor = editorRef.value; | 
|   if (editor === null) return; | 
|   editor.destroy(); | 
| }); | 
|   | 
| const handleCreated = (editor: IDomEditor) => { | 
|   editorRef.value = editor; // 记录 editor 实例,重要! | 
| }; | 
|   | 
| async function customUpload(file: File, insertFn) { | 
|   // TS 语法 | 
|   // customInsert(res, insertFn) {                  // JS 语法 | 
|   // res 即服务端的返回结果 | 
|   // 从 res 中找到 url poster ,然后插入视频 | 
|   let res = await OssManager.asyncUpload({ file: file, fileDirectory: OssRich }); | 
|   | 
|   insertFn(res.url, res.name, res.url); | 
| } | 
|   | 
| const editorConfig: Partial<IEditorConfig> = { | 
|   MENU_CONF: { | 
|     uploadImage: { | 
|       customUpload: customUpload, | 
|     }, | 
|     uploadVideo: { | 
|       async customUpload(file: File, insertFn) { | 
|         // TS 语法 | 
|         // customInsert(res, insertFn) {                  // JS 语法 | 
|         // res 即服务端的返回结果 | 
|         // 从 res 中找到 url poster ,然后插入视频 | 
|         let res = await OssManager.asyncUpload({ file: file, fileDirectory: OssRich }); | 
|   | 
|         insertFn(res.url, res.name, res.url); | 
|       }, | 
|     }, | 
|     // insertImage: { | 
|     //   onInsertedImage(imageNode: ImageElement | null) { | 
|     //     // onInsertedImage(imageNode) {                    // JS 语法 | 
|     //     if (imageNode === null) return; | 
|   | 
|     //     const { src, alt, url, href } = imageNode; | 
|     //     console.log('inserted image', src, alt, url, href); | 
|     //   }, | 
|     //   checkImage(src) { | 
|     //     console.log('src2: ', src); | 
|     //     return src; | 
|     //   }, | 
|     //   parseImageSrc(src) { | 
|     //     console.log('src: ', src); | 
|     //     return src; | 
|     //   }, | 
|     // }, | 
|     // editImage: { | 
|     //   checkImage(src) { | 
|     //     console.log('src2: ', src); | 
|     //     return src; | 
|     //   }, | 
|     //   parseImageSrc(src) { | 
|     //     console.log('src: ', src); | 
|     //     return src; | 
|     //   }, | 
|     // }, | 
|   }, | 
|   placeholder: props.placeholder, | 
|   autoFocus: false, | 
|   ...props.editorConfig, | 
| }; | 
|   | 
| const toolbarConfig: Partial<IToolbarConfig> = { | 
|   excludeKeys: ['emotion', 'group-video', 'insertTable', 'codeBlock', 'fullScreen'], | 
| }; | 
|   | 
| async function handleCustomPaste( | 
|   editor: IDomEditor, | 
|   event: ClipboardEvent, | 
|   callback: (e: boolean) => any | 
| ) { | 
|   let html = event.clipboardData.getData('text/html'); | 
|   const rtf = event.clipboardData.getData('text/rtf'); // 获取 rtf 数据(如从 word wsp 复制粘贴) | 
|   if (html && rtf) { | 
|     event.preventDefault(); | 
|     html = html.replace(/text\\-indent:\\-(.*?)pt/gi, ''); | 
|     const imgSrcs = RichEditorUtils.findAllImgSrcsFromHtml(html); | 
|     // 如果有 | 
|     if (imgSrcs && Array.isArray(imgSrcs) && imgSrcs.length) { | 
|       // 从rtf内容中查找图片数据 | 
|       const rtfImageData = RichEditorUtils.extractImageDataFromRtf(rtf); | 
|   | 
|       // 如果找到 | 
|       if (rtfImageData.length && imgSrcs.length === rtfImageData.length) { | 
|         let urlList: string[] = []; | 
|         for (let i = 0; i < rtfImageData.length; i++) { | 
|           const hexData = rtfImageData[i]; | 
|           let file = RichEditorUtils.hexToFile(hexData.hex, hexData.type); | 
|           let res = await OssManager.asyncUpload({ file: file, fileDirectory: OssRich }); | 
|           html = RichEditorUtils.replaceImagesFileSource(html, imgSrcs[i], res.url); | 
|         } | 
|         editor.dangerouslyInsertHtml(html); | 
|       } | 
|     } | 
|     // 阻止默认的粘贴行为 | 
|     callback(false); | 
|   } else { | 
|     callback(true); | 
|   } | 
| } | 
|   | 
| const { formItem } = useFormItem(); | 
|   | 
| function handleChange() { | 
|   formItem?.validate('change'); | 
| } | 
|   | 
| function handleBlur() { | 
|   formItem?.validate('blur'); | 
| } | 
| </script> |