当前位置:   article > 正文

Vue中实现富文本功能,适配小程序端,uniapp也可实现,vue-quill-editor适配小程序,将vue-quill-editor插件中的class样式换为style内联样式达到适配目的_vue富文本编辑器 能在小程序上展示吗

vue富文本编辑器 能在小程序上展示吗

1、Vue中实现富文本功能

1.1 拓展功能,上传图片从系统图片素材库中拉取图片,黏贴图片时,图片也存入图片素材库中

2、将自带的class样式换为style内联样式

3、适配小程序端(仅展示,不需要编辑)

4、小程序端新增编辑富文本

1、Vue中实现富文本功能

  1. 使用插件 vue-quill-editor ,先下载插件
npm insatll --save vue-quill-editor
  • 1
  1. 在main.js 中引入
import VueQuillEditor from 'vue-quill-editor' //富文本
import * as Quill from 'quill'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 封装富文本组件 textEditor
    uploadPictures 为图片素材库 ,引入自己的素材库,
    vxe-model 是vxe 插件的弹框组件
    diyConfig 为组件传参,默认file,调用本地电脑上的图片,libImgStore为使用素材库中的图片
    imgData 为调用图片素材库时必要的传参
    getContent 为编辑时富文本Html传参

函数
editorFn 回调函数 返回值进行下一步处理

// 组件 textEditor.vue
<template>
  <div>
    <quill-editor class="editor" ref="myTextEditor" v-model="content" :options="editorOption" @blur="onEditorBlur($event)"
      @focus="onEditorFocus($event)" @ready="onEditorReady($event)" @change="(e)=>onEditorChange(e)" >
    </quill-editor>
    <vxe-modal  v-model="modalPic" width="1200" height="720" min-width="860"  id="imgUpdata" min-height="420" resize remember storage transfer   :esc-closable="true">
      <template v-slot:title>
        <span >素材库</span>
      </template>
      <uploadPictures :isChoice="imageData.isChoice" @getPic="getPic" @getPicD="getPicD" v-if="modalPic" :storeId.sync="imageData.storeId"></uploadPictures>
    </vxe-modal>
  </div>
