QuillEditor 组件封装:
需要 使用 npm 安装依赖:
"vue-quill-editor": "^3.0.6",
"quill-image-resize-module": "^3.0.0",
QuillEditor .vue 代码:
1 <template> 2 <div> 3 <quill-editor 4 class="editor" 5 ref="myTextEditor" 6 v-model="editorContent" 7 :options="editorOption" 8 @change="onEditorChange($event)" 9 @ready="ready($event)" 10 > 11 </quill-editor> 12 <a-upload 13 class="ant-my-uploader" 14 style="display:none" 15 action="/content/file/upload-oss-image" 16 :before-upload="beforeUpload" 17 @change="handleChange" 18 > 19 <a-button> <a-icon type="upload" />Upload </a-button> 20 </a-upload> 21 <div> 22 剩余可输入 23 <span :style="{ color: 5000 - innerText.length < 0 ? ‘red‘ : ‘black‘ }">{{ 24 5000 - innerText.length 25 }}</span> 26 字 27 </div> 28 </div> 29 </template> 30 31 <script> 32 import { quillEditor } from "vue-quill-editor"; 33 import "quill/dist/quill.core.css"; 34 import "quill/dist/quill.snow.css"; 35 import "quill/dist/quill.bubble.css"; 36 import Quill from "quill"; 37 import ImageResize from "quill-image-resize-module"; 38 Quill.register("modules/imageResize", ImageResize); 39 // 自定义文字大小 40 let fontSizeStyle = Quill.import("attributors/style/size"); 41 fontSizeStyle.whitelist = [ 42 "10px", 43 "11px", 44 "12px", 45 "13px", 46 "14px", 47 "15px", 48 "16px", 49 "17px", 50 "18px", 51 "19px", 52 "20px", 53 "21px", 54 "22px", 55 "23px", 56 "24px", 57 "25px", 58 "26px" 59 ]; 60 Quill.register(fontSizeStyle, true); 61 import { lineHeightStyle } from "@/utils/lineheight"; 62 //工具菜单栏配置 63 const toolbarOptions = [ 64 ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线 65 ["blockquote", "code-block"], // 引用 代码块 66 [{ header: 1 }, { header: 2 }], // 1、2 级标题 67 [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表 68 [{ script: "sub" }, { script: "super" }], // 上标/下标 69 [{ indent: "-1" }, { indent: "+1" }], // 缩进 70 // [{‘direction‘: ‘rtl‘}], // 文本方向 71 // [{ size: ["small", false, "large", "huge"] }], // 字体大小 72 [{ size: fontSizeStyle.whitelist }], // 字体大小 73 [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题 74 [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色 75 [{ font: [] }], // 字体种类 76 [{ align: [] }], // 对齐方式 77 [{ lineheight: ["initial", "1", "1.5", "1.75", "2", "3", "4", "5"] }], // 对齐方式 78 ["clean"], // 清除文本格式 79 ["link", "image", "video"] // 链接、图片、视频 80 ]; 81 export default { 82 components: { 83 quillEditor 84 }, 85 props: { 86 content: String 87 }, 88 data() { 89 return { 90 innerText: "", 91 editorContent: null, 92 editorOption: { 93 placeholder: "请在这里输入", //提示 94 readyOnly: false, //是否只读 95 theme: "snow", //主题 snow/bubble 96 syntax: true, //语法检测 97 modules: { 98 imageResize: { 99 //添加 100 displayStyles: { 101 //添加 102 backgroundColor: "black", 103 border: "none", 104 color: "white" 105 }, 106 modules: ["Resize", "DisplaySize", "Toolbar"] //添加 107 }, 108 toolbar: { 109 container: toolbarOptions, 110 handlers: { 111 image: function(value) { 112 if (value) { 113 console.log(value); 114 // 触发input框选择图片文件 115 document.querySelector(".ant-my-uploader input").click(); 116 } else { 117 this.quill.format("image", false); 118 } 119 }, 120 lineheight: function(value) { 121 if (value) { 122 this.quill.format("lineHeight", value); 123 } else { 124 console.log(value); 125 } 126 } 127 } 128 } 129 } 130 }, 131 loading: false 132 }; 133 }, 134 computed: { 135 editor() { 136 return this.$refs.myTextEditor.quillEditor; 137 } 138 }, 139 mounted() { 140 // Quill.register({ "formats/line-height": LineHeight }, true); 141 }, 142 watch: { 143 content: { 144 handler: function(val) { 145 this.editorContent = val; 146 }, 147 immediate: true 148 } 149 }, 150 methods: { 151 ready() { 152 Quill.register({ "formats/lineHeight": lineHeightStyle }, true); 153 }, 154 onEditorChange(editor) { 155 // this.editorContent = editor.html; 156 this.innerText = editor.text.replace(/[\r\n]$/g, ""); 157 this.$emit("onChange", { 158 content: editor.html, 159 textLength: this.innerText.length 160 }); 161 }, 162 // 上传图片 163 uploadSuccess(val) { 164 let quill = this.$refs.myTextEditor.quill; 165 // 获取光标所在位置 166 let length = quill.getSelection().index; 167 // 插入图片 res.url为服务器返回的图片地址 168 quill.insertEmbed(length, "image", val); 169 // 调整光标到最后 170 quill.setSelection(length + 1); 171 }, 172 handleChange(info) { 173 switch (info.file.status) { 174 case "uploading": 175 this.loading = true; 176 break; 177 case "done": 178 this.loading = false; 179 // eslint-disable-next-line no-case-declarations 180 const { response } = info.file; // 请求返回的数据 181 if (response.code == 200) { 182 this.uploadSuccess(response.data); 183 this.$message.success({ 184 content: "上传成功!", 185 key: "uploadPic", 186 duration: 2 187 }); 188 } else { 189 this.$message.error({ 190 content: response.msg || "上传发生错误" + response.code, 191 key: "uploadPic", 192 duration: 2 193 }); 194 } 195 break; 196 case "error": 197 this.loading = false; 198 // 错误消息提示 199 this.$message.error({ 200 content: "网络错误请稍后再试", 201 key: "uploadPic", 202 duration: 2 203 }); 204 break; 205 default: 206 break; 207 } 208 }, 209 beforeUpload(file) { 210 return new Promise((resolve, reject) => { 211 this.$message.success({ 212 content: "上传中", 213 key: "uploadPic", 214 duration: 2 215 }); 216 const isJpgOrPng = 217 file.type === "image/jpeg" || 218 file.type === "image/png" || 219 file.type === "image/jpg"; 220 if (!isJpgOrPng) { 221 this.$message.error("图片仅支持 jpeg 或 png 或 jpg 格式"); 222 return reject(false); 223 } 224 // const isLt300kb = file.size / 1024 < 300; 225 const isLt2M = file.size / 1024 / 1024 < 2; 226 if (!isLt2M) { 227 this.$message.error("图片大于 2M"); 228 return reject(false); 229 } 230 return resolve(true); 231 }); 232 } 233 } 234 }; 235 </script> 236 237 <style> 238 .editor { 239 line-height: normal !important; 240 /* height: 400px; */ 241 background-color: #ffffff; 242 } 243 .ql-snow .ql-tooltip[data-mode="link"]::before { 244 content: "请输入链接地址:"; 245 } 246 .ql-snow .ql-tooltip.ql-editing a.ql-action::after { 247 border-right: 0px; 248 content: "保存"; 249 padding-right: 0px; 250 } 251 .ql-snow .ql-tooltip a.ql-action::after { 252 content: "编辑"; 253 } 254 .ql-snow .ql-tooltip a.ql-remove::before { 255 content: "移除"; 256 } 257 .ql-snow .ql-tooltip[data-mode="video"]::before { 258 content: "请输入视频地址:"; 259 } 260 .ql-snow .ql-picker.ql-size .ql-picker-label::before, 261 .ql-snow .ql-picker.ql-size .ql-picker-item::before { 262 content: "14px"; 263 } 264 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before, 265 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before { 266 content: "10px"; 267 } 268 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before, 269 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before { 270 content: "18px"; 271 } 272 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before, 273 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before { 274 content: "32px"; 275 } 276 277 .ql-snow .ql-picker.ql-header .ql-picker-label::before, 278 .ql-snow .ql-picker.ql-header .ql-picker-item::before { 279 content: "文本"; 280 } 281 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, 282 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { 283 content: "标题1"; 284 } 285 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, 286 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { 287 content: "标题2"; 288 } 289 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, 290 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { 291 content: "标题3"; 292 } 293 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, 294 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { 295 content: "标题4"; 296 } 297 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, 298 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { 299 content: "标题5"; 300 } 301 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, 302 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { 303 content: "标题6"; 304 } 305 .ql-snow .ql-picker.ql-font .ql-picker-label::before, 306 .ql-snow .ql-picker.ql-font .ql-picker-item::before { 307 content: "标准字体"; 308 } 309 .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before, 310 .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before { 311 content: "衬线字体"; 312 } 313 .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before, 314 .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before { 315 content: "等宽字体"; 316 } 317 /* 编辑器内部出现滚动条 */ 318 .ql-container { 319 /* overflow-y: auto; */ 320 height: 400px !important; 321 } 322 /*滚动条整体样式*/ 323 .ql-container ::-webkit-scrollbar { 324 width: 10px; /*竖向滚动条的宽度*/ 325 height: 10px; /*横向滚动条的高度*/ 326 } 327 .ql-container ::-webkit-scrollbar-thumb { 328 /*滚动条里面的小方块*/ 329 background: #666666; 330 border-radius: 5px; 331 } 332 .ql-container ::-webkit-scrollbar-track { 333 /*滚动条轨道的样式*/ 334 background: #ccc; 335 border-radius: 5px; 336 } 337 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="10px"]::before, 338 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="10px"]::before { 339 content: "10px"; 340 } 341 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="11px"]::before, 342 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="11px"]::before { 343 content: "11px"; 344 } 345 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="12px"]::before, 346 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="12px"]::before { 347 content: "12px"; 348 } 349 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="13px"]::before, 350 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="13px"]::before { 351 content: "13px"; 352 } 353 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="14px"]::before, 354 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before { 355 content: "14px"; 356 } 357 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="15px"]::before, 358 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="15px"]::before { 359 content: "15px"; 360 } 361 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before, 362 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before { 363 content: "16px"; 364 } 365 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="17px"]::before, 366 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="17px"]::before { 367 content: "17px"; 368 } 369 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before, 370 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before { 371 content: "18px"; 372 } 373 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="19px"]::before, 374 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="19px"]::before { 375 content: "19px"; 376 } 377 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before, 378 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before { 379 content: "20px"; 380 } 381 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="21px"]::before, 382 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="21px"]::before { 383 content: "21px"; 384 } 385 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="22px"]::before, 386 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="22px"]::before { 387 content: "22px"; 388 } 389 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="23px"]::before, 390 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="23px"]::before { 391 content: "23px"; 392 } 393 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="24px"]::before, 394 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="24px"]::before { 395 content: "24px"; 396 } 397 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="25px"]::before, 398 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="25px"]::before { 399 content: "25px"; 400 } 401 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="26px"]::before, 402 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="26px"]::before { 403 content: "26px"; 404 } 405 .ql-snow .ql-picker.ql-lineheight .ql-picker-label::before{ 406 content: "行高"; 407 } 408 .ql-snow 409 .ql-picker.ql-lineheight 410 .ql-picker-item[data-value="initial"]::before { 411 content: "默认"; 412 } 413 .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="1"]::before { 414 content: "1"; 415 } 416 .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="1.5"]::before { 417 content: "1.5"; 418 } 419 .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="1.75"]::before { 420 content: "1.75"; 421 } 422 .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="2"]::before { 423 content: "2"; 424 } 425 .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="3"]::before { 426 content: "3"; 427 } 428 .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="4"]::before { 429 content: "4"; 430 } 431 .ql-snow .ql-picker.ql-lineheight .ql-picker-item[data-value="5"]::before { 432 content: "5"; 433 } 434 .ql-snow .ql-picker.ql-lineheight { 435 width: 70px; 436 } 437 </style>
vue-quill-editor + + antd 组件封装(包含图片上传)
原文:https://www.cnblogs.com/kitty-blog/p/14031723.html