一、不带图片的富文本
1、安装
npm install @vueup/vue-quill@alpha --save
2、引入
main.js中全局引入
import { QuillEditor } from '@vueup/vue-quill' import '@vueup/vue-quill/dist/vue-quill.snow.css'; app.component('QuillEditor', QuillEditor)
3、使用组件
<quill-editor :disabled="true" theme="snow" content-type="html" enable :content="desc" />
4、添加样式
<style scoped>
/deep/ .el-form-item__content {
display: inline
}
</style>
5、效果如下:
二、带图片的富文本(图片存储为base64编码)
1、安装
npm install @vueup/vue-quill@alpha --save
npm install quill-image-extend-module --save
2、创建quillTool.js(用于添加超链接、视频)
在utils目录下创建quilTool.js文件,内容如下:
import { Quill } from '@vueup/vue-quill' // 源码中是import直接倒入,这里要用Quill.import引入 const BlockEmbed = Quill.import('blots/block/embed') const Link = Quill.import('formats/link') const ATTRIBUTES = ['height', 'width'] class quillTool extends BlockEmbed { static create(value) { const node = super.create(value) // 添加video标签所需的属性 node.setAttribute('controls', 'controls') node.setAttribute('type', 'video/mp4') node.setAttribute('src', this.sanitize(value)) return node } static formats(domNode) { return ATTRIBUTES.reduce((formats, attribute) => { if (domNode.hasAttribute(attribute)) { formats[attribute] = domNode.getAttribute(attribute) } return formats }, {}) } static sanitize(url) { return Link.sanitize(url) } static value(domNode) { return domNode.getAttribute('src') } format(name, value) { if (ATTRIBUTES.indexOf(name) > -1) { if (value) { this.domNode.setAttribute(name, value) } else { this.domNode.removeAttribute(name) } } else { super.format(name, value) } } html() { const { video } = this.value() return `<a href="${video}">${video}</a>` } } quillTool.blotName = 'video' // 这里不用改,楼主不用iframe,直接替换掉原来,如果需要也可以保留原来的,这里用个新的blot quillTool.className = 'ql-video' quillTool.tagName = 'video' // 用video标签替换iframe export default quillTool
3、页面代码
template
<el-form-item label="内容:" prop="content" > <QuillEditor ref="quill-editor" v-model:content="entity.content" :options="editorOption" contentType="html" style="margin-bottom: 10px" /> </el-form-item>
js
<script> // 工具栏配置 const toolbarOptions = [ ['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线 ["blockquote", "code-block"], // 引用 [{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表 [{ script: "sub" }, { script: "super" }], // 上标/下标 [{ indent: '-1' }, { indent: '+1' }], // 缩进 [{ direction: 'rtl' }], // 文本方向 [{ size: ['small', false, 'large', 'huge'] }], // 字体大小 [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题 [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色 [{ font: [] }], // 字体种类 [{ align: [] }], // 对齐方式 ['clean'], // 清除文本格式 ['link', 'image', 'video'] // 链接、图片、视频 ] import {QuillEditor, Quill } from '@vueup/vue-quill' import { container, ImageExtend, QuillWatch } from 'quill-image-extend-module' import quillTool from '@/utils/quillTool' Quill.register(quillTool, true) Quill.register('modules/ImageExtend', ImageExtend) import 'quill/dist/quill.core.css' import 'quill/dist/quill.snow.css' import 'quill/dist/quill.bubble.css' export default { name: 'EditDictDialog', components: { QuillEditor }, data() { return { entity:{ title: null, content: null }, submitLoading:false, dialogTableVisible:false, titleMap:{ 'create':"新增", 'update':"修改" }, titleStatus:'create', rules:{ title:[ { required: true,message:'请输入概况标题', trigger: 'blur' }, { min: 1, max: 100, message: '长度在100个字符以内', trigger: 'blur' } ] }, isDisabledName: false, editorOption: { theme: 'snow', placeholder: '请输入', modules: { // 处理点击工具栏图片按钮,上传图片base64位转换成服务器图片url ImageExtend: { loading: true, // 可选参数 是否显示上传进度和提示语 name: 'file_name', // 参数名 action: '', // 服务器地址,如果为空则采用base64插入图片 headers: xhr => { // 设置请求头参数(选填) xhr.setRequestHeader('Content-Type', 'multipart/form-data') }, response: res => { console.log(res) return res.data.imgPath }, size: 8, // 可选参数 图片大小,图片不能超过8M sizeError: () => { this.$message.error('粘贴图片大小不能超过8MB!') } }, toolbar: { container: toolbarOptions, handlers: { image: function(value) { QuillWatch.emit(this.quill.id) }, link: function(value) { if (value) { var href = prompt('请输入链接地址:') this.quill.format('link', href) } else { this.quill.format('link', false) } }, video: function(value) { if (value) { var href = prompt('请输入视频链接:') this.quill.format('video', href) } else { this.quill.format('video', false) } } } } } } } }, methods: { openEditDialog(dialogStatus,titleStatus, row) { this.isDisabledName = false this.dialogTableVisible=dialogStatus; this.titleStatus=titleStatus; if(titleStatus==="create"){ this.cleanData(); } else if (titleStatus==="update") { // 更新不校验饮片名称是否存在 this.isDisabledName = true } if(row){ this.entity=Object.assign({},row); this.$nextTick(() => { this.$refs['quill-editor'].setHTML(this.entity.content) }) } this.$nextTick(() => { this.$refs["form"].clearValidate(); }) }, close(){ this.dialogTableVisible=false; this.cleanData(); this.titleStatus = 'create' this.$nextTick(() => { this.$refs['quill-editor'].setHTML('') this.$refs['form'].clearValidate() }) } } } </script>
style
<style scoped> /deep/ .el-form-item__content { display: inline } /deep/ .el-dialog__footer{ margin-top: 10px; } /deep/ .ql-container { height: 300px; line-height: normal; width: auto; } /deep/ span.ql-size { max-width: 80px !important; } /deep/ .ql-tooltip[data-mode="link"]::before { content: "请输入链接地址:"; } /deep/ .ql-tooltip.ql-editing a.ql-action::after { border-right: 0px; content: "保存"; padding-right: 0px; } /deep/ .ql-tooltip[data-mode="video"] { left: 0 !important; } /deep/ .ql-tooltip[data-mode="video"]::before { content: "请输入视频地址:"; } /deep/ .ql-picker.ql-size .ql-picker-label::before, .ql-picker.ql-size .ql-picker-item::before { content: "14px"; } /deep/ .ql-picker.ql-size .ql-picker-label[data-value="small"]::before, .ql-picker.ql-size .ql-picker-item[data-value="small"]::before { content: "10px"; } /deep/ .ql-picker.ql-size .ql-picker-label[data-value="large"]::before, .ql-picker.ql-size .ql-picker-item[data-value="large"]::before { content: "18px"; } /deep/ .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before, .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before { content: "32px"; } /deep/ .ql-picker.ql-header .ql-picker-label::before, .ql-picker.ql-header .ql-picker-item::before { content: "文本"; } /deep/ .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { content: "标题1"; } /deep/ .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { content: "标题2"; } /deep/ .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { content: "标题3"; } /deep/ .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { content: "标题4"; } /deep/ .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { content: "标题5"; } /deep/ .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { content: "标题6"; } /deep/ .ql-picker.ql-font .ql-picker-label::before, .ql-picker.ql-font .ql-picker-item::before { content: "标准字体"; } /deep/ .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before, .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before { content: "衬线字体"; } /deep/ .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before, .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before { content: "等宽字体"; } </style>
4、效果
添加图片
点击确定后保存到数据库中,该字段为longtext类型,图片以base64编码进行保存。
二、带图片的富文本(图片存储url路径)
由于图片存储为base64编码时,从后台获取图片的话,由于内容太多,导致微信开发者工具无法接收如此大的数据,故改为图片通过url存储。
enctype:规定了form表单在发送到服务器时候编码方式,它有如下的三个值。
application/x-www-form-urlencoded(表单):默认的编码方式。但是在用文本的传输和MP3等大型文件的时候,使用这种编码就显得 效率低下。
multipart/form-data(formData):指定传输数据为二进制类型,比如图片、mp3、文件。
text/plain:纯文体的传输。空格转换为 “+” 加号,但不对特殊字符编码。
注意:图片是以base64编码的方式存储还是以url的方式存储取决于上传图片是是否请求后台上传图片的接口,即如果action值为空则采用base64插入图片,此时需要指定请求头multipart/form-data
headers: xhr => { // 设置请求头参数(选填) xhr.setRequestHeader('Content-Type', 'multipart/form-data') },
如果action为后台的图片上传接口uri,如/api/fileUpload/uploadImage,即上传图片时要请求后台的接口,此时要注释掉请求头:
// headers: xhr => { // 设置请求头参数(选填) // xhr.setRequestHeader('Content-Type', 'multipart/form-data') // },
否则后台报错:the request was rejected because no multipart boundary was found
部分代码修改如下:
editorOption: { theme: 'snow', placeholder: '请输入', modules: { // 处理点击工具栏图片按钮,上传图片base64位转换成服务器图片url ImageExtend: { loading: true, // 可选参数 是否显示上传进度和提示语 name: 'file', // 参数名,与后台接收的参数名保持一致 action: '/api/fileUpload/uploadImage', // 服务器地址,如果为空则采用base64插入图片 // headers: xhr => { // 设置请求头参数(选填) // xhr.setRequestHeader('Content-Type', 'multipart/form-data') // }, response: res => { console.log(res) // return res.data.imgPath return 'http://192.168.10.114:8012/upload/' + res.data.imgPath }, size: 8, // 可选参数 图片大小,图片不能超过8M sizeError: () => { this.$message.error('粘贴图片大小不能超过8MB!') } }, toolbar: { container: toolbarOptions, handlers: { image: function(value) { QuillWatch.emit(this.quill.id) }, link: function(value) { if (value) { var href = prompt('请输入链接地址:') this.quill.format('link', href) } else { this.quill.format('link', false) } }, video: function(value) { if (value) { var href = prompt('请输入视频链接:') this.quill.format('video', href) } else { this.quill.format('video', false) } } } } } },
注意:配置http://192.168.10.114:8012/upload/是为了访问后台时映射本地电脑的图片。
配置addResourceHandler和addResourceLocations来访问本地的图片
import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class FilePathConfig implements WebMvcConfigurer { @Value("${system.file.uploadPath}") private String filePath; public FilePathConfig() { } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { this.filePath.replace("\\", "/"); this.filePath = this.filePath + "/"; registry.addResourceHandler("/upload/**").addResourceLocations("file:" + filePath); } }
配置文件
#文件上传存储的路径
system.file.uploadPath=d:/data/cmdptcp/fileUpload
上传图片时,后台接收的controller
@PostMapping("/uploadImage") @Log("图片上传") public Result uploadImage(@RequestParam("file") MultipartFile multipartFile) { return fileUploadService.uploadImage(multipartFile); }
注意:参数名file与前端保持一致,否则无法接收到请求。
三、更改插入图片大小
(1)、更改插入图片大小、 安装
npm install quill npm install quill-image-resize-module --save npm install quill-image-drop-module --save
(2)、在main.js中引入
// 富文本图片大小 import imageResize from 'quill-image-resize-module' // 调整大小组件。 import { ImageDrop } from 'quill-image-drop-module'; // 拖动加载图片组件。 Quill.register('modules/imageResize', imageResize ); Quill.register('modules/imageDrop', ImageDrop);
(3)、在vue.config.js文件中引入
const webpack = require('webpack'); module.exports = { configureWebpack: { // 在这里添加你的自定义Webpack配置 plugins: [ new webpack.ProvidePlugin({ 'window.Quill': 'quill/dist/quill.js', 'Quill': 'quill/dist/quill.js' }) ] } };
(4)、editorOption中添加如下内容
editorOption: { theme: 'snow', placeholder: '请输入', modules: { ImageExtend: { loading: true, name: 'file', action: imageUploadApi, // headers: xhr => { // xhr.setRequestHeader('Content-Type', 'multipart/form-data') // }, response: res => { console.info(res) return quillImageHost + '/upload/' + res.data.imgPath }, size: 8, sizeError: () => { this.$message.error('粘贴图片大小不能超过8MB!') } }, toolbar: { container: toolbarOptions, handlers: { image: function () { QuillWatch.emit(this.quill.id) }, link: function (value) { if (value) { let href = prompt('请输入链接地址:'); this.quill.format('link', href); } else { this.quill.format('link', false) } }, video: function (value) { if (value) { let href = prompt('请输入视频地址:') this.quill.format('video', href) } else { this.quill.format('video', false) } }, } }, // 如果使用图片可调整大小,加入如下配置 imageResize: { displayStyles: { backgroundColor: 'black', border: 'none', color: 'white' }, modules: ['Resize', 'DisplaySize', 'Toolbar'] } } },