</template>
<script>
// 图片素材库引入 uploadPictures 
import uploadPictures from '@/components/uploadPictures';
import axios from "axios";
  export default {
    props: {
      getContent:{
        type: String,
        default:""
      },
      diyConfig:{//自定义配置
        type: Object,
        default:()=>{
          return {
            image:'file',//file 上传图片从电脑文件中选择,libImgStore 微商城素材库
          }
        }
      },
      imageData:{
        type: Object,
        default:()=>{
          return {}// storeId 店号,isChoice:多选单选
        }
      }
    },
    components: {
      uploadPictures,
    },
    mounted() {
      this.$nextTick(() => {
        const quill = this.$refs.myTextEditor.quill
        quill.root.addEventListener('paste', this.handlePaste)
        //获取图片库中的分类
        if(this.diyConfig.image == 'libImgStore'){
          this.getClassList()
        }
      })
    },
    data() {
      return {
        content: null,
        editorOption: {
          modules: {
            toolbar:{
              container:[
                ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
                ["blockquote", "code-block"], // 引用  代码块
                [{
                  header: 1
                }, {
                  header: 2
                }], // 1、2 级标题
                [{
                  list: "ordered"
                }, {
                  list: "bullet"
                }], // 有序、无序列表
                [{
                  script: "sub"
                }, {
                  script: "super"
                }], // 上标/下标
                [{
                  indent: "-1"
                }, {
                  indent: "+1"
                }], // 缩进
                // [{'direction': 'rtl'}],                         // 文本方向
                [{
                  // size: ["small", false, "large", "huge"]
                  size: ['14px', '16px', '18px', '20px', '24px', '28px', '32px']
                }], // 字体大小
                [{
                  header: [1, 2, 3, 4, 5, 6, false]
                }], // 标题
                [{
                  color: []
                }, {
                  background: []
                }], // 字体颜色、字体背景颜色
                [{
                  font: ['SimSun',
                    'SimHei',
                    'Microsoft-YaHei',
                    'KaiTi',
                    'FangSong']
                }], // 字体种类
                [{
                  align: []
                }], // 对齐方式
                ["clean"], // 清除文本格式
                ["link", "image", "video"] // 链接、图片、视频
              ],//工具菜单栏配置
              handlers: {
                'image': (e)=>this.imageHandle(e,this.quill)
              }
            } 
          },
          placeholder: '添加内容...', //提示
          readyOnly: false, //是否只读
          theme: 'snow', //主题 snow/bubble
          syntax: true, //语法检测
        },
        modalPic: false,
        isHandlingImage: false,
      }
    },
    directives: {
      quillImageAdded: {
        bind(el, binding) {
          el.addEventListener('paste', (event) => {
            console.log(event,"paste")
            if (event.target.tagName === 'IMG') {
              binding.value(event.target.src);
            }
          });
        }
      }
    },
    methods: {
      // 失去焦点
      onEditorBlur() {
        // console.log(editor)
      },
      // 获得焦点
      onEditorFocus() {
        // console.log(editor)
      },
      // 开始
      onEditorReady() {
        // console.log(editor)
      },
      // 值发生变化
      onEditorChange(editor) {
        this.content = editor.html;
        this.$emit('update:editor', this.content)
        this.$emit('editorFn', this.content)
      },
      clearContent() {
        this.content = ''
      },
      getClassList(){
        this.$http.GET('/MA/mallPsdClassify/psdClassList').then(data => {
        this.classList = data.data
        this.formDatas[1].options = data.data.map(item => {
          item.value = item.classifyId;
          item.name = item.classifyId + '--' + item.classifyName;
          return item;
        });
      })
      },
      imageHandle(e,quill){
        if(this.diyConfig && this.diyConfig.image == 'libImgStore'){
          if (e) {
            alert('点击了上传图片')
          } else {
            quill.format('image', false);
          }
        }else{
          quill.format('image', '');
        }
      },
      // 获取图片信息
      getPic(pc) {
        let quill = this.$refs.myTextEditor.quill;
        const range = quill.getSelection(true);
        console.log(pc,"pc",range)
        if (!range) return;
        quill.insertEmbed(range.index, "image", pc.picUrl);
        this.modalPic = false;
      },
      getPicD(pc) {
        let quill = this.$refs.myTextEditor.quill;
        const range = quill.getSelection(true);
        if (!range) return;
          // 将图片插入到富文本编辑器中
        pc.reverse()
        pc.forEach(element => {
          quill.insertEmbed(range.index, "image", element.picUrl);
        });
        this.modalPic = false;
      },
      handlePaste(event) {
        const clipboardData = event.clipboardData || window.clipboardData
        if (!clipboardData) return

        const items = clipboardData.items
        if (!items) return

        for (let i = 0; i < items.length; i++) {
          const item = items[i]
          if (item.type.indexOf('image') !== -1) {
            const file = item.getAsFile()
            // 处理粘贴的图片文件
            this.handleImageUpload(file)
          }
        }
      },
      async handleImageUpload(file) {
        this.isHandlingImage = false;
        if(!this.isHandlingImage && this.diyConfig.image && this.diyConfig.image == 'libImgStore'){
          this.isHandlingImage = true;
          console.log(this.modalPic,"image.png",this.isHandlingImage)
          if(this.modalPic){
            this.isHandlingImage = false;
            return 
          }
          const formData = new FormData()
          formData.append('file', file); // 有的后台需要传文件名,不然会报错
          formData.append("type", 'image');
          formData.append("storeId", this.imageData.storeId);
          // 发送图片到服务器上传
          axios ({
            method: "post",
            url: "/manager/crm-wx/wechat/media/uploadMedia",
            data: formData,
            headers: {
              "Content-Type": "multipart/form-data"
            }
          }).then(res=>{
            if (res.data.code==200 && res.data.data.url!=null) {
                // 上传成功将照片传回父组件
                const imageUrl =  res.data.data.url;
                let obj = {
                  classifyId:this.classList[0].classifyId,
                  classifyName: this.classList[0].classifyName,
                  media_id: res.data.data.media_id,
                  picUrl: imageUrl,
                  picName: file.name,
                }
                //图片上传成功后添加到图片素材库分类中
                this.$http.POSTJSON("/MA/mallPsdClassifyRef/merge", [obj])
                .then(data => {
                  if (data.code == 200) {
                    this.delDiyImg()
                    // // 上传成功后,在富文本框中插入图片
                    const quill = this.$refs.myTextEditor.quill
                    const range = quill.getSelection(true)
                    quill.insertEmbed(range.index, 'image', imageUrl)
                    quill.setSelection(range.index + 1)
                    // this.onEditorChange({html:this.content})
                    // 在富文本框中展示图片
                    // 如果你不想展示上传失败的图片,则可以省略这一步
                    // quill.format('image', imageUrl)
                  }else{
                    this.$message.warning("图片上传失败1!")
                    this.delDiyImg()
                  }
                });
            }else{
              this.$message.warning("图片上传失败2!")
              this.delDiyImg()
            }
            this.isHandlingImage = false;
          }).catch(err=>{
            console.log(err,"err")
            this.$message.warning("图片上传失败3!")
            this.delDiyImg()
            this.isHandlingImage = false;
          })
        }
      },
      delDiyImg(){
        const quill = this.$refs.myTextEditor.quill
        const index = quill.getSelection().index
        quill.deleteText(index-1, 1)
      }
    },
    computed: {
      editor() {
        return this.$refs.myTextEditor.quillEditor;
      }
    },
    watch: {
      getContent: {
        handler(newVal) {
          console.log(newVal,"newVal")
          this.content = newVal;
        },
        immediate: true
      },
      diyConfig: {
        handler(newVal) {
          if(newVal && newVal.image == 'libImgStore'){
            let that = this
            this.editorOption.modules.toolbar.handlers.image = function(val){
              if (val) {
                that.modalPic = true;
              } else {
                this.quill.format('image', false);
              }
            }
          }else{
            delete this.editorOption.modules.toolbar.handlers.image
          }
        },
        immediate: true
      }
    }
  }
