vue中wangEditor5编辑器的基本使用

一、wangEditor5是什么

wangEditor是一款富文本编译器插件,其他的我就不再过多赘述,因为官网上有一大截对于这个编译器的介绍,但我摸索使用的这两天里给我的最直观的感受就是,它是由中国开发者开发,所有的文档都是中文的,这一点上对我这个菜鸡来说非常友好,不用再去逐字逐句翻译,然后去读那些蹩脚的机翻中文。而且功能很丰富,能够满足很多需求,wangEditor5提供很多版本的代码,vue2,vue3,react都支持。

接下来就介绍一下wangEditor5的基本使用,以及博主在使用中遇到的各种问题以及其解决方案。

官方网站:

wangEditor开源 Web 富文本编辑器,开箱即用,配置简单https://www.wangeditor.com/

二、wangEditor5基本使用

(一)、安装

  1. yarn add @wangeditor/editorforvue
  2. # 或者 npm install @wangeditor/editor-for-vue –save

(二)、编译器引入

  1. import { Editor, Toolbar } from ‘@wangeditor/editor-for-vue’;

Editor:引入@wangEditor编译器

Toolbar:引入菜单栏

(三)、css及变量引入

  1. <style src=“@wangeditor/editor/dist/css/style.css” >
  2. </style>

这里需要注意,引入的样式写在带有scoped标签的style内无效。只能引入在全局样式里,但可能会造成样式覆盖,一般会有个清除样式的文件,会把里面的样式覆盖掉。

三、wangEditor5工具栏配置

工具栏配置有很多选项,这里以官方为主,我只做一些常用的配置介绍。

(一)、editor.getAllMenuKeys()

查询编辑器注册的所有菜单 key (可能有的不在工具栏上)这里注意要在

  1.      onCreated(editor) {
  2.              this.editor = Object.seal(editor)
  3.          },

这个函数中去调用 (这个函数是基本配置之一),不然好像调不出来,当然也有可能是博主太菜。

(二)、toolbarConfig中的excludeKeys

  1. toolbarConfig: {
  2.          excludeKeys:[“uploadVideo”,“fullScreen”,“emotion”,“insertTable”]
  3.          },

这个是菜单栏配置的一种:排除某项配置 ,这里填写的key值就是用上面那个方法,查出来的key值。

四、wangEditor5上传图片

首先在data中return以下信息。

  1. editorConfig: {
  2.          placeholder: ‘请输入内容…’ ,
  3.          MENU_CONF: {
  4.                      uploadImage: {
  5.                      customUpload: this.uploadImg,
  6.                      },
  7.                  }
  8.          },

然后书写this.uploadImg函数。

  1.      uploadImg(file, insertFn){
  2.          let imgData = new FormData();
  3.              imgData.append(‘file’, file);
  4.          axIOS({
  5.          url: this.uploadConfig.api,
  6.          method: ‘post’,
  7.          data: imgData,
  8.          }).then((response) => {
  9.          insertFn(response.data.FileURL);
  10.          });
  11.      },

注意,这里因为返回的数据结构与@wangeditor要求的不一致,因此要使用 insertFn 函数 去包裹返回的url地址。

五、wangEditor5的一些问题收集及解决

(一)、引入@wangEditor 编译报错 ” Module parse failed: Unexpected token (12828:18)You may need an appropriate loader to handle this file type.”

-1

解决方法:在 wwebpack.base.conf.js 文件的module>rules>.js 的include下加入

resolve(‘node_modules/@wangeditor’)

就可以了。

-2

(二)、@wangeditor有序列表无序列表的样式消失问题。

大概率是全局样式清除导致的样式消失,可以去调试工具里看一看,样式覆盖的问题。

然后在style里deep一下改变样式就行了。

  1. .editorStyle{
  2.      /deep/ .wetextcontainer>.wescroll>div ol li{
  3.      liststyle: auto ;
  4.      }
  5.      /deep/ .wetextcontainer>.wescroll>div ul li{
  6.      liststyle: disc ;
  7.      }
  8.      /deep/ .wetextplaceholder{
  9.      top:7px;
  10.      }
  11. }

六、完整代码

  1. <template>
  2.      <div v-loading=“Loading” class=“app_detail”>
  3.      <el-form ref=“form” :rules=“rules” :model=“appDetail” label-width=“80px”>
  4.          <el-form-item prop=“name” label=应用名称”>
  5.          <el-input v-model=“appDetail.name” style=width: 360px></el-input>
  6.          </el-form-item>
  7.          <el-form-item label=“分类”>
  8.          <el-select
  9.              v-model=“appDetail.appClassificationID”
  10.              style=width: 360px
  11.              placeholder=“选择应用分类”
  12.          >
  13.              <template v-for=“item in classes”>
  14.              <el-option
  15.                  v-if=“item.parentAppClassificationID”
  16.                  :key=“item.appClassificationID”
  17.                  :label=“item.appClassificationName”
  18.                  :value=“item.appClassificationID”
  19.              ></el-option>
  20.              </template>
  21.          </el-select>
  22.          <div class=“inputdesc”>为了适应前台展示,应用只能属于二级分类</div>
  23.          </el-form-item>
  24.          <el-form-item label=“所属组织”>
  25.          <el-select
  26.              v-model=“appDetail.orgID”
  27.              placeholder=“请选择所属组织”
  28.              style=width: 360px
  29.          >
  30.              <el-option
  31.              v-for=“item in myorgs”
  32.              :key=“item.orgID”
  33.              :label=“item.name”
  34.              :value=“item.orgID”
  35.              ></el-option>
  36.          </el-select>
  37.          </el-form-item>
  38.          <el-form-item prop=“tags” label=“标签”>
  39.          <el-select
  40.              v-model=“appDetail.tags”
  41.              multiple
  42.              filterable
  43.              style=width: 360px
  44.              placeholder=“请输入或选择应用标签”
  45.          >
  46.              <el-option
  47.              v-for=“item in existTags”
  48.              :key=“item”
  49.              :label=“item”
  50.              :value=“item”
  51.              ></el-option>
  52.          </el-select>
  53.          </el-form-item>
  54.          <el-row>
  55.          <el-col :span=“8” class=“appsFrom”>
  56.              <el-form-item
  57.              label=“应用Logo”
  58.              ref=“uploadpic”
  59.              class=“el-form-item-cen”
  60.              prop=“logo”
  61.              >
  62.              <el-upload
  63.                  class=“avatar-uploader”
  64.                  :action=“uploadConfig.api”
  65.                  :with-credentials=“true”
  66.                  :headers=“uploadConfig.headers”
  67.                  :show-file-list=“false”
  68.                  :on-success=“handleAvatarSuccess”
  69.                  :on-error=“handleAvatarError”
  70.                  :before-upload=“beforeAvatarUpload”
  71.              >
  72.                  <img v-if=“appDetail.logo” :src=“appDetail.logo” class=“avatar” />
  73.                  <i v-else class=“el-icon-plus avatar-uploader-icon”></i>
  74.                  <i
  75.                  v-if=“appDetail.logo”
  76.                  class=“el-icon-delete”
  77.                  @click.stop=“() => handleRemove()”
  78.                  ></i>
  79.              </el-upload>
  80.              <span style=color: #999999; font-size: 12px>
  81.                  建议上传 100*100 比例的Logo
  82.      </span>
  83.              </el-form-item>
  84.          </el-col>
  85.          </el-row>
  86.          <el-form-item prop=“desc” label=“应用简介”>
  87.          <el-input
  88.              type=“textarea”
  89.              v-model=“appDetail.desc”
  90.              :rows=“3”
  91.              style=width: 360px
  92.          ></el-input>
  93.          </el-form-item>
  94.          <el-form-item prop=“introduction” label=“应用详情”>
  95.          <div style=border: 1px solid #ccc; >
  96.          <Toolbar
  97.              style=borderbottom: 1px solid #ccc
  98.              :editor=“editor”
  99.              :defaultConfig=“toolbarConfig”
  100.              :mode=“mode”
  101.              class=“barStyle”
  102.          />
  103.          <Editor
  104.              style=height: 500px; overflowy: hidden;
  105.              v-model=“appDetail.introduction”
  106.              :defaultConfig=“editorConfig”
  107.              :mode=“mode”
  108.              @onCreated=“onCreated”
  109.              class=“editorStyle”
  110.          />
  111.      </div>
  112.          </el-form-item>
  113.      </el-form>
  114.      <el-button
  115.          class=“save_btn”
  116.          type=“primary”
  117.          @click=“onSubmit”
  118.          :loading=“commitLoading”
  119.          >保存</el-button
  120.      >
  121.      </div>
  122. </template>
  123. <script>
  124. import { updateApp } from ‘@/api/app’;
  125. import { getStoreAvailableTags } from ‘@/api/appStore’;
  126. import { getToken } from ‘@/utils/auth’;
  127. import axios from ‘axios’;
  128. import { errorHandle } from ‘../../../../utils/error’;
  129. import { Editor, Toolbar } from ‘@wangeditor/editor-for-vue’;
  130. import { IToolbarConfig, DomEditor, IEditorConfig } from ‘@wangeditor/editor’
  131. export default {
  132.      name: ‘BasicInfo’,
  133.      components: { Editor, Toolbar },
  134.      props: {
  135.      appDetail: {
  136.          type: Object
  137.      },
  138.      marketID: {
  139.          type: String
  140.      },
  141.      Loading: Boolean
  142.      },
  143.      data() {
  144.      var baseDomain = process.env.BASE_API;
  145.      if (baseDomain == ‘/’) {
  146.          baseDomain = window.location.origin;
  147.      }
  148.      const isChinese = (temp) => {
  149.          return /^[\u4e00-\u9fa5]+$/i.test(temp);
  150.      };
  151.      const tagValidate = (rule, value, callback) => {
  152.          let checked = true;
  153.          value.map((tag) => {
  154.          if (tag.length < 2) {
  155.              callback(‘每个标签至少两个字符’);
  156.              checked = false;
  157.              return;
  158.          }
  159.          if (isChinese(tag) && tag.length > 5) {
  160.              callback(‘中文标签字数应处于2-5个之间’);
  161.              checked = false;
  162.              return;
  163.          }
  164.          if (Number(tag) > 0) {
  165.              callback(‘标签不能为纯数字组成’);
  166.              checked = false;
  167.              return;
  168.          }
  169.          });
  170.          if (checked) {
  171.          callback();
  172.          }
  173.      };
  174.      return {
  175.          editor: null,
  176.          toolbarConfig: {
  177.          excludeKeys:[“uploadVideo”,“fullScreen”,“emotion”,“insertTable”]
  178.          },
  179.          editorConfig: {
  180.          placeholder: ‘请输入内容…’ ,
  181.          MENU_CONF: {
  182.                      uploadImage: {
  183.                      customUpload: this.uploadImg,
  184.                      },
  185.                  }
  186.          },
  187.          mode: ‘default’, // or ‘simple’
  188.          commitLoading: false,
  189.          classes: [],
  190.          existTags: [],
  191.          appPublishTypes: [
  192.          {
  193.              value: ‘public’,
  194.              label: ‘免费公开’
  195.          },
  196.          {
  197.              value: ‘integral’,
  198.              label: ‘金额销售’
  199.          },
  200.          {
  201.              value: ‘private’,
  202.              label: ‘私有’
  203.          },
  204.          {
  205.              value: ‘show’,
  206.              label: ‘展览’
  207.          }
  208.          ],
  209.          uploadConfig: {
  210.          api: `${baseDomain}/appserver/uploads/picture`,
  211.          headers: {
  212.              Authorization: getToken()
  213.          },
  214.          },
  215.          editorOption: {},
  216.          rules: {
  217.          name: [
  218.              { required: true, message: ‘应用名称不能为空’, trigger: ‘blur’ },
  219.              { min: 2, message: ‘至少两个字符’, trigger: ‘blur’ },
  220.              { max: 24, message: ‘应用名称建议不超过24个字符’, trigger: ‘blur’ }
  221.          ],
  222.          desc: [
  223.              { required: true, message: ‘应用简介不能为空’, trigger: ‘blur’ },
  224.              { min: 10, message: ‘至少10个字符’, trigger: ‘blur’ },
  225.              { max: 82, message: ‘描述最多82个字符’, trigger: ‘blur’ }
  226.          ],
  227.          introduction: [
  228.              { max: 10140, message: ‘描述最多10240个字符’, trigger: ‘blur’ }
  229.          ],
  230.          tags: [{ validator: tagValidate, trigger: ‘change’ }]
  231.          }
  232.      };
  233.      },
  234.      created() {
  235.      this.fetchStoreAppClassList();
  236.      this.fetchStoreAppTags();
  237.      },
  238.      computed: {
  239.      myorgs() {
  240.          return this.$store.state.user.userOrgs;
  241.      }
  242.      },
  243.      methods: {
  244.      uploadImg(file, insertFn){
  245.          let imgData = new FormData();
  246.              imgData.append(‘file’, file);
  247.          axios({
  248.          url: this.uploadConfig.api,
  249.          method: ‘post’,
  250.          data: imgData,
  251.          }).then((response) => {
  252.          insertFn(response.data.FileURL);
  253.          });
  254.      },
  255.      onCreated(editor) {
  256.              this.editor = Object.seal(editor)
  257.          },
  258.      fetchStoreAppTags() {
  259.          getStoreAvailableTags({
  260.          marketID: this.marketID,
  261.          size: 1
  262.          })
  263.          .then((res) => {
  264.              if (res && res.tags) {
  265.              const tags = [];
  266.              res.tags.map((item) => {
  267.                  tags.push(item.name);
  268.              });
  269.              this.existTags = tags;
  270.              }
  271.          })
  272.          .catch((err) => {
  273.              this.Loading = false;
  274.          });
  275.      },
  276.      fetchStoreAppClassList() {
  277.          this.$store
  278.          .dispatch(‘GetStoreAppClassificationList’, {
  279.              marketID: this.marketID,
  280.              disableTree: true
  281.          })
  282.          .then((res) => {
  283.              if (res) {
  284.              this.classes = res;
  285.              }
  286.          })
  287.          .catch(() => {});
  288.      },
  289.      fetchUserOrgs() {
  290.          this.$store
  291.          .dispatch(‘GetUserOrgList’)
  292.          .then((res) => {
  293.              if (res) {
  294.              this.myorgs = res;
  295.              }
  296.          })
  297.          .catch(() => {});
  298.      },
  299.      markdownContentUpdate(md, render) {
  300.          this.appData.introduction_html = render;
  301.      },
  302.      markdownImgAdd(pos, $file) {
  303.          // 第一步.将图片上传到服务器.
  304.          var formdata = new FormData();
  305.          formdata.append(‘file’, $file);
  306.          axios({
  307.          url: this.api,
  308.          method: ‘post’,
  309.          data: formdata,
  310.          headers: this.Token
  311.          }).then((re) => {
  312.          if (re && re.data && re.data.data) {
  313.          this.$refs.md.$img2Url(pos, re.data.data);
  314.          }
  315.          });
  316.      },
  317.      handleAvatarSuccess(res, file) {
  318.          this.appDetail.logo = res.FileURL;
  319.      },
  320.      handleAvatarError(re) {
  321.          if (re.code == 10024) {
  322.          this.$message.warning(
  323.              ‘上传图片类型不支持,请上传以.png .jpg .jpeg 结尾的图片’
  324.          );
  325.          return;
  326.          }
  327.          this.$message.warning(‘上传失败!’);
  328.      },
  329.      beforeAvatarUpload(file) {
  330.          const isJPG = file.type === ‘image/jpeg’;
  331.          const isPng = file.type === ‘image/png’;
  332.          const isLt2M = file.size / 1024 / 1024 < 2;
  333.          if (!isJPG && !isPng) {
  334.          this.$message.warning(‘上传Logo图片只能是JPG、PNG格式!’);
  335.          }
  336.          if (!isLt2M) {
  337.          this.$message.warning(‘上传头像图片大小不能超过 2MB!’);
  338.          }
  339.          return (isJPG || isPng) && isLt2M;
  340.      },
  341.      handleRemove() {
  342.          this.$confirm(‘是否删除logo’, ‘提示’, {
  343.          confirmButtonText: ‘确定’,
  344.          cancelButtonText: ‘取消’,
  345.          type: ‘warning’
  346.          }).then(() => {
  347.          this.appDetail.logo = ;
  348.          });
  349.      },
  350.      handlePictureCardPreview(file) {
  351.          this.dialogImageUrl = file.url;
  352.          this.dialogVisible = true;
  353.      },
  354.      changeSelectApp_type_id(value) {
  355.          this.appData.app_type_id = value;
  356.          this.$forceUpdate();
  357.      },
  358.      changeSelectPublish_type(value) {
  359.          this.appData.publish_type = value;
  360.          this.$forceUpdate();
  361.      },
  362.      onSubmit() {
  363.          this.$refs.form.validate((valid) => {
  364.          if (valid) {
  365.              this.commitLoading = true;
  366.              this.$confirm(‘是否提交数据’, ‘提示’, {
  367.              confirmButtonText: ‘确定’,
  368.              cancelButtonText: ‘取消’,
  369.              type: ‘warning’
  370.              })
  371.              .then(() => {
  372.                  updateApp(this.appDetail)
  373.                  .then((res) => {
  374.                      this.$message.success(‘应用信息更新成功’);
  375.                      this.commitLoading = false;
  376.                  })
  377.                  .catch((err) => {
  378.                      errorHandle(err);
  379.                      this.commitLoading = false;
  380.                  });
  381.              })
  382.              .catch(() => {
  383.                  this.commitLoading = false;
  384.              });
  385.          } else {
  386.              return false;
  387.          }
  388.          });
  389.      }
  390.      }
  391. };
  392. </script>
  393. <style lang=“scss” scoped >
  394. .app_detail {
  395.      position: relative;
  396.      paddingbottom: 20px;
  397.      .save_btn {
  398.      marginleft: 80px;
  399.      }
  400.      .elselect {
  401.      width: 100%;
  402.      }
  403. }
  404. .editorStyle{
  405.      /deep/ .wetextcontainer>.wescroll>div ol li{
  406.      liststyle: auto ;
  407.      }
  408.      /deep/ .wetextcontainer>.wescroll>div ul li{
  409.      liststyle: disc ;
  410.      }
  411.      /deep/ .wetextplaceholder{
  412.      top:7px;
  413.      }
  414. }
  415. .barStyle{
  416.      /deep/ .webaritem{
  417.      padding:2.5px
  418.      }
  419.      /deep/ .webaritem > button >.title{
  420.      borderleft:0 !important;
  421.      }
  422. }
  423. </style>;
  424. <style src=“@wangeditor/editor/dist/css/style.css” >
  425. .inputdesc {
  426.      fontsize: 12px;
  427.      color: rgba(0, 0, 0, 0.45);
  428.      transition: color 0.3s cubicbezier(0.215, 0.61, 0.355, 1);
  429. }
  430. .app_detail img {
  431.      width: auto;
  432. }
  433. .app_detail .qlformats {
  434.      lineheight: 22px;
  435. }
  436. </style>

总结

到此这篇关于vue中wangEditor5编辑器的基本使用的文章就介绍到这了,更多相关vue wangEditor5的使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

标签

发表评论