image.png

问题

编辑器在一个页面使用多个富文本框上传图片时报

  1. TypeError: Cannot read property ‘index‘ of null

问题原因

在上传图片成功方法中,代码是这样子的:

  1. quillImgSuccess(res, file) {
  2. console.log(res);
  3. console.log(file);
  4. // res为图片服务器返回的数据
  5. // 获取富文本组件实例
  6. let quill = this.$refs.quillEditor.quill;
  7. // 如果上传成功
  8. if (res.code == 200) {
  9. // 获取光标所在位置
  10. let length = quill.getSelection().index;
  11. // 插入图片 res.url为服务器返回的图片地址
  12. console.log(res)
  13. quill.insertEmbed(length, "image", risun.util.nginxServerUrl()+res.fileName);
  14. // 调整光标到最后
  15. quill.setSelection(length + 1);
  16. } else {
  17. this.$message.error("图片插入失败");
  18. }
  19. },
  20. // 富文本图片上传失败
  21. uploadError() {
  22. // loading动画消失
  23. this.$message.error("图片插入失败");
  24. }

其中的这一句:

  1. // 获取光标所在位置
  2. let length = quill.getSelection().index;

会报这个错误。究其原因,其实是

  1. <quill-editor
  2. class="editor"
  3. v-model="content"
  4. ref="quillEditor"
  5. :options="editorOption"
  6. @blur="onEditorBlur($event)"
  7. @focus="onEditorFocus($event)"
  8. @change="onEditorChange($event)"
  9. ></quill-editor>

中的

  1. ref="quillEditor"

请注意,这里的 ref 被写死了。在同一个页面使用多个 vue-quill-editor 时,这个上传完成后调用的方法 quillImgSuccess 用 this 获得的对象只能获得第一个富文本框实例!这就是为什么出错的原因!

解决方法