</script>

<style lang="scss">
.ql-editor u ,.ql-snow u{
  text-decoration: underline;
}
.ql-editor s,del , .ql-snow s,del{
  text-decoration: line-through;
}
.ql-editor i,em , .ql-snow i,em{
  font-style: italic;
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimHei]::before {
  content: "黑体";
  font-family: "SimHei";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Microsoft-YaHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Microsoft-YaHei]::before {
  content: "微软雅黑";
  font-family: "Microsoft YaHei";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=KaiTi]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=KaiTi]::before {
  content: "楷体";
  font-family: "KaiTi";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=FangSong]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=FangSong]::before {
  content: "仿宋";
  font-family: "FangSong";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Arial]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Arial]::before {
  content: "Arial";
  font-family: "Arial";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Times-New-Roman]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Times-New-Roman]::before {
  content: "Times New Roman";
  font-family: "Times New Roman";
}

.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=sans-serif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=sans-serif]::before {
  content: "sans-serif";
  font-family: "sans-serif";
}

.ql-font-SimSun {
  font-family: "SimSun";
}

.ql-font-SimHei {
  font-family: "SimHei";
}

.ql-font-Microsoft-YaHei {
  font-family: "Microsoft YaHei";
}

.ql-font-KaiTi {
  font-family: "KaiTi";
}

.ql-font-FangSong {
  font-family: "FangSong";
}

