vue webform(wangEditor5在vue中的基本使用)
目录
一 、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>创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!