目录
一、wangEditor5是什么
二、wangEditor5基本使用
(一)、安装
(二)、编译器引入
(三)、css及变量引入
三、wangEditor5工具栏配置
(一)、editor.getAllMenuKeys()
(二)、toolbarConfig中的excludeKeys
四、wangEditor5上传图片
五、wangEditor5的一些问题收集及解决
(一)、引入@wangEditor 编译报错 " Module parse failed: Unexpected token (12828:18)You may need an appropriate loader to handle this file type."
(二)、@wangeditor有序列表无序列表的样式消失问题。
一、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>