.ql-font-Arial {
  font-family: "Arial";
}

.ql-font-Times-New-Roman {
  font-family: "Times New Roman";
}

.ql-font-sans-serif {
  font-family: "sans-serif";
}

/* 字号设置 */
/* 默认字号 */
.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="14px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before {
  content: "14px";
  font-size: 14px;
}

.ql-size-14px {
  font-size: 14px;
}

.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before {
  content: "16px";
  font-size: 16px;
}

.ql-size-16px {
  font-size: 16px;
}

.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before {
  content: "18px";
  font-size: 18px;
}

.ql-size-18px {
  font-size: 18px;
}

.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {
  content: "20px";
  font-size: 20px;
}

.ql-size-20px {
  font-size: 20px;
}

.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="24px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="24px"]::before {
  content: "24px";
  font-size: 24px;
}

.ql-size-24px {
  font-size: 24px;
}

.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="26px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="26px"]::before {
  content: "26px";
  font-size: 26px;
}

.ql-size-26px {
  font-size: 26px;
}

.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="28px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="28px"]::before {
  content: "28px";
  font-size: 28px;
}

.ql-size-28px {
  font-size: 28px;
}

.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="32px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="32px"]::before {
  content: "32px";
  font-size: 32px;
}

.ql-size-32px {
  font-size: 32px;
}


  .editor {
    line-height: normal !important;
  }

  .ql-snow .ql-tooltip[data-mode=link]::before {
    content: "请输入链接地址:" !important;
  }

  .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
    border-right: 0px;
    content: '保存' !important;
    padding-right: 0px;
  }

  .ql-snow .ql-tooltip[data-mode=video]::before {
    content: "请输入视频地址:" !important;
  }
  .ql-snow .ql-picker.ql-header .ql-picker-label::before,
  .ql-snow .ql-picker.ql-header .ql-picker-item::before {
    content: '文本' !important;
  }

  .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' !important;
  }

  .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' !important;
  }

  .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' !important;
  }

  .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' !important;
  }

  .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' !important;
  }

  .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' !important;
  }
  .quill-editor .ql-editor .ql-container img{
    max-width: 400px;
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
  • 538
  • 539
  • 540
  • 541
  • 542
  • 543
  • 544

在页面中使用

<textEditor ref="editor" :editor.sync="content" :getContent="getContent"
 :diyConfig="{image:'libImgStore'}" :imageData="{storeId: form.storeId,isChoice:'多选'}"
  style="width:340px;"  @editorFn="editorFn"></textEditor>
  • 1
  • 2
  • 3

此时富文本已经可以使用了

2、 将自带的class样式换为style内联样式

可以参考文档 https://www.kancloud.cn/liuwave/quill/1409946#_61

将main.js 中引入的文件换为 下面文件

import VueQuillEditor from 'vue-quill-editor' //富文本
import * as Quill from 'quill'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
// 设置字体大小
const fontSizeStyle = Quill.import('attributors/style/size') // 引入这个后会把样式写在style上
fontSizeStyle.whitelist = [ '14px', '16px', '18px', '20px', '24px', '28px', '32px']
Quill.register(fontSizeStyle, true)
const Font = Quill.import('attributors/style/font') // 引入这个后会把样式写在style上
const fonts = [
  'SimSun',
  'SimHei',
  'Microsoft-YaHei',
  'KaiTi',
  'FangSong'
]
Font.whitelist = fonts // 将字体加入到白名单
Quill.register(Font, true)
var Align = Quill.import('attributors/style/align')
Quill.register(Align, true)

var Direction = Quill.import('attributors/style/direction')

Quill.register(Direction, true)
Vue.use(VueQuillEditor);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在这里插入图片描述
中间这个部分 会在写入样式时,写入style样式,达到目的
其中的 whitelist 、fonts、这两个数组赋值,是将富文本自带的数组换为自定义的数组了

查看原本数据 可以 console.log(Quill.imports) 单独打印出来看

3、 小程序中展示(仅展示,不编辑)

使用uview 中自带的富文本功能,Parse只需把富文本传值传入即可展示,对应的样式查看官方文档
https://v1.uviewui.com/components/parse.html
基本使用,长按复制,样式设置 等功能都能满足 (注意有1.0版本和2.0 版本)根据下载的插件看不同版本的文档

4、小程序中新增、编辑管理端中存入的富文本

1、下载插件

https://ext.dcloud.net.cn/plugin?id=12013 富文本插件
下载到自己项目中

2、把不需要的功能屏蔽,或者如果有满足不了的可以自己开发。

在这里插入图片描述

源码中 上传图片代码有两处,根据平台不同,找到对应代码,图片我默认给宽度100%了,所以我控制了不让调整大小,更剧自己需求具体适配
在微信小程序中适配是 // #ifdef MP 从这以下的代码
在这里插入图片描述

在这里插入图片描述
添加一个参数用来控制富文本的样式 ,我主要是用来更改他的高度
在这里插入图片描述

下载插件后的代码 piaoyi-editor 更改完后的代码,可直接复制替换

<template>
	<view class="container-editor">
		<view class="textarea">
			<view class="page-body">
				<view class='wrapper ql-editor'>
					<PickerColor ref="colorPicker" :color="{r: 255,g: 0,b: 0,a: 0.6}" @confirm="confirm"></PickerColor>
					<view class='toolbar' @tap="format">
						<view :class="formats.fontSize === '24px' ? 'ql-active' : ''" class="iconfont icon-font-size"
							data-name="fontSize" data-value="24px"></view>
						<view :class="formats.color? 'ql-active' : ''" class="iconfont icon-zitiyanse" data-name="color" :data-value="formats.color">
						</view>
						<view :class="formats.header === 1 ? 'ql-active' : ''" class="iconfont icon-formatheader1"
							data-name="header" :data-value="1"></view>
						<view :class="formats.header === 2 ? 'ql-active' : ''" class="iconfont icon-formatheader2"
							data-name="header" :data-value="2"></view>
						<view :class="formats.bold ? 'ql-active' : ''" class="iconfont icon-zitijiacu" data-name="bold">
						</view>
						<view :class="formats.italic ? 'ql-active' : ''" class="iconfont icon-zitixieti"
							data-name="italic"></view>
						<view :class="formats.underline ? 'ql-active' : ''" class="iconfont icon-zitixiahuaxian"
							data-name="underline"></view>
						<view :class="formats.strike ? 'ql-active' : ''" class="iconfont icon-shanchuxian"
							data-name="strike"></view>
						<view :class="formats.align === 'left' ? 'ql-active' : ''" class="iconfont icon-zuoduiqi"
							data-name="align" data-value="left"></view>
						<view :class="formats.align === 'center' ? 'ql-active' : ''" class="iconfont icon-juzhongduiqi"
							data-name="align" data-value="center"></view>
						<view :class="formats.align === 'right' ? 'ql-active' : ''" class="iconfont icon-youduiqi"
							data-name="align" data-value="right"></view>
						<view :class="formats.align === 'justify' ? 'ql-active' : ''" class="iconfont icon-zuoyouduiqi"
							data-name="align" data-value="justify"></view>
						<!-- <view :class="formats.lineHeight ? 'ql-active' : ''" class="iconfont icon-LineHeight"
							data-name="lineHeight" data-value="2"></view>
						<view :class="formats.letterSpacing ? 'ql-active' : ''" class="iconfont icon-Character-Spacing"
							data-name="letterSpacing" data-value="2em">
						</view> -->
						<!-- <view :class="formats.marginTop ? 'ql-active' : ''" class="iconfont icon-duanqianju"
							data-name="marginTop" data-value="10px"></view>
						<view :class="formats.previewarginBottom ? 'ql-active' : ''" class="iconfont icon-duanhouju"
							data-name="marginBottom" data-value="10px"></view> -->
						<!-- <view class="iconfont icon-rili4" @tap="insertDate"></view> -->
						<!-- <view class="iconfont icon-checklist" data-name="list" data-value="check"></view> -->
						<view :class="formats.list === 'ordered' ? 'ql-active' : ''" class="iconfont icon-youxupailie"
							data-name="list" data-value="ordered"></view>
						<view :class="formats.list === 'bullet' ? 'ql-active' : ''" class="iconfont icon-wuxupailie"
							data-name="list" data-value="bullet"></view>
					<!-- 	<view class="iconfont icon-outdent" data-name="indent" data-value="-1"></view>
						<view class="iconfont icon-indent" data-name="indent" data-value="+1"></view> -->
						<!-- <view class="iconfont icon-fengexian" @tap="insertDivider"></view> -->
						<view class="iconfont icon-charutupian" @tap="insertImage"></view>
						<view class="iconfont icon-undo" @tap="undo"></view>
						<view class="iconfont icon-redo" @tap="redo"></view>
						<view class="iconfont icon-format" @tap="clear"></view>
					</view>
					<!-- showImgResize -->
					<editor id="editor" class="editor" placeholder="开始输入..." showImgSize showImgToolbar 
						@statuschange="onStatusChange" :read-only="readOnly" @ready="onEditorReady" :style="[editStyle]"
						@input="saveContens">
					</editor>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	import PickerColor from "./color-picker.vue"
	export default {
		components: {
			PickerColor
		},
		props: {
			api: {
				type: String,
				default: ''
			},
			photoUrl: {
				type: String,
				default: ''
			},
			values: {
				type: String,
				default: ''
			},
			readOnly: {
				type: Boolean,
				default: false
			},
			maxlength: {
				type: Number,
				default: 300
			},
			name: {
				type: String,
				default: 'file'
			},
			editStyle: {
				type: Object,
				default: ()=>{}
			},
		},
		data() {
			return {
				currentTab: 0,
				curColor: '#000000',
				show: true,
				hdid: "",
				myHtml: "",
				formats: {}
			}
		},
		methods: {
			showPicker() {
				this.$refs.colorPicker.open()
			},
			confirm(e) {
				this.editorCtx.format('color', e.hex)
			},
			saveContens() {
				let that = this;
				let maxlength = parseInt(that.maxlength);
				that.editorCtx.getContents({
					success: function(res) {
						let html_text = res.html;
						let html_length = html_text.length;
						if (html_length > maxlength) {
							uni.showModal({
								title: '最多只能输入' + maxlength + '字',
								confirmText: '确定',
								showCancel: false,
								success(res) {
									that.$emit("changes", {
										html: res.html,
										length: html_length
									});
								}
							});
						} else {
							that.$emit("changes", {
								html: res.html=='<p><br></p>'?'':res.html,
								length: html_length
							});
						}
					},
				})
			},
			update() {
				//获取一下是否有数据
				let that = this;
				setTimeout(() => {
					that.editorCtx.setContents({
						"html": that.values
					});
				}, 1000)
			},
			onEditorReady() {
				let that = this;
				uni.createSelectorQuery().in(this).select('#editor').context((res) => {
					that.editorCtx = res.context;
					that.update();
				}).exec((result) => {});
			},
			undo() {
				this.editorCtx.undo()
			},
			redo() {
				this.editorCtx.redo()
			},
			format(e) {
				let {
					name,
					value
				} = e.target.dataset
				if (!name) return
				if (name == 'color') {
					this.showPicker()
				} else {
					this.editorCtx.format(name, value)
				}
			},
			onStatusChange(e) {
				const formats = e.detail
				this.formats = formats
			},
			insertDivider() {
				this.editorCtx.insertDivider();
			},
			clear() {
				let that = this
				this.editorCtx.clear({
					success: res=>{
						this.$emit("changes",{html:""})
					}
				});
			},
			insertDate() {
				const date = new Date()
				const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
				this.editorCtx.insertText({
					text: formatDate
				})
			},
			insertImage() {
				let that = this;
				// #ifdef APP-PLUS || H5
				uni.chooseImage({
					count: 1, //默认9
					sizeType: ['compressed'], //可以指定是原图还是压缩图,默认二者都有
					sourceType: ['album'], //从相册或者相机选择
					success: (res) => {
						const tempFilePaths = res.tempFilePaths[0]
						if (!this.api || !this.photoUrl) {
							that.editorCtx.insertImage({
								src: tempFilePaths,
								alt: '图像',
								success: function() {}
							})
							uni.showToast({
								title: '未传入api字段或者photoUrl字段,此为临时图片路径',
								duration: 3000,
								icon: 'none'
							})
						} else {
							uni.uploadFile({
								url: this.photoUrl + this.api,
								filePath: tempFilePaths,
								name: this.name,
								formData: {},
								success: (uploadFileRes) => {
									var obj = JSON.parse(uploadFileRes.data)
									if (obj.code == 200) {
										this.img = this.photoUrl + '/' + obj.data
										wx.showToast({
											title: obj.msg,
											icon: 'none'
										})
										this.editorCtx.insertImage({
											src: this.img,
											alt: '图像',
											width: "100%",
											success: function() {
												const imageElement = document.querySelector('.custom-image-class');
												console.log(imageElement,"imageElement")
												if (imageElement) {
													imageElement.style.width = '100%'; // 设置宽度为20px
												}
											}
										})
									} else {
										wx.showToast({
											title: obj.msg,
											icon: 'none'
										})
									}
								}
							});
						}
					},
					fail() {
						uni.showToast({
							title: '未授权访问相册权限,请授权后使用',
							icon: 'none'
						})
					}
				});
				// #endif
				// #ifdef MP
				uni.chooseMedia({
					count: 1,
					mediaType: ['image'],
					sourceType: ['album'],
					sizeType: 'compressed',
					success: (chooseImageRes) => {
						const tempFilePaths = chooseImageRes.tempFiles[0].tempFilePath;
						console.log(tempFilePaths)
						if (!this.api || !this.photoUrl) {
							that.editorCtx.insertImage({
								src: tempFilePaths,
								alt: '图像',
								success: function() {}
							})
							uni.showToast({
								title: '未传入api字段或者photoUrl字段,此为临时图片路径',
								duration: 3000,
								icon: 'none'
							})
						} else {
							uni.uploadFile({
								url: this.photoUrl + this.api,
								filePath: tempFilePaths,
								name: this.name,
								formData: {},
								success: (uploadFileRes) => {
									var obj = JSON.parse(uploadFileRes.data)
									console.log(obj,"uploadFileRes")
									if (obj.code == 200) {
										// this.img = path + '/' + obj.data
										this.img =  obj.data.url
										wx.showToast({
											title: obj.msg,
											icon: 'none'
										})
										this.editorCtx.insertImage({
											src: this.img,
											alt: '图像',
											width: "100%",
											success: function() {}
										})
									} else {
										wx.showToast({
											title: obj.msg,
											icon: 'none'
										})
									}
								},
								fail(err) {
									console.log(err)
									uni.showToast({
										title: err.errMsg,
										icon: 'none'
									})
								}
							});
						}
					}
				})
				// #endif
			}
		}
	}
</script>

<style>
	@import url('iconfont.css');

	.tabs {
		display: flex;
		justify-content: space-around;
		background-color: #FFFFFF;
	}

	.tabs .current {
		border-bottom: 2px solid #0369D6;
	}

	.tabs .tab {
		font-size: 32upx;
	}

	.main {
		padding: 20upx;
		background-color: #FFFFFF;
	}

	.main .item {
		display: flex;
		justify-content: space-between;
		line-height: 80upx;
		border-bottom: 1px solid #F2F2F2;
	}

	.main .item .left {
		min-width: 200upx;
	}

	.main .title {
		padding-bottom: 20upx;
		font-weight: bold;
		border-bottom: 1px solid #F2F2F2;
	}

	.main .textarea {
		border: 1px solid #F2F2F2;
	}

	.wrapper {
		padding: 0 10upx;
	}

	.iconfont {
		display: inline-block;
		width: 9%;
		cursor: pointer;
		font-size: 40upx !important;
		text-align: center;
		padding: 10upx 0;
	}

	.icon-rili4 {
		font-size: 48upx !important;
	}

	.icon-duanqianju,
	.icon-duanhouju,
	.icon-zitijiacu,
	.icon-zitixieti,
	.icon-zitixiahuaxian,
	.icon-shanchuxian {
		font-size: 36upx !important;
	}

	.toolbar {
		box-sizing: border-box;
		border-bottom: 0;
		margin-bottom: 10upx;
		
	}

	.ql-container {
		box-sizing: border-box;
		width: 100%;
		min-height: 600upx;
		height: auto;
		background: #fff;
		font-size: 32upx;
		line-height: 1;
		padding-bottom: 60upx;
	}

	/deep/ .ql-editor.ql-blank:before {
		font-size: 28upx;
		font-style: inherit;
	}

	.ql-active {
		color: #ff0000;
	}

	button {
		width: 150upx;
		font-size: 30upx;
	}

	.editor {
		color: #333;
	}
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438

插件已经修改为成后,就可以在页面中使用了

3、在页面中使用

注意下面部分需要换成自己的接口
在这里插入图片描述
完整代码 如下

<template>
	<view class="richtext">
		<piaoyiEditor :values="desc" :maxlength="3000" @changes="saveContens" :readOnly="readOnly" :photoUrl="photoUrl"
			:api="api" :name="name" :editStyle="editStyle"/>
		<view class="q-flex-center q-position-fixed-Bottom q-borderBox u-p-b-20 u-p-t-20 q-shadow  bottomBar">
			<view class="buttonSTY" @click="submit">
				确认
			</view>
		</view>
	</view>
</template>
<script>
	import piaoyiEditor from '@/uni_modules/piaoyi-editor/components/piaoyi-editor/piaoyi-editor.vue';
	export default {
		data() {
			return {
				readOnly: false, //是否只读
				photoUrl: '', //服务器图片域名或者ip
				api: '/wechat/media/uploadMedia', //上传图片接口地址
				txt: '',
				name: 'file',
				desc: '<div>11111222</div>',
				editStyle: {
					height: "calc(100vh - 130px)",
				}
			};
		},
		components: {
			piaoyiEditor
		},
		onLoad(option){
			this.desc = decodeURIComponent(option.desc);
			this.txt = this.desc;
		},
		onShow() {
			this.photoUrl = this.$common.api_url+this.$common.fileserver;
			this.api = `/wechat/media/uploadMedia?type=image&storeId=${uni.getStorageSync('shopNo')}`;
		}, 
		methods: {
			saveContens(e) {
				console.log(e,"e")
				this.txt = e.html
			},
			submit(){
				let that = this;
				let pages = getCurrentPages()
				let prevPage = pages[pages.length - 2];
				uni.navigateBack({
					delta: 1,
					success() {
						prevPage.$vm.richTextSubmit(encodeURIComponent(that.txt));
					},
				})
			},
		},
	}
</script>

<style scoped>
	.bottomBar{
		background: #fff;
	}
	.buttonSTY{
		width: 600rpx;
		text-align: center;
		background: #FC6B3A;
		height: 80rpx;
		font-size: 30rpx;
		line-height: 80rpx;
		color: #f3f3f3;
		border-radius: 40rpx;
	}
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/91131?site
推荐阅读
相关标签
  

闽ICP备14008679号