houzhongjian
2024-07-11 759b1c71011abd6b58c37d2566f3f3c208c2f1b2
提交 | 用户 | 时间
759b1c 1 <template>
H 2   <div>
3     <el-upload
4       :action="uploadFileUrl"
5       :before-upload="handleBeforeUpload"
6       :on-success="handleUploadSuccess"
7       :on-error="handleUploadError"
8       name="file"
9       :show-file-list="false"
10       :headers="headers"
11       style="display: none"
12       ref="upload"
13       v-if="this.type === 'url'"
14     >
15     </el-upload>
16     <div class="editor" ref="editor" :style="styles"></div>
17   </div>
18 </template>
19
20 <script>
21 import Quill from "quill";
22 import "quill/dist/quill.core.css";
23 import "quill/dist/quill.snow.css";
24 import "quill/dist/quill.bubble.css";
25 import { getAccessToken } from "@/utils/auth";
26
27 export default {
28   name: "Editor",
29   props: {
30     /* 编辑器的内容 */
31     value: {
32       type: String,
33       default: "",
34     },
35     /* 高度 */
36     height: {
37       type: Number,
38       default: null,
39     },
40     /* 最小高度 */
41     minHeight: {
42       type: Number,
43       default: null,
44     },
45     /* 只读 */
46     readOnly: {
47       type: Boolean,
48       default: false,
49     },
50     // 上传文件大小限制(MB)
51     fileSize: {
52       type: Number,
53       default: 5,
54     },
55     /* 类型(base64格式、url格式) */
56     type: {
57       type: String,
58       default: "url",
59     }
60   },
61   data() {
62     return {
63       uploadFileUrl: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
64       headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
65       Quill: null,
66       currentValue: "",
67       options: {
68         theme: "snow",
69         bounds: document.body,
70         debug: "warn",
71         modules: {
72           // 工具栏配置
73           toolbar: [
74             ["bold", "italic", "underline", "strike"],       // 加粗 斜体 下划线 删除线
75             ["blockquote", "code-block"],                    // 引用  代码块
76             [{ list: "ordered" }, { list: "bullet" }],       // 有序、无序列表
77             [{ indent: "-1" }, { indent: "+1" }],            // 缩进
78             [{ size: ["small", false, "large", "huge"] }],   // 字体大小
79             [{ header: [1, 2, 3, 4, 5, 6, false] }],         // 标题
80             [{ color: [] }, { background: [] }],             // 字体颜色、字体背景颜色
81             [{ align: [] }],                                 // 对齐方式
82             ["clean"],                                       // 清除文本格式
83             ["link", "image", "video"]                       // 链接、图片、视频
84           ],
85         },
86         placeholder: "请输入内容",
87         readOnly: true,
88       },
89     };
90   },
91   computed: {
92     styles() {
93       let style = {};
94       if (this.minHeight) {
95         style.minHeight = `${this.minHeight}px`;
96       }
97       if (this.height) {
98         style.height = `${this.height}px`;
99       }
100       return style;
101     },
102   },
103   watch: {
104     value: {
105       handler(val) {
106         if (val !== this.currentValue) {
107           this.currentValue = val === null ? "" : val;
108           if (this.Quill) {
109             this.Quill.pasteHTML(this.currentValue);
110           }
111         }
112       },
113       immediate: true,
114     },
115   },
116   mounted() {
117     this.init();
118   },
119   beforeDestroy() {
120     this.Quill = null;
121   },
122   methods: {
123     init() {
124       const editor = this.$refs.editor;
125       this.Quill = new Quill(editor, this.options);
126       // 取消自动聚焦 start
127       this.$nextTick(()=>{
128         this.Quill.blur();
129         if(!this.readOnly){
130           this.Quill.enable();
131         }
132       });
133       // 如果设置了上传地址则自定义图片上传事件
134       if (this.type === 'url') {
135         let toolbar = this.Quill.getModule("toolbar");
136         toolbar.addHandler("image", (value) => {
137           this.uploadType = "image";
138           if (value) {
139             this.$refs.upload.$children[0].$refs.input.click();
140           } else {
141             this.quill.format("image", false);
142           }
143         });
144       }
145       this.Quill.pasteHTML(this.currentValue);
146       this.Quill.on("text-change", (delta, oldDelta, source) => {
147         const html = this.$refs.editor.children[0].innerHTML;
148         const text = this.Quill.getText();
149         const quill = this.Quill;
150         this.currentValue = html;
151         this.$emit("input", html);
152         this.$emit("on-change", { html, text, quill });
153       });
154       this.Quill.on("text-change", (delta, oldDelta, source) => {
155         this.$emit("on-text-change", delta, oldDelta, source);
156       });
157       this.Quill.on("selection-change", (range, oldRange, source) => {
158         this.$emit("on-selection-change", range, oldRange, source);
159       });
160       this.Quill.on("editor-change", (eventName, ...args) => {
161         this.$emit("on-editor-change", eventName, ...args);
162       });
163     },
164     // 上传前校检格式和大小
165     handleBeforeUpload(file) {
166       // 校检文件大小
167       if (this.fileSize) {
168         const isLt = file.size / 1024 / 1024 < this.fileSize;
169         if (!isLt) {
170           this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);
171           return false;
172         }
173       }
174       return true;
175     },
176     handleUploadSuccess(res, file) {
177       // 获取富文本组件实例
178       let quill = this.Quill;
179       // 如果上传成功
180       // edit by iailab
181       if (res.code === 200 || res.code === 0) {
182         // 获取光标所在位置
183         let length = quill.getSelection().index;
184         // 插入图片  res.url为服务器返回的图片地址
185         // edit by iailab
186         quill.insertEmbed(length, "image", res.data);
187         // 调整光标到最后
188         quill.setSelection(length + 1);
189       } else {
190         this.$message.error("图片插入失败");
191       }
192     },
193     handleUploadError() {
194       this.$message.error("图片插入失败");
195     },
196   },
197 };
198 </script>
199
200 <style>
201 .editor, .ql-toolbar {
202   white-space: pre-wrap !important;
203   line-height: normal !important;
204 }
205 .quill-img {
206   display: none;
207 }
208 .ql-snow .ql-tooltip[data-mode="link"]::before {
209   content: "请输入链接地址:";
210 }
211 .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
212   border-right: 0px;
213   content: "保存";
214   padding-right: 0px;
215 }
216
217 .ql-snow .ql-tooltip[data-mode="video"]::before {
218   content: "请输入视频地址:";
219 }
220
221 .ql-snow .ql-picker.ql-size .ql-picker-label::before,
222 .ql-snow .ql-picker.ql-size .ql-picker-item::before {
223   content: "14px";
224 }
225 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
226 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
227   content: "10px";
228 }
229 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
230 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
231   content: "18px";
232 }
233 .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
234 .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
235   content: "32px";
236 }
237
238 .ql-snow .ql-picker.ql-header .ql-picker-label::before,
239 .ql-snow .ql-picker.ql-header .ql-picker-item::before {
240   content: "文本";
241 }
242 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
243 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
244   content: "标题1";
245 }
246 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
247 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
248   content: "标题2";
249 }
250 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
251 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
252   content: "标题3";
253 }
254 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
255 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
256   content: "标题4";
257 }
258 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
259 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
260   content: "标题5";
261 }
262 .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
263 .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
264   content: "标题6";
265 }
266
267 .ql-snow .ql-picker.ql-font .ql-picker-label::before,
268 .ql-snow .ql-picker.ql-font .ql-picker-item::before {
269   content: "标准字体";
270 }
271 .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
272 .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
273   content: "衬线字体";
274 }
275 .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
276 .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
277   content: "等宽字体";
278 }
279 </style>