vuetify学习第7天之v-editor---自定义封装富文本编辑器
目录
文章目录
- 1、vue-quill-editor
- 2、自定义封装
- 3、常用属性和事件
- 4、应用案例
- ***后记*** :
内容
1、vue-quill-editor
Quill editor component for Vue.
基于 Quill、适用于 Vue 的富文本编辑器,支持服务端渲染和单页应用。
详情请参考github地址:https://github.com/surmon-china/vue-quill-editor#readme
2、自定义封装
- 优点:vueQuillEditor组件为轻量级富文本编辑器,可全局或者部分导入,实现丰富的图文混排。
- 缺点
- 图片上传:图片转化为base64编码,当图片较大时,占用很多空间
- 视频资源:只显示url地址。
根据以上考虑,我们基于vueQuillEditor封装自定义富文本编辑器,主要变更功能为图片上传,实现图片上传至给定url地址功能,同时添加字符统计功能。
- Editor.vue源代码2-1:
<template>
<div class="myEditor">
<quilleditor v-model="content" ref="myTextEditor" :options="editorOption" @change="onChange">
<div id="toolbar" slot="toolbar">
<select class="ql-size">
<option value="small"></option>
<!-- Note a missing, thus falsy value, is used to reset to default -->
<option selected></option>
<option value="large"></option>
<option value="huge"></option>
</select>
<!-- Add subscript and superscript buttons -->
<span class="ql-formats">
<button class="ql-script" value="sub"></button>
</span>
<span class="ql-formats">
<button class="ql-script" value="super"></button>
</span>
<span class="ql-formats">
<button type="button" class="ql-bold"></button>
</span>
<span class="ql-formats">
<button type="button" class="ql-italic"></button>
</span>
<span class="ql-formats">
<button type="button" class="ql-blockquote"></button>
</span>
<span class="ql-formats">
<button type="button" class="ql-list" value="ordered"></button>
</span>
<span class="ql-formats">
<button type="button" class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button type="button" class="ql-link"></button>
</span>
<span class="ql-formats">
<button type="button" @click="imgClick" style="outline:none">
<svg viewBox="0 0 18 18">
<rect class="ql-stroke" height="10" width="12" x="3" y="4" />
<circle class="ql-fill" cx="6" cy="7" r="1" />
<polyline class="ql-even ql-fill" points="5 12 5 11 7 9 8 10 11 7 13 9 13 12 5 12" />
</svg>
</button>
</span>
<span class="ql-formats">
<button type="button" class="ql-video"></button>
</span>
</div>
</quilleditor>
<div class="limit">
当前已输入
<span>{{currentChars}}</span> 个字符,您还可以输入
<span>{{remainChars}}</span> 个字符。
</div>
</div>
</template>
<script>
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import { quillEditor } from "vue-quill-editor";
export default {
name: "v-editor",
props: {
value: {
type: String
},
/*上传图片的地址*/
uploadUrl: {
type: String,
default: "/"
},
/*上传图片的file控件name*/
fileName: {
type: String,
default: "file"
},
maxUploadSize: {
type: Number,
default: 1024 * 1024 * 500
}
},
data() {
return {
content: "",
editorOption: {
modules: {
toolbar: "#toolbar"
}
}
};
},
methods: {
onChange() {
this.$emit("input", this.content);
},
/*选择上传图片切换*/
onFileChange(e) {
var fileInput = e.target;
if (fileInput.files.length === 0) {
return;
}
this.editor.focus();
if (fileInput.files[0].size > this.maxUploadSize) {
this.$message("图片不能大于500KB,图片尺寸过大");
}
var data = new FormData();
data.append(this.fileName, fileInput.files[0]);
this.axios.post(this.uploadUrl, data).then(res => {
if (res.data) {
this.editor.insertEmbed(
this.editor.getSelection().index,
"image",
res.data
);
}
});
},
/*点击上传图片按钮*/
imgClick() {
if (!this.uploadUrl) {
console.log("no editor uploadUrl");
return;
}
/*内存创建input file*/
var input = document.createElement("input");
input.type = "file";
input.name = this.fileName;
input.accept = "image/jpeg,image/png,image/jpg,image/gif";
input.onchange = this.onFileChange;
input.click();
}
},
computed: {
editor() {
return this.$refs.myTextEditor.quill;
},
// 当前已输入字符数
currentChars() {
return this.content.length;
},
// 剩余可输入字符数
remainChars() {
let num = 10000 - Number(this.content.length);
return num > 0 ? num : 0;
}
},
components: {
quilleditor: quillEditor
},
mounted() {
this.content = this.value;
this.currentChars;
this.remainChars;
},
watch: {
value(newVal) {
if (this.editor) {
if (newVal !== this.content) {
this.content = newVal;
}
}
}
}
};
</script>
<style lang='scss' scoped>
.quill-editor {
height: 400px;
}
.limit {
height: 30px;
border: 1px solid #ccc;
line-height: 30px;
text-align: right;
padding: 0;
margin: 0;
margin: 10px 0;
span {
color: #ee2a7b;
}
}
</style>
3、常用属性和事件
- 常用属性详解
名称 | 类型 | 默认值 | 说明 |
value | string | ‘’ | 编辑器双向绑定输入内容 |
options | object | {} | 编辑器工具类配置 |
uploadUrl | string | ‘’ | 图片上传地址 |
fileName | string | file | 图片上传后台获取变量名称 |
- 常用事件详解
名称 | 参数 | 说明 | |
change | $event | ||
blur | $event | ||
focus | $event | ||
ready | {$event, html, text } |
4、应用案例
- 需求:视频学习到了商品添加功能,涉及商品描述,需要图文混排,故选择富文本编辑器组件。
- 商品表单源代码4-1:
<template>
<v-stepper v-model="step">
<v-stepper-header>
<v-stepper-step :complete="step > 1" step="1">基本信息</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step :complete="step > 2" step="2">商品描述</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step :complete="step > 3" step="3">规格参数</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="4">SKU属性</v-stepper-step>
</v-stepper-header>
<v-stepper-items>
<v-stepper-content step="1">
<v-row align="center">
<v-col cols="6">
<v-cascader-single
v-model="goods.categories"
label="请选择商品分类"
url="/item/category/list"
required
showAllLevels
/>
</v-col>
<v-col cols="5">
<v-select
:items="brandList"
v-model="goods.brandID"
label="请选择品牌"
item-value="id"
item-text="name"
dense
>
<template v-slot:selection="{ item }">
<v-chip color="primary" close>{{ item.name }}</v-chip>
</template>
</v-select>
</v-col>
</v-row>
<v-text-field label="商品标题" v-model="goods.title" required counter="200"></v-text-field>
<v-text-field label="商品卖点" v-model="goods.subTitle" counter="200"></v-text-field>
<v-textarea
label="包装清单"
v-model="spuDetail.packingList"
counter="1000"
:rows="3"
no-resize
></v-textarea>
<v-textarea label="售后服务" v-model="goods.aterService" counter="1000" :rows="3" no-resize></v-textarea>
</v-stepper-content>
<v-stepper-content step="2">
<v-editor v-model="spuDetail.description" uploadUrl="/upload/image"></v-editor>
</v-stepper-content>
<v-stepper-content step="3">规格参数</v-stepper-content>
<v-stepper-content step="4">SKU属性</v-stepper-content>
</v-stepper-items>
</v-stepper>
</template>
<script>
export default {
name: "goods-form",
props: {
oldGoods: {
type: Object
},
isEdit: {
type: Boolean,
default: false
},
step: {
type: Number,
default: 1
}
},
data() {
return {
valid: false, // 表单校验结果标记
goods: {
categories: [], // 商品分类
brandID: "", // 商品品牌
title: "", // 商品标题
subTitle: "" // 商品卖点
},
spuDetail: {
packingList: "", // 包装清单
afterService: "", // 售后服务
descirption: "" // 商品描述
},
brandList: [], // 品牌列表
brandName: "" // 选中品牌名称
};
},
methods: {
submit() {
// 表单校验
if (this.$refs.myGoodsForm.validate()) {
// 定义一个请求参数对象,通过解构表达式来获取goods中的属性
const { categories, letter, ...params } = this.goods;
// 数据库中只要保存分类的id即可,因此我们对categories的值进行处理,只保留id,并转为字符串
params.cids = categories.map(c => c.id).join(",");
// 将字母都处理为大写
params.letter = letter.toUpperCase();
// 将数据提交到后台
// this.$http.post('/item/goods', this.$qs.stringify(params))
this.$http({
method: this.isEdit ? "put" : "post",
url: "/item/goods",
data: this.$qs.stringify(params)
})
.then(() => {
// 关闭窗口
this.$emit("close");
this.$message.success("保存成功!");
})
.catch(() => {
this.$message.error("保存失败!");
});
}
},
clear() {
// 重置表单
this.$refs.myGoodsForm.reset();
// 需要手动清空商品分类
this.categories = [];
}
},
watch: {
"goods.categories": {
// 监控分类的变化
handler() {
// 根据分类最后一级id查询品牌数据
this.axios
.get("/item/goods/cateBrand/" + this.goods.categories[2].id)
.then(resp => {
if (resp.status !== 200) {
// 报错提示
}
this.brandList = resp.data;
});
},
deep: true
}
}
};
</script>
<style scoped>
</style>
- 效果图示4-1: