Jelajahi Sumber

0.0.14

完成富文本编辑器的开发
Sean 5 bulan lalu
induk
melakukan
50c783fdb2
3 mengubah file dengan 216 tambahan dan 79 penghapusan
  1. 1 1
      package.json
  2. 207 77
      src/views/news/creatNews.vue
  3. 8 1
      vue.config.js

+ 1 - 1
package.json

@@ -33,7 +33,7 @@
     "path-to-regexp": "2.4.0",
     "pinyin": "2.9.0",
     "quill": "^1.3.7",
-    "quill-image-uploader": "^1.3.0",
+    "quill-image-resize-module": "^3.0.0",
     "screenfull": "4.2.0",
     "script-loader": "0.7.2",
     "sortablejs": "1.8.4",

+ 207 - 77
src/views/news/creatNews.vue

@@ -7,9 +7,15 @@
           <el-checkbox v-model="form.islink">是否使用外链</el-checkbox>
         </el-form-item>
         <div v-if="form.islink==1">
+          <el-form-item label="导航池名称:" prop="category_name">
+            <el-cascader :key="parentKey" v-model="form.cat_arr_id" placeholder="请选择要绑定的父级导航" :props="parentData" filterable clearable></el-cascader>
+          </el-form-item>
           <el-form-item label="外链地址:" prop="linkurl">
             <el-input v-model="form.linkurl" autocomplete="off"></el-input>
           </el-form-item>
+          <el-form-item label="作者:" prop="author">
+            <el-input v-model="form.author" autocomplete="off"></el-input>
+          </el-form-item>
         </div>
         <div v-if="form.islink==0">
           <el-form-item label="导航池名称:" prop="category_name">
@@ -32,15 +38,20 @@
           <el-form-item label="资讯描述:" prop="introduce">
             <el-input type="textarea" v-model="form.introduce"></el-input>
           </el-form-item>
-          <div class="QuillTitle"><span>* </span>资讯内容:</div>
+          <div class="QuillTitle">
+            <span>* </span>资讯内容:
+            <el-button @click="toggleSourceMode">
+              {{ showHtml ? '切换到编辑模式' : '切换到源码模式' }}
+            </el-button>
+          </div>
           <el-form-item label="" prop="content">
             <div class="editor-container">
-              <quill-editor
-                  ref="quillEditor"
-                  v-model="form.content"
-                  :options="editorOptions"
-                  class="my-quill-editor"
-                />
+              <div v-if="showHtml">
+                <textarea v-model="editorHtml" style="width: 100%; height: 400px;"></textarea>
+              </div>
+              <div v-else>
+                <quill-editor ref="quillEditor" v-model="form.content" :options="editorOptions" class="my-quill-editor"/>
+              </div>
               <!-- 多图上传隐藏的input -->
               <input type="file" ref="multiFileInput" @change="handleMultipleFiles" multiple hidden accept="image/jpeg, image/png" />
             </div>
@@ -82,20 +93,18 @@
 </template>
 
 <script>
-// 引入 Quill 和相关插件
-import Quill from 'quill';
-import VueQuillEditor from 'vue-quill-editor';
-import 'quill/dist/quill.core.css';
+import { quillEditor } from 'vue-quill-editor';
 import 'quill/dist/quill.snow.css';
-import 'quill/dist/quill.bubble.css';
-import ImageUploader from 'quill-image-uploader';
+import ImageResize from 'quill-image-resize-module';
+import Quill from 'quill';  // 引入 Quill
+import Delta from 'quill-delta'; // 引入 Delta,用于手动修改文档
 
-// 注册插件
-Quill.register('modules/imageUploader', ImageUploader);
+// 注册 Image Resize 模块
+Quill.register('modules/imageResize', ImageResize);
 
 export default {
   components: {
-    QuillEditor: VueQuillEditor.quillEditor // 注册 QuillEditor 组件
+    quillEditor
   },
   data() {
     //0.全局操作 start ------------------------------------------------------------>
@@ -118,8 +127,9 @@ export default {
     //0.全局操作 end ------------------------------------------------------------>
     return {
       //1.表单项 start ------------------------------------------------------------>
-      editStatus:false,//当前页面是编辑还是添加
-      editId:"",//如果是编辑 添加资讯id
+      editStatus:false,
+
+      //提交表单
       form: {
         //1.1使用了外链
         title: '',//资讯标题
@@ -164,29 +174,44 @@ export default {
         fromurl:[{required:true,trigger:'blur',validator:validateEmpty}]
       },
       //1.3富文本编辑器配置
+      showHtml: false, //用于保存源码内容
+      editorHtml: '',
       editorOptions: {
         placeholder: '请输入内容...',
-        theme: 'snow',
+        theme: 'snow',  // 主题样式
         modules: {
           toolbar: {
             container: [
-              [{ 'font': [] }],
-              [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
-              [{ 'size': ['small', false, 'large', 'huge'] }],
-              ['bold', 'italic', 'underline', 'strike'],
-              [{ 'color': [] }, { 'background': [] }],
-              [{ 'script': 'sub' }, { 'script': 'super' }],
-              [{ 'list': 'ordered' }, { 'list': 'bullet' }],
-              [{ 'indent': '-1' }, { 'indent': '+1' }],
-              [{ 'align': [] }],
-              ['link', 'image'],  // 确保image按钮存在
-              ['clean']
+              [{ 'font': [] }],                                // 字体
+              [{ 'header': [1, 2, 3, 4, 5, 6, false] }],       // 标题
+              [{ 'size': ['small', false, 'large', 'huge'] }],  // 字体大小
+              ['bold', 'italic', 'underline', 'strike'],        // 加粗、斜体、下划线、删除线
+              [{ 'color': [] }, { 'background': [] }],          // 文字颜色、背景颜色
+              [{ 'script': 'sub' }, { 'script': 'super' }],     // 上标、下标
+              [{ 'list': 'ordered'}, { 'list': 'bullet' }],     // 列表
+              [{ 'indent': '-1'}, { 'indent': '+1' }],          // 缩进
+              [{ 'align': [] }],                                // 对齐方式
+              ['blockquote', 'code-block'],                     // 引用、代码块
+              ['link', 'image', 'video'],                       // 链接、图片、视频
+              ['clean'],
+              [{ 'html': true }]  // 添加自定义按钮的占位符
             ],
             handlers: {
               image: () => {
                 this.handleImageClick();
+              },
+              showHtml: function() {
+                this.$emit('toggleSourceMode');
               }
             }
+          },
+          imageResize: {
+            displayStyles: {
+              backgroundColor: 'black',
+              border: 'none',
+              color: 'white'
+            },
+            modules: ['Resize', 'DisplaySize', 'Toolbar']  // 启用不同的调整方式
           }
         }
       },
@@ -257,49 +282,7 @@ export default {
       // 阻止默认的上传行为
       return false;
     },
-    //1.2 编辑器上传图片
-    handleImageClick() {
-      this.$refs.multiFileInput.click(); // 打开文件选择框
-    },
-    handleMultipleFiles(event) {
-      const files = event.target.files;
-      if (files.length) {
-        this.uploadMultipleImages(files); // 处理多图片上传
-      }
-    },
-    uploadMultipleImages(files) {
-      const uploadPromises = [];
-      for (let i = 0; i < files.length; i++) {
-        uploadPromises.push(this.uploadImage(files[i]));
-      }
-
-      Promise.all(uploadPromises).then(urls => {
-        const quillEditor = this.$refs.quillEditor.quill;
-        urls.forEach(url => {
-          const range = quillEditor.getSelection();
-          quillEditor.insertEmbed(range.index, 'image', url); // 在编辑器中插入图片
-        });
-      }).catch(error => {
-        this.$message.error('图片上传失败,请重试!');
-      });
-    },
-    uploadImage(file) {
-      const formData = new FormData();
-      formData.append('file', file);
-      return this.$store.dispatch('pool/uploadFile', formData)
-        .then(res => {
-          if (res && res.data && res.data.imgUrl) {
-            return res.data.imgUrl;
-          } else {
-            throw new Error('图片上传失败');
-          }
-        })
-        .catch(error => {
-          this.$message.error('图片上传失败,请重试!');
-          throw error;
-        });
-    },
-    //1.3 提交表单
+    //1.2 提交表单
     addToServe(){
       //提交之前先判断是否为外链
       //如果使用了外链,清理掉除了外链以外的内容
@@ -328,17 +311,17 @@ export default {
         }
       })
     },
-    //1.4 清理表单
+    //1.3 清理表单
     cleatForm(type){
       if(type==1){
         //使用了外链,进行部分表单清理
-        this.form.cat_arr_id = "";
+        //this.form.cat_arr_id = "";
         this.form.level = "";
         this.form.imgurl = "";
         this.form.keyword = "";
         this.form.introduce = "";
         this.form.content = "";
-        this.form.author = "";
+        //this.form.author = "";
         this.form.hits = "";
         this.form.is_original = "";
         this.form.copyfrom = "";
@@ -463,8 +446,115 @@ export default {
       })
     },
     //跳转操作 end ------------------------------------------------------------>
+
+    //4.富文本编辑器 start ------------------------------------------------------------>
+    //4.1 编辑器点击上传图片
+    handleImageClick() {
+      this.$refs.multiFileInput.click(); // 打开文件选择框
+    },
+    handleMultipleFiles(event) {
+      const files = event.target.files;
+      if (files.length) {
+        this.uploadMultipleImages(files); // 处理多图片上传
+      }
+    },
+    uploadMultipleImages(files) {
+      const uploadPromises = [];
+      for (let i = 0; i < files.length; i++) {
+        uploadPromises.push(this.uploadImage(files[i]));
+      }
+
+      Promise.all(uploadPromises).then(urls => {
+        const quillEditor = this.$refs.quillEditor.quill;
+        urls.forEach(url => {
+          const range = quillEditor.getSelection();
+          quillEditor.insertEmbed(range.index, 'image', url); // 在编辑器中插入图片
+        });
+      }).catch(error => {
+        this.$message.error('图片上传失败,请重试!');
+      });
+    },
+    uploadImage(file) {
+      const formData = new FormData();
+      formData.append('file', file);
+      return this.$store.dispatch('pool/uploadFile', formData)
+        .then(res => {
+          if (res && res.data && res.data.imgUrl) {
+            return res.data.imgUrl;
+          } else {
+            throw new Error('图片上传失败');
+          }
+        })
+        .catch(error => {
+          this.$message.error('图片上传失败,请重试!');
+          throw error;
+        });
+    },
+
+    //4.2 图片粘贴上传
+    // 处理从网页粘贴的图片 URL
+    handleImageFromWeb(imageUrl) {
+      return new Promise((resolve) => {
+        console.log('开始下载图片:', imageUrl);
+
+        this.fetchImageAsBlob(imageUrl).then((blob) => {
+          console.log('图片已下载为 Blob:', blob);
+
+          const formData = new FormData();
+          formData.append('file', blob, 'image.jpg');
+
+          this.$store.dispatch('pool/uploadFile', formData).then((res) => {
+            if (res && res.data && res.data.imgUrl) {
+              console.log('图片上传成功:', res.data.imgUrl);
+              resolve(res.data.imgUrl);
+            } else {
+              console.log('图片上传失败,保留原 URL:', imageUrl);
+              resolve(imageUrl);
+            }
+          }).catch((error) => {
+            console.error('图片上传时出现错误:', error);
+            resolve(imageUrl);
+          });
+        }).catch((error) => {
+          console.error('图片下载失败:', error);
+          resolve(imageUrl);
+        });
+      });
+    },
+    fetchImageAsBlob(url) {
+      return fetch(url)
+        .then(response => {
+          if (!response.ok) {
+            throw new Error('Failed to fetch image');
+          }
+          return response.blob();
+        });
+    },
+    //编辑源码
+    toggleSourceMode() {
+      if (!this.showHtml) {
+        // 切换到源码模式,将编辑器内容同步到 textarea 中
+        this.editorHtml = this.$refs.quillEditor.quill.root.innerHTML;
+        this.showHtml = true; // 显示 textarea
+      } else {
+        // 切换回富文本模式,将 textarea 内容同步回编辑器
+        this.showHtml = false; // 显示 Quill 编辑器
+
+        // Quill 编辑器可能被销毁,所以使用 $nextTick 确保 DOM 渲染完成后再操作编辑器
+        this.$nextTick(() => {
+          if (this.$refs.quillEditor) {
+            this.$refs.quillEditor.quill.root.innerHTML = this.editorHtml;
+          } else {
+            console.error('Quill 编辑器实例未找到');
+          }
+        });
+      }
+    }
+    //富文本编辑器 end ------------------------------------------------------------>
+
   },
   mounted(){
+    //1.判断是新建还是回显
     if(this.$route.query.id!=undefined){
       this.editId = this.$route.query.id;
       this.editStatus = true;
@@ -474,7 +564,43 @@ export default {
       this.editStatus = false;
       console.log("添加新闻!")
     }
-  }
+
+    //复制内容到富文本 start ------------------------------------------------------------>
+    this.$nextTick(() => {
+      const quillEditor = this.$refs.quillEditor.quill;
+
+      if (quillEditor) {
+        console.log('Quill 编辑器已初始化');
+
+        // 在粘贴事件触发时,记录所有 img 的 src
+        quillEditor.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
+          if (node.tagName === 'IMG') {
+            const imageUrl = node.getAttribute('src');
+            console.log('检测到粘贴的图片 URL:', imageUrl);
+
+            if (imageUrl && !imageUrl.startsWith('data:') && !imageUrl.startsWith('file://')) {
+              // 先处理图片上传
+              this.handleImageFromWeb(imageUrl).then((uploadedImageUrl) => {
+                // 查找编辑器中所有 img 标签并替换 src
+                const imgs = quillEditor.root.querySelectorAll('img');
+                imgs.forEach((img) => {
+                  if (img.getAttribute('src') === imageUrl) {
+                    img.setAttribute('src', uploadedImageUrl);  // 替换 src
+                    console.log('图片 src 已替换为:', uploadedImageUrl);
+                  }
+                });
+              });
+            }
+          }
+          return delta;  // 返回原始 delta
+        });
+      } else {
+        console.error('Quill 初始化失败');
+      }
+
+    });
+    //复制富文本 end ------------------------------------------------------------>
+  },
 };
 </script>
 
@@ -558,4 +684,8 @@ export default {
     height: 150px;
     display: block;
   }
+
+  .my-quill-editor {
+    height: 320px;
+  }
 </style>

+ 8 - 1
vue.config.js

@@ -1,6 +1,7 @@
 'use strict'
 const path = require('path')
 const defaultSettings = require('./src/settings.js')
+const webpack = require('webpack');
 
 function resolve(dir) {
   return path.join(__dirname, dir)
@@ -59,7 +60,13 @@ module.exports = {
       alias: {
         '@': resolve('src')
       }
-    }
+    },
+    plugins: [
+      new webpack.ProvidePlugin({
+        'window.Quill': 'quill/dist/quill.js',  // 确保 Quill 在全局作用域内可用
+        Quill: 'quill/dist/quill.js'  // 手动引入 Quill
+      })
+    ]
   },
   chainWebpack(config) {
     // it can improve the speed of the first screen, it is recommended to turn on preload