Procházet zdrojové kódy

feat: 活动详情编辑器 Quill 切换为 wangEditor 5

- 新增 @wangeditor/editor ^5.1.23 依赖
- src/components/editor/index.vue 用 createEditor/createToolbar 重写,
  保留 content prop / EditorChange 事件 / init() 方法的 API 契约,
  图片上传沿用 /sys/oss/upload 端点 + customInsert 桥接后端老响应格式
- main.js 删除为 quill-image-resize-module 加的 window.Quill 兜底,
  该模块随旧编辑器一起退役
- 重新打包 dist.zip

注:邮件模板/公告/demo 新闻 3 处独立 Quill 用法本次不动,quill/
vue-quill-editor 保留在 dependencies 给那 3 处继续用。
Developer před 6 dny
rodič
revize
7fe7c9a2e8
5 změnil soubory, kde provedl 1076 přidání a 1357 odebrání
  1. binární
      dist.zip
  2. 956 1208
      package-lock.json
  3. 1 0
      package.json
  4. 119 144
      src/components/editor/index.vue
  5. 0 5
      src/main.js

binární
dist.zip


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 956 - 1208
package-lock.json


+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "et:list": "gulp themes"
     "et:list": "gulp themes"
   },
   },
   "dependencies": {
   "dependencies": {
+    "@wangeditor/editor": "^5.1.23",
     "axios": "^0.19.0",
     "axios": "^0.19.0",
     "babel-eslint": "^8.0.1",
     "babel-eslint": "^8.0.1",
     "babel-plugin-component": "^1.1.1",
     "babel-plugin-component": "^1.1.1",

+ 119 - 144
src/components/editor/index.vue

@@ -1,171 +1,146 @@
 <template>
 <template>
-<div class="quill-editor">
-    <el-upload class="avatar-uploaderEdi" :action="url" :show-file-list="false" :on-success="uploadSuccess" :before-upload="beforeUpload">
-    </el-upload>
-    <quill-editor v-model="editorContent" class="ql-container sicepot" @change="onEditorChange($event)" :content="editorContent" :options="editorOption" ref="QuillEditor">
-    </quill-editor>
+<div class="wangeditor-wrapper">
+    <div ref="toolbarContainer" class="toolbar-container"></div>
+    <div ref="editorContainer" class="editor-container"></div>
 </div>
 </div>
 </template>
 </template>
 
 
 <script>
 <script>
-import Cookies from "js-cookie";
-import 'quill/dist/quill.core.css'
-import 'quill/dist/quill.snow.css'
-import 'quill/dist/quill.bubble.css'
-import {
-    quillEditor,
-    Quill
-} from 'vue-quill-editor'
-// window.Quill 已在 main.js 入口处挂载,避免 webpack import 提升导致 resize 模块执行时 window.Quill 仍为 undefined。
-import ImageResize from 'quill-image-resize-module'
-Quill.register('modules/imageResize', ImageResize)
-// Quill 1.3.6 的 ui/icons.js 不含 undo/redo,需手动注入到全局 icons 映射,
-// 否则 snow 主题的 buildButtons 会因 icons[name] 为 null 跳过按钮 innerHTML 的渲染。
-const icons = Quill.import('ui/icons')
-icons['undo'] = '<svg viewbox="0 0 18 18"><polygon class="ql-fill ql-stroke" points="6 10 4 12 2 10 2 14 6 14 4 12 6 10 8 7 14 7 14 11 16 11 16 4 8 4 6 7"></polygon></svg>'
-icons['redo'] = '<svg viewbox="0 0 18 18"><polygon class="ql-fill ql-stroke" points="12 10 14 12 16 10 16 14 12 14 14 12 12 10 10 7 4 7 4 11 2 11 2 4 10 4 12 7"></polygon></svg>'
-const toolbarOptions = [
-    ['undo', 'redo'], // 撤销,重做
-    ['bold', 'italic', 'underline', 'strike'], // 加粗,斜体,下划线,删除线
-    ['blockquote', 'code-block'], //引用,代码块
-    [{
-        'header': 1
-    }, {
-        'header': 2
-    }], // 几级标题
-    [{
-        'list': 'ordered'
-    }, {
-        'list': 'bullet'
-    }], // 有序列表,无序列表
-    [{
-        'script': 'sub'
-    }, {
-        'script': 'super'
-    }], // 下角标,上角标
-    [{
-        'indent': '-1'
-    }, {
-        'indent': '+1'
-    }], // 缩进
-    [{
-        'direction': 'rtl'
-    }], // 文字输入方向
-    [{
-        'size': ['small', false, 'large', 'huge']
-    }], // 字体大小
-    [{
-        'header': [1, 2, 3, 4, 5, 6, false]
-    }], // 标题
-    [{
-        'color': []
-    }, {
-        'background': []
-    }], // 颜色选择
-    [{
-        'font': ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial']
-    }], // 字体
-    [{
-        'align': []
-    }], // 居中
-    ['clean'], // 清除样式,
-    ['link', 'image'], // 上传图片、上传视频
-]
+import '@wangeditor/editor/dist/css/style.css'
+import { createEditor, createToolbar } from '@wangeditor/editor'
+import Cookies from 'js-cookie'
+
 export default {
 export default {
+    name: 'EditorVue',
+    props: {
+        content: {
+            type: String,
+            default: ''
+        }
+    },
     data() {
     data() {
         return {
         return {
-            editorContent: this.content || '',
-            editorOption: {
-                placeholder: '请在这里输入',
-                theme: 'snow', //主题 snow/bubble
-                modules: {
-                    history: {
-                        delay: 1000,
-                        maxStack: 50,
-                        userOnly: false
-                    },
-                    imageResize: {
-                        displayStyles: {
-                            backgroundColor: 'black',
-                            border: 'none',
-                            color: 'white'
-                        },
-                        modules: ['Resize', 'DisplaySize', 'Toolbar']
-                    },
-                    toolbar: {
-                        container: toolbarOptions,
-                        handlers: {
-                            undo: function () {
-                                this.quill.history.undo()
-                            },
-                            redo: function () {
-                                this.quill.history.redo()
-                            },
-                            image: function (value) {
-                                if (value) {
-                                    // 调用element的图片上传组件
-                                    document.querySelector('.avatar-uploaderEdi input').click()
-                                } else {
-                                    this.quill.format('image', false)
-                                }
-                            }
-                        }
-                    }
-                }
-            },
-            url: `${
-        window.SITE_CONFIG["apiURL"]
-      }/sys/oss/upload?token=${Cookies.get("token")}`,
+            editor: null,
+            toolbar: null
         }
         }
     },
     },
-    props: ['content'],
-    components: {
-        quillEditor
-    },
     watch: {
     watch: {
-        content(newValue) {
-            // 检查新值是否与当前编辑器内容不同,避免不必要的更新和光标跳动
-            if (newValue !== this.editorContent) {
-                this.editorContent = newValue;
+        content(newVal) {
+            // 外部 prop 变化(异步加载详情)才回填,避免输入过程中光标跳动与循环触发
+            if (this.editor && newVal !== this.editor.getHtml()) {
+                this.editor.setHtml(newVal || '')
             }
             }
         }
         }
     },
     },
+    mounted() {
+        this._createEditor()
+    },
+    beforeDestroy() {
+        this._destroyEditor()
+    },
     methods: {
     methods: {
+        // 父组件 ref 调用的初始化入口(对齐老 Quill 实现的 init() 行为)
         init() {
         init() {
-            this.editorContent = this.content || '';
-        },
-        beforeUpload(file) {},
-        // 值发生变化
-        onEditorChange(editor) {
-            this.$emit('EditorChange', this.editorContent);
+            this._destroyEditor()
+            this._createEditor()
         },
         },
-        uploadSuccess(res) {
-            // 获取富文本组件实例
-            let quill = this.$refs.QuillEditor.quill
-            // 如果上传成功
-            if (res) {
-                // 获取光标所在位置
-                let length = quill.getSelection().index;
-                // 插入图片,res为服务器返回的图片链接地址
-                quill.insertEmbed(length, 'image', res.data)
-                // 调整光标到最后
-                quill.setSelection(length + 1)
-            } else {
-                // 提示信息,需引入Message
-                this.$message.error('图片插入失败!')
+        _destroyEditor() {
+            if (this.editor) {
+                this.editor.destroy()
+                this.editor = null
+            }
+            if (this.toolbar) {
+                this.toolbar.destroy()
+                this.toolbar = null
             }
             }
         },
         },
+        _createEditor() {
+            const editorConfig = {
+                html: this.content || '',
+                placeholder: '请在这里输入',
+                readOnly: false,
+                autoFocus: false,
+                MENU_CONF: {
+                    uploadImage: {
+                        // 沿用项目原有的 OSS 上传端点,token 走 URL 参数
+                        server: `${window.SITE_CONFIG['apiURL']}/sys/oss/upload?token=${Cookies.get('token') || ''}`,
+                        fieldName: 'file',
+                        // 后端响应是项目老格式 { code: 0, data: 'url', msg: '...' },
+                        // wangEditor 默认期望 { errno, data: [{ url, alt, href }] },这里桥接一下
+                        customInsert(res, insertFn) {
+                            if (res && res.code === 0 && res.data) {
+                                insertFn(res.data, '', res.data)
+                            }
+                        }
+                    },
+                    fontFamily: {
+                        fontFamilyList: [
+                            { name: '宋体', value: 'SimSun' },
+                            { name: '黑体', value: 'SimHei' },
+                            { name: '微软雅黑', value: 'Microsoft YaHei' },
+                            { name: '楷体', value: 'KaiTi' },
+                            { name: '仿宋', value: 'FangSong' },
+                            { name: 'Arial', value: 'Arial' }
+                        ]
+                    }
+                },
+                onChange: (editor) => {
+                    this.$emit('EditorChange', editor.getHtml())
+                }
+            }
+
+            this.editor = createEditor({
+                selector: this.$refs.editorContainer,
+                config: editorConfig,
+                mode: 'default'
+            })
 
 
+            const toolbarConfig = {
+                toolbarKeys: [
+                    'undo', 'redo',
+                    '|',
+                    'headerSelect',
+                    'bold', 'italic', 'underline', 'strikeThrough',
+                    '|',
+                    'color', 'bgColor',
+                    '|',
+                    'fontSize', 'fontFamily',
+                    'lineHeight',
+                    '|',
+                    'justifyLeft', 'justifyCenter', 'justifyRight',
+                    '|',
+                    'bulletedList', 'numberedList',
+                    'indent',
+                    '|',
+                    'insertLink', 'uploadImage',
+                    'blockquote', 'codeBlock',
+                    '|',
+                    'divider', 'splitLine'
+                ]
+            }
+
+            this.toolbar = createToolbar({
+                editor: this.editor,
+                selector: this.$refs.toolbarContainer,
+                config: toolbarConfig,
+                mode: 'default'
+            })
+        }
     }
     }
 }
 }
 </script>
 </script>
 
 
 <style>
 <style>
-.sicepot {
-    height: auto !important;
+.wangeditor-wrapper {
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    background: #fff;
 }
 }
-
-.sicepot .ql-container.ql-snow {
-    height: 600px !important;
+.wangeditor-wrapper .toolbar-container {
+    border-bottom: 1px solid #dcdfe6;
+}
+.wangeditor-wrapper .editor-container {
+    min-height: 600px;
+    height: auto !important;
 }
 }
-</style>
+</style>

+ 0 - 5
src/main.js

@@ -21,11 +21,6 @@ import cropperDlg2 from '@/components/cropperDlg/index2.vue'
 import { hasPermission, getDictLabel } from '@/utils'
 import { hasPermission, getDictLabel } from '@/utils'
 import cloneDeep from 'lodash/cloneDeep'
 import cloneDeep from 'lodash/cloneDeep'
 
 
-// quill-image-resize-module 内部直接访问 window.Quill.imports.parchment,
-// 在 webpack 打包环境下 window.Quill 未挂载会报错。必须在入口处尽早挂载。
-import { Quill as VueQuill } from 'vue-quill-editor'
-window.Quill = VueQuill
-
 import VueCropper from 'vue-cropper'
 import VueCropper from 'vue-cropper'
 Vue.use(VueCropper)
 Vue.use(VueCropper)
 Vue.component('cropperDlg', cropperDlg)
 Vue.component('cropperDlg', cropperDlg)