wangEditor是一个轻量级 web 富文本编辑器,配置方便,使用简单。

        官方文档:Introduction · wangEditor 用户文档

        本文简单介绍vue如何使用wangEditor,进行这些操作(编辑富文本内容/显示内容/上传图片/删除图片)。后台数据库中保存的富文本就是html,顺便把图片的地址一并保存在里面。

1. npm安装wangEditor:

npm install wangEditor

2.单个Vue页面使用

<template>
    <div>
        <!--编辑区域-->
        <div ref="editor" v-html="textEditor"></div>

        <!--显示区域,一定要用样式-->
        <pre class="sd_pre_change_note" style="overflow-y: scroll;" v-html="textDescription"></pre>
    </div>
</template>
<script>
import wangEditor from 'wangeditor'   // 引入 wangEditor

export default {
    name: 'JustTest',
    data (){
        textEditor:'',
        textDescription:'',
        editor:null,                //wangEditor编辑器
        imgsrc: [],
    },
    created () {
        this.createEditor()
    },
    destroyed() {
      this.editor.destroy()
      this.editor = null
    },
    methods: {
      //创建富文本编辑器
      createEditor(){
        let that = this
        const editor = new wangEditor(this.$refs.editor)        //创建编辑器
        editor.config.height = 370                         // 设置编辑区域高度为 500px
        editor.config.excludeMenus = [                   // 配置菜单栏,设置不需要的菜单
            'todo',
            'video'
        ],
        editor.config.showLinkImg = false            //隐藏插入网络图片的功能
        
        //文本内容发生变换
        editor.config.onchange = (newHtml) => {
          this.onContentchange(newHtml)

          this.textDescription = newHtml
        }
        
        //上传文件
        editor.config.uploadImgServer = 'http://127.0.0.1:8000/backend/upload'
        editor.config.uploadImgMaxSize = 2 * 1024 * 1024 // 2M
        editor.config.uploadImgAccept = ['jpg', 'jpeg', 'png']
        editor.config.uploadImgMaxLength = 3 // 一次最多上传 3 个图片
        editor.config.uploadImgParams = {
            updir: 'detail',
        }
        editor.config.uploadFileName = 'image'
     
        editor.config.uploadImgHooks = {
          before: function(xhr) {          // 上传图片之前
              // 可阻止图片上传
              // return {
              //     prevent: true,
              //     msg: '需要提示给用户的错误信息'
              // }
          },
          success: function(xhr) {            // 图片上传并返回了结果,图片插入已成功
              console.log('success', xhr)
          },
          fail: function(xhr, editor, resData) {            // 图片上传并返回了结果,但图片插入时出错了
              this.$message({type: 'error', message: '插入图片失败!'})
              console.log('fail', resData)
          },
          error: function(xhr, editor, resData) {          // 上传图片出错,一般为 http 请求的错误
            this.$message({type: 'error', message: '上传图片失败!'})
            console.log('error', xhr, resData)
          },
          timeout: function(xhr) {          // 上传图片超时
            this.$message({type: 'error', message: '上传图片超时!'})
            console.log('timeout')
          },
          // 图片上传并返回了结果,想要自己把图片插入到编辑器中
          // 例如服务器端返回的不是 { errno: 0, data: [...] } 这种格式,可使用 customInsert
          customInsert: function(insertImgFn, result) {
              // result 即服务端返回的接口
              // insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
              insertImgFn(result.data[0])
              that.imgsrc = that.getSrc(editor.txt.html())    //获取富文本中包含的所有图片地址
          }
        }
        
        editor.create()
        this.editor = editor
      },
      // html 即变化之后的内容, 删除图片
      onContentchange(html){
        if (this.imgsrc.length !== 0) {
          let nowimgs = this.getSrc(html)
          let merge = this.imgsrc.concat(nowimgs).filter(function (v, i, arr) {
            return arr.indexOf(v) === arr.lastIndexOf(v)
          })

          //后台请求,删除图片
          let params = {imgPaths:JSON.stringify(merge)}
          this.axios.post(`/auditInfo/deleteImg`,qs.stringify(params))
          .then(response =>{
            console.log(response.data);
          })
          .catch(function (error) {
            console.log(error);
          });

          this.imgsrc = nowimgs
        }
      },
      //得到html中的图片地址
      getSrc (html) {
        var imgReg = /<img.*?(?:>|\/>)/gi
        // 匹配src属性
        var srcReg = /src=[\\"]?([^\\"]*)[\\"]?/i
        var arr = html.match(imgReg)
        let imgs = []
        if (arr) {
          for (let i = 0; i < arr.length; i++) {
            var src = arr[i].match(srcReg)[1]
            imgs.push(src)
          }
        }
        return imgs
      },
    }
}
</script>
<style scoped lang="less">

/*wangEditor css*/
/deep/.sd_pre_change_note{
  table {
    border-top: 1px solid #ccc;
    border-left: 1px solid #ccc;
  }
  table td, table th {
    border-bottom: 1px solid #ccc;
    border-right: 1px solid #ccc;
    padding: 3px 5px;
  }
  table th {
    background-color: #f1f1f1;
    border-bottom: 2px solid #ccc;
    text-align: center;
  }
  /* blockquote 样式 */
  blockquote {
    display: block;
    border-left: 8px solid #d0e5f2;
    padding: 5px 10px;
    margin: 10px 0;
    line-height: 1.4;
    font-size: 100%;
    background-color: #f1f1f1;
  }

  /* code 样式 */
  code {
    display: inline-block;
    *display: inline;
    *zoom: 1;
    background-color: #f1f1f1;
    border-radius: 3px;
    padding: 3px 5px;
    margin: 0 3px;
  }
  pre code {
    display: inline-flex;
  }

  /* ul ol 样式 */
  ul, ol {
    margin: 10px 0 10px 20px;
  }
</style>

后台部分  urls.py

from django.conf.urls import url
from django.urls import path
from Apps.AuditList import auditList as auditListViews

urlpatterns = [
    ...
    path('auditInfo/deleteImg', auditListViews.deleteImg, name='deleteImg'),
    path('backend/upload', auditListViews.uploadImags, name='uploadImags'),
    url(r'^auditImg/(?P<path>.*)$', serve, {'document_root': os.path.join(BASE_DIR, "static/img/detail"),}),  #显示静态资源 图片
    ...
]

views.py

from django.http import JsonResponse, HttpResponse
import os
import re
import json


def deleteImg(request):
    if request.method == 'POST':
        response = {}
        try:
            imgPaths = json.loads(request.POST.get('imgPaths','[]'))
            for i in imgPaths:
                matchData = re.search(r'auditImg/(.*)', i)
                if matchData:
                    BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
                    file_path = BASE_DIR + '/static/img/detail/'+matchData.groups()[0]        #拼出server上保存的图片路径
                    os.remove(file_path)           
            response['msg'] = 'success'
            response['error_num'] = 0

        except Exception as e:
            print(traceback.format_exc(limit=1))
            response['msg'] = str(e)
            response['error_num'] = 1

        return JsonResponse(response)


#audit富文本 上传图片   图片保存在/static/img/detail/下
def uploadImags(request):
    if request.method == 'POST':
        resp = {'errno': 100, 'data': '请选择图片'}
        upfiles = request.FILES.getlist('image', None)
        updir = request.POST.get('updir', 'common')
 
        if(upfiles == None):
            return HttpResponse(json.dumps(resp), content_type="application/json")
        
        resUrls = []
        resIds = []
        for up in upfiles:
            upfileres = _upload(up, updir)
            resUrls.append(upfileres['url'])
            resIds.append(upfileres['id'])
 
        #前端使用的plopload上传,一次上传一张,多张分多次上传, 使用的wangeditor,是多张图片是一次上传的
        if updir == 'detail':
            resp = {'errno': 0, 'data': resUrls}
        else:
            resp = {'errno': 200, 'id': resIds[0], 'url': resUrls[0]}
 
 
        return HttpResponse(json.dumps(resp), content_type="application/json")
 
def _upload(upfile, updir):
    new_file_name = _hash_filename(upfile.name)
    res_save_path = _get_path(updir)
    save_path = res_save_path['save_path']
    local_save_path = res_save_path['local_save_path']
    local_save_file = local_save_path + new_file_name
    save_file = save_path + new_file_name
    url = 'http://127.0.0.1:8000/' + save_file.replace('static/img/'+updir,'auditImg')
   
    with open(local_save_file, 'wb') as f:
        for line in upfile.chunks(): 
            f.write(line)
        f.close()
    
    return {'id': 23, 'url': url}

def _hash_filename(filename):
    _, suffix = os.path.splitext(filename)
    return '%s%s' % (uuid.uuid4().hex, suffix)

def _get_path(updir):
    if(updir == ''):
        path = 'static/img/' + time.strftime("%Y%m%d", time.localtime()) + '/'
    else:
        path = 'static/img/' + updir + '/' + time.strftime("%Y%m%d", time.localtime()) + '/'
    # 本地储存路径
    local_save_path = path
    # 数据库存储路径
    save_path = os.path.join(path)
    isExists = os.path.exists(local_save_path)
    if not isExists:
        os.makedirs(local_save_path) 
    
    return {'save_path': save_path, 'local_save_path': local_save_path}

注:该文省略了保存富文本html到后台的步骤,省略了读取后台保存的富文本html步骤

期间遇到的问题:

1.富文本插入表情,保存html到后台失败

要使用富文本的表情图,需要设置该字段的格式为utf8mb4 

ALTER TABLE auditinfo MODIFY testDescription TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

 SHOW FULL COLUMNS FROM auditinfo;

2.插入图片成功了,但是还是要实现删除图片的功能,防止服务器保存无用的信息

网上搜了删除图片的功能,成功添加

 

目录/大纲的使用

        当编辑的文章过长时,可以使用WangEditor的Scroll-To-Head,获取编辑区域的所有标题,生成目录/大纲,可以通过点击大纲让编辑器滚动到某个标题。

官方文档:Scroll To Head · wangEditor 用户文档

代码实现:

创建编辑器的时候,配置 editor.config.onCatalogChange,可以实时获取编辑器所有标题,标题变化即可触发该回调函数。

<el-row :gutter="20">
  <el-col :span="headChecked?18:24">
    <div ref="editor" v-html="noteEditor"></div>
    <el-input type="textarea" v-model="noteContent" readonly v-show="false" ></el-input>
  </el-col>
  <el-col :span="headChecked?6:0" style="background:#F2F6FC">
    <h4 style="text-align:center">目 录</h4>
    <el-divider></el-divider>
    <ul style="height:400px;overflow-y: scroll">
      <li v-for="head in contentHeadList" :key="head.id">
        <el-link :underline="false" v-if="head.tag=='H1'" style="font-size:16px" @click="scrollTo(head.id)">{{head.text}}</el-link>
        <el-link :underline="false" v-else-if="head.tag=='H2'" @click="scrollTo(head.id)">&nbsp;&nbsp;&nbsp;&nbsp;{{head.text}}</el-link>
        <el-link :underline="false" v-else @click="scrollTo(head.id)">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{{head.text}}</el-link>
      </li>
    </ul>
  </el-col>
</el-row>

//滚动到某个标题
editor.config.onCatalogChange = function (headList) {
    this.headList = headList
    console.log(headList)
/*
        headList 格式
        [
            { 
                id: "eold9", // 标题 id
                tag: "H1",
                text: "标题文字"
            },
            { ... },
            { ... }
        ]
    */
}

____________________________________________________________

scrollTo(headId){
    this.editor.scrollToHead(headId)
}

后台数据库保存,编辑内容noteContent和标题headList,内容noteContent中,标题也是带着 id 的,格式如下。

<h1 id="eold9">标题一</h1>
<p>正文</p>
<h2 id="nh339">标题二</h2>
<p>正文</p>
<h3 id="5mgwk">标题三</h3>
<p>正文</p>

可以使用这些 id 来做锚点,实现 scroll to head 的功能。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