vue-quill-editor 富文本 修改上传图片到服务器 输出网页中的元素是否可编辑 删除contenteditable="true" 直接就能用
第一步
npm install @vueup/vue-quill@alpha --save
npm install vue-quill-editor --save
第二步 main.js //全局引入
// 富文本编辑 import { QuillEditor } from '@vueup/vue-quill' // 新增:导入样式 import '@vueup/vue-quill/dist/vue-quill.snow.css' import '@vueup/vue-quill/dist/vue-quill.bubble.css' app.component('QuillEditor', QuillEditor) // 这里用的是 component
Html部分
<template> <div style="height: 400px"> <QuillEditor ref="myQuillEditor" theme="snow" v-model:content="content" :options="data.editorOption" contentType="html" @update:content="setValue()" @blur="onEditorBlur($event)" @focus="onEditorFocus($event)" @input="onEditorChange($event)" /> <!-- 使用自定义图片上传 --> <input type="file" hidden accept=".jpg,.png" ref="fileBtn" @change="handleUpload" /> </div> <div class="text" @click="onEditorclick">提交</div>
<!-- 显示输出的富文本 -->
<div v-html="lstecount"></div>
</template>
js部分:
<script setup>
//局部引入 import { QuillEditor } from '@vueup/vue-quill' import 'quill/dist/quill.core.css' import '@vueup/vue-quill/dist/vue-quill.snow.css' import { reactive, onMounted, ref, toRaw, watch } from 'vue' import axios from 'axios' import { ElMessage, ElLoading } from 'element-plus' const props = defineProps(['value']) const emit = defineEmits(['updateValue']) const content = ref('')//输出内容 const myQuillEditor = ref()//获取富文本框 watch( () => props.value, (val) => { console.log(toRaw(myQuillEditor.value)) toRaw(myQuillEditor.value).setHTML(val) }, { deep: true } ) const fileBtn = ref()//获取图片选择
//富文本框的头部 const data = reactive({ content: '', editorOption: { modules: { toolbar: [
['logo'], //新增logo ['bold', 'italic', 'underline', 'strike'],//加粗, 斜体, 下划线, 中线 [{ size: ['small', false, 'large', 'huge'] }],//字体大小 [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题 [{ font: [] }],//选择字体 [{ align: [] }],//对其方式 [{ list: 'ordered' }, { list: 'bullet' }],//列表 [{ indent: '-1' }, { indent: '+1' }],//缩进 [{ header: 1 }, { header: 2 }],//标题,1\2表示字体大小 ['image'],//上传图片 [{ direction: 'rtl' }],//文本方向 ['clean'], // 清除文本格式 [{ color: [] }, { background: [] }] //字体颜色,背景色 ] }, placeholder: '请输入内容...' } }) const imgHandler = (state) => { if (state) { fileBtn.value.click() //调用隐藏的图片上传 } } const setValue = () => { const text = toRaw(myQuillEditor.value).getHTML() emit('updateValue', text) } const handleUpload = (e) => { // 图片上传 const loading = ElLoading.service({ lock: true, text: '图片上传中请稍后...', background: 'rgba(255, 255, 255, 0.7)' }) const files = Array.prototype.slice.call(e.target.files) // console.log(files, 'files') if (!files) { return } const formdata = new FormData() formdata.append('file', files[0]) axios.post('https://上传地址', formdata).then((res) => { if (res.data.status == 200) { const quill = toRaw(myQuillEditor.value).getQuill()//获取富文本这个实例 const length = quill.getSelection().index //获取光标的位置 // 插入图片,res为服务器返回的图片链接地址 quill.insertEmbed(length, 'image', res.data.data) // 调整光标到最后 quill.setSelection(quill.getSelection().index + 1) loading.close()//上传成功关闭遮蔽罩 console.log(res.data.data)
fileBtn.value.value = '' //清空上传文件 }else if(res.data.status==500){
//上传失败
} }) }
//新增logo
const test = (state) => {
//在使用的页面中初始化按钮样式
let revoke = document.querySelector('.ql-logo') //获取元素
let Front = document.createElement('i') //创建元素
revoke.style.cssText = 'width:130px'
Front.style.cssText = 'font-weight:900;color:#007768;font-size:18px'
Front.innerHTML = 'logo' //logo名字
revoke.appendChild(Front) //追加到元素中
}
nextTick(() => {
test()
})
onMounted(() => { const quill = toRaw(myQuillEditor.value).getQuill() if (myQuillEditor.value) { quill.getModule('toolbar').addHandler('image', imgHandler) } if (content.value.length != '') { toRaw(myQuillEditor.value).setHTML(props.value) } }) // 失去焦点触发函数 let contentshop = ref() //获得到的富文本 const onEditorBlur = async (editor) => { if (content.value !== '') { contentshop.value = editor.value.innerHTML } }
//获得焦点是触发的函数 const onEditorFocus = async (editor) => { contentshop.value = editor.value.innerHTML } // 值发生变化时 const onEditorChange = async (editor) => { contentshop.value = editor.target.outerHTML }
let lstecount = ref()//储存最后提交的富文本内容
const onEditorclick = async () => { if (content.value == '') { ElMessage({ type: 'error', message: '输入不能为空' }) } else { console.log(contentshop.value) //实时出的内容 lstecount.value = contentshop.value.split('contenteditable="true"').join('') //删除输出的富文本中的contenteditable='true'(Html可编辑,删除后为不可编辑)
console.log(lstecount.value) // 提交
} }
</script>
Css:
<style scoped lang="scss"> :deep(.ql-editor) { min-height: 180px; } :deep(.ql-formats) { height: 21px; line-height: 21px; } .functional { height: 473px; width: 1120px; text-align: left; border: 1px #eee solid; background: #fff; margin: 40px auto; } :deep(.editor) { line-height: normal !important; width: 1120px; height: 430px; } //修改为中文 :deep(.ql-snow .ql-tooltip[data-mode='link']::before) { content: '请输入链接地址:'; } :deep(.ql-snow .ql-tooltip.ql-editing a.ql-action::after) { border-right: 0px; content: '保存'; padding-right: 0px; } :deep(.ql-snow .ql-tooltip[data-mode='video']::before) { content: '请输入视频地址:'; } :deep(.ql-snow .ql-picker.ql-size .ql-picker-label::before), :deep(.ql-snow .ql-picker.ql-size .ql-picker-item::before) { content: '14px'; } :deep(.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before), :deep(.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before) { content: '10px'; } :deep(.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before), :deep(.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before) { content: '18px'; } :deep(.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before), :deep(.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before) { content: '32px'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item::before) { content: '文本'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before) { content: '标题1'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before) { content: '标题2'; } // :deep() :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before) { content: '标题3'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before) { content: '标题4'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before) { content: '标题5'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before) { content: '标题6'; } :deep(.ql-snow .ql-picker.ql-font .ql-picker-label::before), :deep(.ql-snow .ql-picker.ql-font .ql-picker-item::before) { content: '标准字体'; } :deep(.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before), :deep(.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before) { content: '衬线字体'; } :deep(.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before), :deep(.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before) { content: '等宽字体'; } .SizeTiShi { float: right; font-size: 12px; color: #999; text-align: right; margin-right: 20px; margin-top: 50px; } .text { text-align: center; border: 1px solid #000; height: 60px; width: 120px; line-height: 60px; font-size: 24px; cursor: pointer; margin: 60px auto; } #QuillEditor { max-height: 300px; overflow: scroll; } </style>
完整代码:
<template> <div style="height: 400px"> <QuillEditor ref="myQuillEditor" theme="snow" v-model:content="content" :options="data.editorOption" contentType="html" @update:content="setValue()" @blur="onEditorBlur($event)" @focus="onEditorFocus($event)" @input="onEditorChange($event)" /> <!-- 使用自定义图片上传 --> <input type="file" hidden accept=".jpg,.png" ref="fileBtn" @change="handleUpload" /> </div> <div class="text" @click="onEditorclick">提交</div> <!-- 显示输出的富文本 --> <div v-html="lstecount"></div> </template> <script setup> //局部引入 import { QuillEditor } from '@vueup/vue-quill' import 'quill/dist/quill.core.css' import '@vueup/vue-quill/dist/vue-quill.snow.css' import { reactive, onMounted, ref, toRaw, watch } from 'vue' import axios from 'axios' import { ElMessage, ElLoading } from 'element-plus' const props = defineProps(['value']) const emit = defineEmits(['updateValue']) const content = ref('')//输出内容 const myQuillEditor = ref()//获取富文本框 watch( () => props.value, (val) => { console.log(toRaw(myQuillEditor.value)) toRaw(myQuillEditor.value).setHTML(val) }, { deep: true } ) const fileBtn = ref()//获取图片选择 //富文本框的头部 const data = reactive({ content: '', editorOption: { modules: { toolbar: [ ['bold', 'italic', 'underline', 'strike'],//加粗, 斜体, 下划线, 中线 [{ size: ['small', false, 'large', 'huge'] }],//字体大小 [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题 [{ font: [] }],//选择字体 [{ align: [] }],//对其方式 [{ list: 'ordered' }, { list: 'bullet' }],//列表 [{ indent: '-1' }, { indent: '+1' }],//缩进 [{ header: 1 }, { header: 2 }],//标题,1\2表示字体大小 ['image'],//上传图片 [{ direction: 'rtl' }],//文本方向 ['clean'], // 清除文本格式 [{ color: [] }, { background: [] }] //字体颜色,背景色 ] }, placeholder: '请输入内容...' } }) const imgHandler = (state) => { if (state) { fileBtn.value.click() //调用隐藏的图片上传 } } const setValue = () => { const text = toRaw(myQuillEditor.value).getHTML() emit('updateValue', text) } const handleUpload = (e) => { // 图片上传 const loading = ElLoading.service({ lock: true, text: '图片上传中请稍后...', background: 'rgba(255, 255, 255, 0.7)' }) const files = Array.prototype.slice.call(e.target.files) // console.log(files, 'files') if (!files) { return } const formdata = new FormData() formdata.append('file', files[0]) axios.post('https://上传地址', formdata).then((res) => { if (res.data.status == 200) { const quill = toRaw(myQuillEditor.value).getQuill()//获取富文本这个实例 const length = quill.getSelection().index //获取光标的位置 // 插入图片,res为服务器返回的图片链接地址 quill.insertEmbed(length, 'image', res.data.data) // 调整光标到最后 quill.setSelection(quill.getSelection().index + 1) loading.close()//上传成功关闭遮蔽罩 console.log(res.data.data) fileBtn.value.value = '' //清空上传文件 }else if(res.data.status==500){ //上传失败 } }) } onMounted(() => { const quill = toRaw(myQuillEditor.value).getQuill() if (myQuillEditor.value) { quill.getModule('toolbar').addHandler('image', imgHandler) } if (content.value.length != '') { toRaw(myQuillEditor.value).setHTML(props.value) } }) // 失去焦点触发函数 let contentshop = ref() //获得到的富文本 const onEditorBlur = async (editor) => { if (content.value !== '') { contentshop.value = editor.value.innerHTML } } //获得焦点是触发的函数 const onEditorFocus = async (editor) => { contentshop.value = editor.value.innerHTML } // 值发生变化时 const onEditorChange = async (editor) => { contentshop.value = editor.target.outerHTML } let lstecount = ref()//储存最后提交的富文本内容 const onEditorclick = async () => { if (content.value == '') { ElMessage({ type: 'error', message: '输入不能为空' }) } else { console.log(contentshop.value) //实时出的内容 lstecount.value = contentshop.value.split('contenteditable="true"').join('') //删除输出的富文本中的contenteditable='true'(Html可编辑,删除后为不可编辑) console.log(lstecount.value) // 提交 } } </script> <style scoped lang="scss"> :deep(.ql-editor) { min-height: 180px; } :deep(.ql-formats) { height: 21px; line-height: 21px; } .functional { height: 473px; width: 1120px; text-align: left; border: 1px #eee solid; background: #fff; margin: 40px auto; } :deep(.editor) { line-height: normal !important; width: 1120px; height: 430px; } //修改为中文 :deep(.ql-snow .ql-tooltip[data-mode='link']::before) { content: '请输入链接地址:'; } :deep(.ql-snow .ql-tooltip.ql-editing a.ql-action::after) { border-right: 0px; content: '保存'; padding-right: 0px; } :deep(.ql-snow .ql-tooltip[data-mode='video']::before) { content: '请输入视频地址:'; } :deep(.ql-snow .ql-picker.ql-size .ql-picker-label::before), :deep(.ql-snow .ql-picker.ql-size .ql-picker-item::before) { content: '14px'; } :deep(.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before), :deep(.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before) { content: '10px'; } :deep(.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before), :deep(.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before) { content: '18px'; } :deep(.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before), :deep(.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before) { content: '32px'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item::before) { content: '文本'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before) { content: '标题1'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before) { content: '标题2'; } // :deep() :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before) { content: '标题3'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before) { content: '标题4'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before) { content: '标题5'; } :deep(.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before), :deep(.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before) { content: '标题6'; } :deep(.ql-snow .ql-picker.ql-font .ql-picker-label::before), :deep(.ql-snow .ql-picker.ql-font .ql-picker-item::before) { content: '标准字体'; } :deep(.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before), :deep(.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before) { content: '衬线字体'; } :deep(.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before), :deep(.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before) { content: '等宽字体'; } .SizeTiShi { float: right; font-size: 12px; color: #999; text-align: right; margin-right: 20px; margin-top: 50px; } .text { text-align: center; border: 1px solid #000; height: 60px; width: 120px; line-height: 60px; font-size: 24px; cursor: pointer; margin: 60px auto; } #QuillEditor { max-height: 300px; overflow: scroll; } </style>
示例图:
补充: 清空编辑框中的数据的函数
const cleartext = async () => { const quill = toRaw(myQuillEditor.value).getQuill() quill.deleteText(0, quill.getLength()) }