| <template> | 
|   <div> | 
|     <div :id="`${id}-toolbar`"> | 
|       <button class="ql-bold" title="粗体"></button> | 
|       <button class="ql-italic" title="斜体"></button> | 
|       <button class="ql-underline" title="下划线"></button> | 
|       <button class="ql-strike" title="删除线"></button> | 
|       <select class="ql-size" title="字体大小"> | 
|         <option value="small"></option> | 
|         <option selected></option> | 
|         <option value="large"></option> | 
|         <option value="huge"></option> | 
|       </select> | 
|       <select class="ql-header" title="标题大小"> | 
|         <option value="1"></option> | 
|         <option value="2"></option> | 
|         <option value="3"></option> | 
|         <option value="4"></option> | 
|         <option value="5"></option> | 
|         <option value="6"></option> | 
|         <option selected></option> | 
|       </select> | 
|       <select class="ql-font" title="字体"></select> | 
|       <select class="ql-align" title="对齐方式"></select> | 
|       <select class="ql-color" title="字体颜色"></select> | 
|       <select class="ql-background" title="背景颜色"></select> | 
|       <button class="ql-blockquote" title="引用"></button> | 
|       <button class="ql-code-block" title="代码块"></button> | 
|       <button class="ql-list" value="ordered" title="数字列表"></button> | 
|       <button class="ql-list" value="bullet" title="点列表"></button> | 
|       <button class="ql-script" value="sub" title="右下标"></button> | 
|       <button class="ql-script" value="super" title="右上标"></button> | 
|       <button class="ql-indent" value="-1" title="向左缩进"></button> | 
|       <button class="ql-indent" value="+1" title="向右缩进"></button> | 
|       <button class="ql-clean" title="清空样式"></button> | 
|   | 
|       <button class="ql-link" title="链接"></button> | 
|       <button class="ql-image" title="插入图片" v-if="base64"></button> | 
|       <div class="q-menu" title="插入图片" v-if="!base64"> | 
|         <Upload | 
|           :action="uploadFileUrl" | 
|           :headers="accessToken" | 
|           :on-success="handleSuccess" | 
|           :on-error="handleError" | 
|           :format="['jpg','jpeg','png','gif','bmp']" | 
|           accept=".jpg, .jpeg, .png, .gif, .bmp" | 
|           :max-size="5120" | 
|           :on-format-error="handleFormatError" | 
|           :on-exceeded-size="handleMaxSize" | 
|           :before-upload="beforeUpload" | 
|           :show-upload-list="false" | 
|           ref="qup" | 
|         > | 
|           <Icon type="md-images" size="20" /> | 
|         </Upload> | 
|       </div> | 
|       <button class="ql-video" title="插入视频链接"></button> | 
|       <div class="q-menu" title="编辑HTML代码" @click="editHTML" v-if="expandHtml"> | 
|         <Icon type="md-code-working" size="22" /> | 
|       </div> | 
|       <div class="q-menu" title="预览" @click="fullscreenModal=true" v-if="expandPreview"> | 
|         <Icon type="ios-eye" size="24" /> | 
|       </div> | 
|       <div class="q-menu" title="清空" @click="clear" v-if="expandClear"> | 
|         <Icon type="md-trash" size="18" style="display: block;" /> | 
|       </div> | 
|     </div> | 
|   | 
|     <div :id="id" :style="{height: height}"></div> | 
|   | 
|     <Modal | 
|       title="编辑html代码" | 
|       v-model="showHTMLModal" | 
|       :mask-closable="false" | 
|       :width="900" | 
|       :fullscreen="full" | 
|     > | 
|       <Input | 
|         v-if="!full" | 
|         v-model="dataEdit" | 
|         :rows="15" | 
|         type="textarea" | 
|         style="max-height:60vh;overflow:auto;" | 
|       /> | 
|       <Input v-if="full" v-model="dataEdit" :rows="32" type="textarea" /> | 
|       <div slot="footer"> | 
|         <Button @click="full=!full" icon="md-expand">全屏开/关</Button> | 
|         <Button @click="editHTMLOk" type="primary" icon="md-checkmark-circle-outline">确定保存</Button> | 
|       </div> | 
|     </Modal> | 
|     <Modal title="预览" v-model="fullscreenModal" fullscreen> | 
|       <div v-html="data">{{data}}</div> | 
|       <div slot="footer"> | 
|         <Button @click="fullscreenModal=false">关闭</Button> | 
|       </div> | 
|     </Modal> | 
|   </div> | 
| </template> | 
|   | 
| <script> | 
| import { uploadFile } from "@/api/index"; | 
| import Quill from "quill"; | 
| import "quill/dist/quill.snow.css"; | 
| import xss from "xss"; | 
| export default { | 
|   name: "editor", | 
|   props: { | 
|     id: { | 
|       type: String, | 
|       default: "quill" | 
|     }, | 
|     value: String, | 
|     base64: { | 
|       type: Boolean, | 
|       default: false | 
|     }, | 
|     height: { | 
|       type: String, | 
|       default: "300px" | 
|     }, | 
|     expandHtml: { | 
|       type: Boolean, | 
|       default: true | 
|     }, | 
|     expandPreview: { | 
|       type: Boolean, | 
|       default: true | 
|     }, | 
|     expandClear: { | 
|       type: Boolean, | 
|       default: true | 
|     }, | 
|     openXss: { | 
|       type: Boolean, | 
|       default: false | 
|     } | 
|   }, | 
|   data() { | 
|     return { | 
|       accessToken: {}, | 
|       uploadFileUrl: uploadFile, | 
|       editor: null, | 
|       options: { | 
|         theme: "snow", | 
|         modules: { | 
|           toolbar: `#${this.id}-toolbar` | 
|         }, | 
|         placeholder: "在这输入内容 ..." | 
|       }, | 
|       data: this.value, // 富文本数据 | 
|       dataEdit: "", // 编辑数据 | 
|       showHTMLModal: false, // 显示html | 
|       full: false, // html全屏开关 | 
|       fullscreenModal: false // 显示全屏预览 | 
|     }; | 
|   }, | 
|   methods: { | 
|     initEditor() { | 
|       this.accessToken = { | 
|         accessToken: this.getStore("accessToken") | 
|       }; | 
|       this.editor = new Quill(`#${this.id}`, this.options); | 
|       let that = this; | 
|       if (this.value) { | 
|         this.editor.pasteHTML(this.value); | 
|       } | 
|       this.editor.on("text-change", function(delta, oldDelta, source) { | 
|         let html = that.editor.container.firstChild.innerHTML; | 
|         if (that.openXss) { | 
|           that.data = xss(html); | 
|         } else { | 
|           that.data = html; | 
|         } | 
|         that.$emit("input", that.data); | 
|         that.$emit("on-change", that.data); | 
|       }); | 
|     }, | 
|     handleFormatError(file) { | 
|       this.$Notice.warning({ | 
|         title: "不支持的文件格式", | 
|         desc: | 
|           "所选文件‘ " + | 
|           file.name + | 
|           " ’格式不正确, 请选择 .jpg .jpeg .png .gif .bmp格式文件" | 
|       }); | 
|     }, | 
|     handleMaxSize(file) { | 
|       this.$Notice.warning({ | 
|         title: "文件大小过大", | 
|         desc: "所选文件‘ " + file.name + " ’大小过大, 不得超过 5M." | 
|       }); | 
|     }, | 
|     beforeUpload() { | 
|       return true; | 
|     }, | 
|     handleSuccess(res, file) { | 
|       if (res.success) { | 
|         let url = res.result; | 
|         // 获取光标位置 | 
|         let range = this.editor.getSelection(true); | 
|         // 总元素 | 
|         let delta = this.editor.getContents().length; | 
|         let index; | 
|         if (range) { | 
|           index = range.index; | 
|         } else { | 
|           index = delta; | 
|         } | 
|         // 插入元素 | 
|         this.editor.insertEmbed(index, "image", url); | 
|         this.editor.setSelection(index + 1, 0); | 
|       } else { | 
|         this.$Message.error(res.message); | 
|       } | 
|     }, | 
|     handleError(error, file, fileList) { | 
|       this.$Message.error(error.toString()); | 
|     }, | 
|     editHTML() { | 
|       this.dataEdit = this.data; | 
|       this.showHTMLModal = true; | 
|     }, | 
|     editHTMLOk() { | 
|       this.editor.pasteHTML(this.dataEdit); | 
|       this.$emit("input", this.data); | 
|       this.$emit("on-change", this.data); | 
|       this.showHTMLModal = false; | 
|     }, | 
|     clear() { | 
|       this.$Modal.confirm({ | 
|         title: "确认清空", | 
|         content: "确认要清空编辑器内容?清空后不能撤回", | 
|         onOk: () => { | 
|           this.data = ""; | 
|           this.editor.pasteHTML(this.data); | 
|           this.$emit("input", this.data); | 
|           this.$emit("on-change", this.data); | 
|         } | 
|       }); | 
|     }, | 
|     setData(value) { | 
|       if (!this.editor) { | 
|         this.initEditor(); | 
|       } | 
|       if (value != this.data) { | 
|         this.data = value; | 
|         let index = this.editor.selection.savedRange.index; | 
|         this.editor.pasteHTML(this.data); | 
|         this.editor.setSelection(index, 0); | 
|         this.$emit("input", this.data); | 
|         this.$emit("on-change", this.data); | 
|       } | 
|     } | 
|   }, | 
|   watch: { | 
|     value(val) { | 
|       this.setData(val); | 
|     } | 
|   }, | 
|   mounted() { | 
|     this.initEditor(); | 
|   } | 
| }; | 
| </script> | 
|   | 
| <style lang="less"> | 
| .q-menu { | 
|   margin: 0 3px; | 
|   display: inline-block; | 
|   cursor: pointer; | 
|   color: #444; | 
|   :hover { | 
|     color: #06c; | 
|   } | 
| } | 
| .ql-tooltip { | 
|   left: 30% !important; | 
| } | 
|   | 
| // 中文 | 
| .ql-snow .ql-tooltip.ql-editing a.ql-action::after { | 
|     border-right: 0px; | 
|     content: '保存'; | 
|     padding-right: 0px; | 
| } | 
| .ql-snow .ql-tooltip[data-mode=video]::before { | 
|     content: "请输入视频地址:"; | 
| } | 
| .ql-snow .ql-picker.ql-size .ql-picker-label::before, | 
| .ql-snow .ql-picker.ql-size .ql-picker-item::before { | 
|   content: "14px"; | 
| } | 
|   | 
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before, | 
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before { | 
|   content: "10px"; | 
| } | 
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before, | 
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before { | 
|   content: "18px"; | 
| } | 
| .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before, | 
| .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before { | 
|   content: "32px"; | 
| } | 
|   | 
| .ql-snow .ql-picker.ql-header .ql-picker-label::before, | 
| .ql-snow .ql-picker.ql-header .ql-picker-item::before { | 
|   content: '标题'; | 
| } | 
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, | 
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { | 
|   content: '标题1'; | 
| } | 
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, | 
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { | 
|   content: '标题2'; | 
| } | 
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, | 
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { | 
|   content: '标题3'; | 
| } | 
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, | 
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { | 
|   content: '标题4'; | 
| } | 
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, | 
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { | 
|   content: '标题5'; | 
| } | 
| .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, | 
| .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { | 
|   content: '标题6'; | 
| } | 
|   | 
| .ql-snow .ql-picker.ql-font .ql-picker-label::before, | 
| .ql-snow .ql-picker.ql-font .ql-picker-item::before { | 
|   content: "标准字体"; | 
| } | 
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before, | 
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before { | 
|   content: "衬线字体"; | 
| } | 
| .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before, | 
| .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before { | 
|   content: "等宽字体"; | 
| } | 
| </style> |