index.vue 7.9 KB

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