完整代码如下

  1. <template>
  2. <div>
  3. <!-- 图片上传组件辅助 -->
  4. <el-upload
  5. :key="randomString(3)"
  6. class="avatar-uploader quill-img"
  7. :action="uploadImgUrl"
  8. name="file"
  9. :headers="headers"
  10. :show-file-list="false"
  11. :on-success="quillImgSuccess"
  12. :on-error="uploadError"
  13. :before-upload="quillImgBefore"
  14. accept='.jpg,.jpeg,.png,.gif,.jfif'
  15. ></el-upload>
  16. <!-- 富文本组件 -->
  17. <quill-editor
  18. class="ql-editor"
  19. v-model="content"
  20. :ref="toref"
  21. :options="editorOption"
  22. @blur="onEditorBlur($event)"
  23. @focus="onEditorFocus($event)"
  24. @change="onEditorChange($event)"
  25. ></quill-editor>
  26. </div>
  27. </template>
  28. <script>
  29. import { getToken } from '@/utils/auth'
  30. // import { getToken } from "@/api/system/upload.js";
  31. // 工具栏配置
  32. const toolbarOptions = [
  33. ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
  34. ["blockquote", "code-block"], // 引用 代码块
  35. [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
  36. [{ indent: "-1" }, { indent: "+1" }], // 缩进
  37. [{ size: ["small", false, "large", "huge"] }], // 字体大小
  38. [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
  39. [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
  40. [{ align: [] }], // 对齐方式
  41. ["clean"], // 清除文本格式
  42. ["link", "image", /*"video"*/] // 链接、图片、视频
  43. ];
  44. import { quillEditor } from "vue-quill-editor";
  45. import "quill/dist/quill.core.css";
  46. import "quill/dist/quill.snow.css";
  47. import "quill/dist/quill.bubble.css";
  48. export default {
  49. props: {
  50. /* 编辑器的内容 */
  51. value: {
  52. type: String
  53. },
  54. /* 图片大小 */
  55. maxSize: {
  56. type: Number,
  57. default: 4000 //kb
  58. },
  59. toref: {
  60. type: String,
  61. default:"quillEditor"
  62. },
  63. quillIndex:{
  64. type:Number,
  65. default:0
  66. },
  67. },
  68. components: { quillEditor },
  69. data() {
  70. return {
  71. content: this.value,
  72. host: process.env.VUE_APP_BASE_API,
  73. // reqData: {
  74. // },
  75. // dir:"fuwenben",
  76. editorOption: {
  77. placeholder: "",
  78. theme: "snow", // or 'bubble'
  79. placeholder: "请输入内容",
  80. modules: {
  81. toolbar: {
  82. container: toolbarOptions,
  83. handlers: {
  84. image: (value) => {
  85. if (value) {
  86. // 触发input框选择图片文件
  87. const index = this.quillIndex;
  88. document.querySelectorAll(".quill-img input")[index].click();
  89. } else {
  90. this.quill.format("image", false);
  91. }
  92. }
  93. }
  94. }
  95. }
  96. },
  97. uploadImgUrl: process.env.VUE_APP_BASE_API + "/common/upload", // 上传的图片服务器地址
  98. headers: {
  99. Authorization: 'Bearer ' + getToken()
  100. }
  101. };
  102. },
  103. watch: {
  104. value: function() {
  105. this.content = this.value;
  106. }
  107. },
  108. created() {
  109. console.log(this.toref,"## toref")
  110. },
  111. methods: {
  112. onEditorBlur() {
  113. //失去焦点事件
  114. },
  115. onEditorFocus() {
  116. //获得焦点事件
  117. },
  118. onEditorChange() {
  119. //内容改变事件
  120. this.$emit("input", this.content);
  121. },
  122. randomString(len){
  123. //默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
  124. var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
  125. var tempLen = chars.length, tempStr='';
  126. for(var i=0; i<len; ++i){
  127. tempStr += chars.charAt(Math.floor(Math.random() * tempLen ));
  128. }
  129. return tempStr;
  130. },
  131. // 富文本图片上传前
  132. async quillImgBefore(file) {
  133. let fileType = file.type;
  134. if(fileType === 'image/jpeg' || fileType === 'image/png' || fileType === 'image/jpg'){
  135. return true;
  136. }else {
  137. this.$message.error('请插入图片类型文件(jpg/jpeg/png)');
  138. return false;
  139. }
  140. },
  141. quillImgSuccess(res, file) {
  142. // res为图片服务器返回的数据
  143. // 获取富文本组件实例
  144. // let quill = this.$refs.quillEditor.quill;
  145. // 获取富文本组件实例
  146. console.log("this:",this.$refs);
  147. console.log("toref:",this.toref);
  148. let toeval = "this.$refs."+this.toref+".quill"
  149. console.log("toeval",toeval);
  150. let quill = eval(toeval);
  151. console.log("quill",quill);
  152. // 如果上传成功
  153. // 获取光标所在位置
  154. // let length = quill.getSelection().index;
  155. let length = quill.selection.savedRange.index;
  156. // 插入图片
  157. quill.insertEmbed(length, "image",this.host + res.key);
  158. // 调整光标到最后
  159. quill.setSelection(length + 1);
  160. },
  161. // 富文本图片上传失败
  162. uploadError() {
  163. // loading动画消失
  164. this.$message.error("图片插入失败");
  165. }
  166. }
  167. };
  168. </script>
  169. <style>
  170. .ql-editor {
  171. line-height: normal !important;
  172. min-height: 300px;
  173. }
  174. .quill-img {
  175. display: none;
  176. }
  177. .ql-snow .ql-tooltip[data-mode="link"]::before {
  178. content: "请输入链接地址:";
  179. }
  180. .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  181. border-right: 0px;
  182. content: "保存";
  183. padding-right: 0px;
  184. }
  185. .ql-snow .ql-tooltip[data-mode="video"]::before {
  186. content: "请输入视频地址:";
  187. }
  188. .ql-snow .ql-picker.ql-size .ql-picker-label::before,
  189. .ql-snow .ql-picker.ql-size .ql-picker-item::before {
  190. content: "14px";
  191. }
  192. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
  193. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
  194. content: "10px";
  195. }
  196. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
  197. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
  198. content: "18px";
  199. }
  200. .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
  201. .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
  202. content: "32px";
  203. }
  204. .ql-snow .ql-picker.ql-header .ql-picker-label::before,
  205. .ql-snow .ql-picker.ql-header .ql-picker-item::before {
  206. content: "文本";
  207. }
  208. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
  209. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
  210. content: "标题1";
  211. }
  212. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
  213. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
  214. content: "标题2";
  215. }
  216. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
  217. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
  218. content: "标题3";
  219. }
  220. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
  221. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
  222. content: "标题4";
  223. }
  224. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
  225. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
  226. content: "标题5";
  227. }
  228. .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
  229. .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  230. content: "标题6";
  231. }
  232. .ql-snow .ql-picker.ql-font .ql-picker-label::before,
  233. .ql-snow .ql-picker.ql-font .ql-picker-item::before {
  234. content: "标准字体";
  235. }
  236. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
  237. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
  238. content: "衬线字体";
  239. }
  240. .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
  241. .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
  242. content: "等宽字体";
  243. }
  244. </style>

如何引用

  1. <el-form-item label="课程介绍" prop="introduce">
  2. <editor toref="torefIntroduce" :quillIndex="0" v-model="form.introduce" :min-height="192"/>
  3. </el-form-item>
  4. <el-form-item label="试看内容" >
  5. <editor toref="torefTrySeeContent" :quillIndex="1" v-model="form.trySeeContent" :min-height="192"/>
  6. </el-form-item>
  7. <el-form-item label="内容" >
  8. <editor toref="torefContent" :quillIndex="2" v-model="form.content" :min-height="192"/>
  9. </el-form-item>

这是在页面引用组件时的代码,js 代码忽略了。
注意一下 quillIndex 的顺序别搞错了,根据索引找到富文本组件

最关键的是这几个地方:
一、在组件里
1. 富文本组件里的 ref 属性:

  1. :ref="toref"

这里的 toref 是页面传过来的值。

  1. props 中增加:
    1. toref: {
    2. type: String,
    3. default:"quillEditor"
    4. },

参考

https://blog.csdn.net/yx111115/article/details/109132660