vue中wangEditor5編輯器的基本使用

一、wangEditor5是什麼

wangEditor是一款富文本編譯器插件,其他的我就不再過多贅述,因為官網上有一大截對於這個編譯器的介紹,但我摸索使用的這兩天裡給我的最直觀的感受就是,它是由中國開發者開發,所有的文檔都是中文的,這一點上對我這個菜雞來說非常友好,不用再去逐字逐句翻譯,然後去讀那些蹩腳的機翻中文。而且功能很豐富,能夠滿足很多需求,wangEditor5提供很多版本的代碼,vue2,vue3,react都支持。

接下來就介紹一下wangEditor5的基本使用,以及博主在使用中遇到的各種問題以及其解決方案。

官方網站:

wangEditor開源 Web 富文本編輯器,開箱即用,配置簡單https://www.wangeditor.com/

二、wangEditor5基本使用

(一)、安裝

yarn add @wangeditor/editor-for-vue
# 或者 npm install @wangeditor/editor-for-vue --save

(二)、編譯器引入

import { Editor, Toolbar } from '@wangeditor/editor-for-vue';

Editor:引入@wangEditor編譯器 

Toolbar:引入菜單欄

(三)、css及變量引入

<style src="@wangeditor/editor/dist/css/style.css" >
 
</style>

這裡需要註意,引入的樣式寫在帶有scoped標簽的style內無效。隻能引入在全局樣式裡,但可能會造成樣式覆蓋,一般會有個清除樣式的文件,會把裡面的樣式覆蓋掉。

三、wangEditor5工具欄配置

工具欄配置有很多選項,這裡以官方為主,我隻做一些常用的配置介紹。

(一)、editor.getAllMenuKeys() 

查詢編輯器註冊的所有菜單 key (可能有的不在工具欄上)這裡註意要在

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

這個函數中去調用 (這個函數是基本配置之一),不然好像調不出來,當然也有可能是博主太菜。

(二)、toolbarConfig中的excludeKeys

toolbarConfig: {
        excludeKeys:["uploadVideo","fullScreen","emotion","insertTable"]
       },

這個是菜單欄配置的一種:排除某項配置 ,這裡填寫的key值就是用上面那個方法,查出來的key值。

四、wangEditor5上傳圖片

首先在data中return以下信息。

editorConfig: { 
        placeholder: '請輸入內容...' ,
        MENU_CONF: {
					uploadImage: {
						customUpload: this.uploadImg,
					},
				}
      },

然後書寫this.uploadImg函數。

 uploadImg(file, insertFn){
      let imgData = new FormData();
			imgData.append('file', file);
      axios({
        url: this.uploadConfig.api,
        method: 'post',
        data: imgData,
      }).then((response) => {
       insertFn(response.data.FileURL);
      });
    },

註意,這裡因為返回的數據結構與@wangeditor要求的不一致,因此要使用 insertFn 函數 去包裹返回的url地址。

五、wangEditor5的一些問題收集及解決

(一)、引入@wangEditor 編譯報錯 " Module parse failed: Unexpected token (12828:18)You may need an appropriate loader to handle this file type."

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

resolve('node_modules/@wangeditor')

 就可以瞭。

(二)、@wangeditor有序列表無序列表的樣式消失問題。

大概率是全局樣式清除導致的樣式消失,可以去調試工具裡看一看,樣式覆蓋的問題。

然後在style裡deep一下改變樣式就行瞭。

.editorStyle{
  /deep/ .w-e-text-container>.w-e-scroll>div ol li{
    list-style: auto ;
  }
  /deep/ .w-e-text-container>.w-e-scroll>div ul li{
    list-style: disc ;
  }
  /deep/ .w-e-text-placeholder{
    top:7px;
  }
  
}

六、完整代碼

