不支持

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())
}

 

posted @ 2024-06-18 09:40  骑上我的小摩托  阅读(7)  评论(0编辑  收藏  举报