<template>
  <div v-loading="Loading" class="app_detail">
    <el-form ref="form" :rules="rules" :model="appDetail" label-width="80px">
      <el-form-item prop="name" label="應用名稱">
        <el-input v-model="appDetail.name" style="width: 360px"></el-input>
      </el-form-item>
      <el-form-item label="分類">
        <el-select
          v-model="appDetail.appClassificationID"
          style="width: 360px"
          placeholder="選擇應用分類"
        >
          <template v-for="item in classes">
            <el-option
              v-if="item.parentAppClassificationID"
              :key="item.appClassificationID"
              :label="item.appClassificationName"
              :value="item.appClassificationID"
            ></el-option>
          </template>
        </el-select>
        <div class="inputdesc">為瞭適應前臺展示,應用隻能屬於二級分類</div>
      </el-form-item>
      <el-form-item label="所屬組織">
        <el-select
          v-model="appDetail.orgID"
          placeholder="請選擇所屬組織"
          style="width: 360px"
        >
          <el-option
            v-for="item in myorgs"
            :key="item.orgID"
            :label="item.name"
            :value="item.orgID"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-form-item prop="tags" label="標簽">
        <el-select
          v-model="appDetail.tags"
          multiple
          filterable
          style="width: 360px"
          placeholder="請輸入或選擇應用標簽"
        >
          <el-option
            v-for="item in existTags"
            :key="item"
            :label="item"
            :value="item"
          ></el-option>
        </el-select>
      </el-form-item>
      <el-row>
        <el-col :span="8" class="appsFrom">
          <el-form-item
            label="應用Logo"
            ref="uploadpic"
            class="el-form-item-cen"
            prop="logo"
          >
            <el-upload
              class="avatar-uploader"
              :action="uploadConfig.api"
              :with-credentials="true"
              :headers="uploadConfig.headers"
              :show-file-list="false"
              :on-success="handleAvatarSuccess"
              :on-error="handleAvatarError"
              :before-upload="beforeAvatarUpload"
            >
              <img v-if="appDetail.logo" :src="appDetail.logo" class="avatar" />
              <i v-else class="el-icon-plus avatar-uploader-icon"></i>
              <i
                v-if="appDetail.logo"
                class="el-icon-delete"
                @click.stop="() => handleRemove()"
              ></i>
            </el-upload>
            <span style="color: #999999; font-size: 12px">
              建議上傳 100*100 比例的Logo
            </span>
          </el-form-item>
        </el-col>
      </el-row>
      <el-form-item prop="desc" label="應用簡介">
        <el-input
          type="textarea"
          v-model="appDetail.desc"
          :rows="3"
          style="width: 360px"
        ></el-input>
      </el-form-item>
      <el-form-item prop="introduction" label="應用詳情">
        <div style="border: 1px solid #ccc; ">
        <Toolbar
            style="border-bottom: 1px solid #ccc"
            :editor="editor"
            :defaultConfig="toolbarConfig"
            :mode="mode"
            class="barStyle"
        />
        <Editor
            style="height: 500px; overflow-y: hidden;"
            v-model="appDetail.introduction"
            :defaultConfig="editorConfig"
            :mode="mode"
            @onCreated="onCreated"
            class="editorStyle"
        />
    </div>
      </el-form-item>
    </el-form>
    <el-button
      class="save_btn"
      type="primary"
      @click="onSubmit"
      :loading="commitLoading"
      >保存</el-button
    >
  </div>
</template>
 
<script>
import { updateApp } from '@/api/app';
import { getStoreAvailableTags } from '@/api/appStore';
import { getToken } from '@/utils/auth';
import axios from 'axios';
import { errorHandle } from '../../../../utils/error';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
import { IToolbarConfig, DomEditor, IEditorConfig } from '@wangeditor/editor'
export default {
  name: 'BasicInfo',
  components: { Editor, Toolbar },
  props: {
    appDetail: {
      type: Object
    },
    marketID: {
      type: String
    },
    Loading: Boolean
  },
  data() {
    var baseDomain = process.env.BASE_API;
    if (baseDomain == '/') {
      baseDomain = window.location.origin;
    }
    const isChinese = (temp) => {
      return /^[\u4e00-\u9fa5]+$/i.test(temp);
    };
    const tagValidate = (rule, value, callback) => {
      let checked = true;
      value.map((tag) => {
        if (tag.length < 2) {
          callback('每個標簽至少兩個字符');
          checked = false;
          return;
        }
        if (isChinese(tag) && tag.length > 5) {
          callback('中文標簽字數應處於2-5個之間');
          checked = false;
          return;
        }
        if (Number(tag) > 0) {
          callback('標簽不能為純數字組成');
          checked = false;
          return;
        }
      });
      if (checked) {
        callback();
      }
    };
    return {
      editor: null,
      toolbarConfig: {
        excludeKeys:["uploadVideo","fullScreen","emotion","insertTable"]
       },
      editorConfig: { 
        placeholder: '請輸入內容...' ,
        MENU_CONF: {
					uploadImage: {
						customUpload: this.uploadImg,
					},
				}
      },
      mode: 'default', // or 'simple'
      commitLoading: false,
      classes: [],
      existTags: [],
      appPublishTypes: [
        {
          value: 'public',
          label: '免費公開'
        },
        {
          value: 'integral',
          label: '金額銷售'
        },
        {
          value: 'private',
          label: '私有'
        },
        {
          value: 'show',
          label: '展覽'
        }
      ],
      uploadConfig: {
        api: `${baseDomain}/app-server/uploads/picture`,
        headers: {
          Authorization: getToken()
        },
      },
      editorOption: {},
      rules: {
        name: [
          { required: true, message: '應用名稱不能為空', trigger: 'blur' },
          { min: 2, message: '至少兩個字符', trigger: 'blur' },
          { max: 24, message: '應用名稱建議不超過24個字符', trigger: 'blur' }
        ],
        desc: [
          { required: true, message: '應用簡介不能為空', trigger: 'blur' },
          { min: 10, message: '至少10個字符', trigger: 'blur' },
          { max: 82, message: '描述最多82個字符', trigger: 'blur' }
        ],
        introduction: [
          { max: 10140, message: '描述最多10240個字符', trigger: 'blur' }
        ],
        tags: [{ validator: tagValidate, trigger: 'change' }]
      }
    };
  },
  created() {
    this.fetchStoreAppClassList();
    this.fetchStoreAppTags();
  },
  computed: {
    myorgs() {
      return this.$store.state.user.userOrgs;
    }
  },
  
  methods: {
    uploadImg(file, insertFn){
      let imgData = new FormData();
			imgData.append('file', file);
      axios({
        url: this.uploadConfig.api,
        method: 'post',
        data: imgData,
      }).then((response) => {
       insertFn(response.data.FileURL);
      });
    },
    onCreated(editor) {
            this.editor = Object.seal(editor) 
        },
    fetchStoreAppTags() {
      getStoreAvailableTags({
        marketID: this.marketID,
        size: -1
      })
        .then((res) => {
          if (res && res.tags) {
            const tags = [];
            res.tags.map((item) => {
              tags.push(item.name);
            });
            this.existTags = tags;
          }
        })
        .catch((err) => {
          this.Loading = false;
        });
    },
    fetchStoreAppClassList() {
      this.$store
        .dispatch('GetStoreAppClassificationList', {
          marketID: this.marketID,
          disableTree: true
        })
        .then((res) => {
          if (res) {
            this.classes = res;
          }
        })
        .catch(() => {});
    },
    fetchUserOrgs() {
      this.$store
        .dispatch('GetUserOrgList')
        .then((res) => {
          if (res) {
            this.myorgs = res;
          }
        })
        .catch(() => {});
    },
    markdownContentUpdate(md, render) {
      this.appData.introduction_html = render;
    },
    markdownImgAdd(pos, $file) {
      // 第一步.將圖片上傳到服務器.
      var formdata = new FormData();
      formdata.append('file', $file);
      axios({
        url: this.api,
        method: 'post',
        data: formdata,
        headers: this.Token
      }).then((re) => {
        if (re && re.data && re.data.data) {
          this.$refs.md.$img2Url(pos, re.data.data);
        }
      });
    },
    handleAvatarSuccess(res, file) {
      this.appDetail.logo = res.FileURL;
    },
    handleAvatarError(re) {
      if (re.code == 10024) {
        this.$message.warning(
          '上傳圖片類型不支持,請上傳以.png .jpg .jpeg 結尾的圖片'
        );
        return;
      }
      this.$message.warning('上傳失敗!');
    },
    beforeAvatarUpload(file) {
      const isJPG = file.type === 'image/jpeg';
      const isPng = file.type === 'image/png';
      const isLt2M = file.size / 1024 / 1024 < 2;
 
      if (!isJPG && !isPng) {
        this.$message.warning('上傳Logo圖片隻能是JPG、PNG格式!');
      }
      if (!isLt2M) {
        this.$message.warning('上傳頭像圖片大小不能超過 2MB!');
      }
      return (isJPG || isPng) && isLt2M;
    },
    handleRemove() {
      this.$confirm('是否刪除logo', '提示', {
        confirmButtonText: '確定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.appDetail.logo = '';
      });
    },
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },
    changeSelectApp_type_id(value) {
      this.appData.app_type_id = value;
      this.$forceUpdate();
    },
    changeSelectPublish_type(value) {
      this.appData.publish_type = value;
      this.$forceUpdate();
    },
    onSubmit() {
      this.$refs.form.validate((valid) => {
        if (valid) {
          this.commitLoading = true;
          this.$confirm('是否提交數據', '提示', {
            confirmButtonText: '確定',
            cancelButtonText: '取消',
            type: 'warning'
          })
            .then(() => {
              updateApp(this.appDetail)
                .then((res) => {
                  this.$message.success('應用信息更新成功');
                  this.commitLoading = false;
                })
                .catch((err) => {
                  errorHandle(err);
                  this.commitLoading = false;
                });
            })
            .catch(() => {
              this.commitLoading = false;
            });
        } else {
          return false;
        }
      });
    }
  }
};
</script>
<style lang="scss" scoped >
.app_detail {
  position: relative;
  padding-bottom: 20px;
  .save_btn {
    margin-left: 80px;
    
  }
  .el-select {
    width: 100%;
  }
}
.editorStyle{
  /deep/ .w-e-text-container>.w-e-scroll>div ol li{
    list-style: auto ;
  }
  /deep/ .w-e-text-container>.w-e-scroll>div ul li{
    list-style: disc ;
  }
  /deep/ .w-e-text-placeholder{
    top:7px;
  }
  
}
.barStyle{
  /deep/ .w-e-bar-item{
    padding:2.5px
  }
    /deep/ .w-e-bar-item > button >.title{
    border-left:0 !important;
  }
}
</style>
<style src="@wangeditor/editor/dist/css/style.css" >
.inputdesc {
  font-size: 12px;
  color: rgba(0, 0, 0, 0.45);
  transition: color 0.3s cubic-bezier(0.215, 0.61, 0.355, 1);
}
.app_detail img {
  width: auto;
}
.app_detail .ql-formats {
  line-height: 22px;
}
</style>

總結

到此這篇關於vue中wangEditor5編輯器的基本使用的文章就介紹到這瞭,更多相關vue wangEditor5的使用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: