使用contenteditable=“true“改造div可编辑文本框

使用contenteditable="true"改造div可编辑文本框,并去除粘贴复制的格式问题

contenteditable=“true” 可编辑
contenteditable=“false” 禁止编辑
contenteditable=“plaintext-only” 可以让编辑区域只能键入纯文本 不建议使用兼容性不好

一:简单版

<template>
  <div :contenteditable="contenteditable" class="contentAble" :placeholder="placeholder" @focus="isLocked = true" @blur="isLocked = false" @input="changeDivText($event)" @paste="optimizePasteEvent" v-html="content" />
</template>

<script>
export default {
  props: {
    placeholder: {
      type: String,
      default: '请输入内容'
    },
    value: {
      type: String,
      default: ''
    },
    contenteditable: {
      type: String,
      default: 'true'
    }
  },
  data() {
    return {
      content: this.value,
      isLocked: false
    }
  },
  watch: {
    'value'() {
      if (!this.isLocked || !this.content) {
        this.content = this.value
      }
    }
  },
  methods: {
    // 监听粘贴内容到输入框事件,对内容进行处理
    optimizePasteEvent(e) {
      e.stopPropagation()
      e.preventDefault()
      let text = ''; const event = (e.originalEvent || e)
      if (event.clipboardData && event.clipboardData.getData) {
        text = event.clipboardData.getData('text/plain')
      } else if (window.clipboardData && window.clipboardData.getData) {
        text = window.clipboardData.getData('text')
      }

      if (document.queryCommandSupported('insertText')) {
        document.execCommand('insertText', false, text)
      } else {
        document.execCommand('paste', false, text)
      }
    },
    // 获取内容
    changeDivText(e) {
      this.$emit('input', e.target.innerHTML)
    }
  }
}
</script>

<style lang="scss" scoped>
.contentAble{
  border-radius: 10px;
  border: 1px solid #dcdfe6;
  padding: 20px;
  min-height: 200px;
  &:empty::before {
    content: attr(placeholder);
    color: #c0c4cf;
  }
}
</style>

使用例子

import CanableText from '@/components/CanableText'
<CanableText v-model="content" placeholder="请输入内容" />

二:升级版(可插入图片或者粘贴图片)

<template>
  <div>
    <div
      ref="editor"
      :contenteditable="contenteditable"
      class="contentAble"
      :class="{uploadImage:uploadImage}"
      :placeholder="placeholder"
      @focus="isLocked = true"
      @blur="isLocked = false"
      @input="changeDivText($event)"
      @paste.prevent="handlePaste"
      v-html="content"
    />
    //插入图片封装组件,代码太长,根据自己需求编写旧不展示了
    <EditorImage v-if="uploadImage" ref="EditorImage" @successCBK="imageSuccessCBK">
      <template v-slot:button>
        <el-button class="blueBorBtn insetImg" @click="insertImg">插入图片</el-button>
      </template>
    </EditorImage>
  </div>
</template>

<script>
import EditorImage from '@/components/Tinymce/components/EditorImage'
export default {
  components: { EditorImage },
  props: {
    placeholder: {
      type: String,
      default: '请输入内容'
    },
    value: {
      type: String,
      default: ''
    },
    contenteditable: {
      type: Boolean,
      default: true
    },
    imgShowWidth: { // 聊天输入框中粘贴的图片显示的宽度
      type: Number,
      default: 60
    },
    imgShowHeight: { // 聊天输入框中粘贴的图片显示的高度
      type: Number,
      default: 60
    },
    uploadImage: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      content: this.value,
      isLocked: false
    }
  },
  watch: {
    'value'() {
      if (!this.isLocked || !this.content) {
        this.content = this.value
      }
    }
  },
  methods: {
    async handlePaste(event) {
      const pasteResult = this.optimizePasteEvent(event)
      if (pasteResult) return
      await this.handlePasteImageFile(event.clipboardData)
    },
    // 监听粘贴文本到输入框事件,对内容进行处理
    optimizePasteEvent(e) {
      e.stopPropagation()
      e.preventDefault()
      let text = ''; const event = (e.originalEvent || e)
      if (event.clipboardData && event.clipboardData.getData) {
        text = event.clipboardData.getData('text/plain')
      } else if (window.clipboardData && window.clipboardData.getData) {
        text = window.clipboardData.getData('text')
      }

      if (document.queryCommandSupported('insertText')) {
        document.execCommand('insertText', false, text)
      } else {
        document.execCommand('paste', false, text)
      }
      if (text) return true
      return false
    },
    // 粘贴图片(可以粘贴图片路径显示图片在文本框中)
    async handlePasteImageFile(clipboardData) {
      const img = this.getPasteImageFile(clipboardData.files)
      if (!img) return
      //这段代码为上次图片接口结果得到的路径,根据自己需求编写
      //this.uploadFileList = [{
      //  raw: img,
      //  size: img.size,
      //  name: img.name,
      //  chunkList: [],
      //  status: this.FileStatus.wait,
      //  progressStatus: 'warning',
      //  uploadProgress: 0
      //}]
      //this.uploadFileList.length && this.submitUpload(async result => {
      //  if (!result) {
      //    return this.$message.error('图片上传失败,请重新上传')
      //  }
        const oImage = await this.getImageObject(result, this.imgShowWidth, this.imgShowHeight)
        this.cursorInsert(oImage)
        this.changeDivText()
     // })
    },
    getPasteImageFile(clipboardDataFiles) {
      if (!clipboardDataFiles.length) {
        // console.log('没有要粘贴的文件')
        return null
      }
      // 剪切版中选择的(用户第一个点的在尾)第一张图片
      const clipboardDataFileList = Array.from(clipboardDataFiles || [])
      let firstSelectedImage = null
      clipboardDataFileList.forEach(file => {
        if (!file.type.match(/image\//i)) {
          return
        }
        firstSelectedImage = file
      })
      /**
       * 这里的 firstSelectedFile 对象就是和 <input type="file" /> onchange 时 一样的 文件对象
       * */
      return firstSelectedImage
    },
    // 获取一个 image object
    getImageObject(uploadRes, showWidth, showHeight) {
      const oImage = new Image(showWidth, showHeight)
      oImage.src = uploadRes.url
      return oImage
    },
    // 光标处插入节点
    cursorInsert(node) {
      // 获取光标范围
      if (window.getSelection()) {
        this.focus()
      }
      const selObj = window.getSelection()
      const range = selObj.getRangeAt(0)
      // 光标处插入节点
      range.insertNode(node)
      // 取消insert node 后的选中状态,将光标恢复到 insert node 后面
      range.collapse(false)
    },
    // 输入框 焦点
    focus() {
      this.$refs.editor.focus()
    },
    // 插入图片
    insertImg() {
      this.$refs.EditorImage.show()
    },
    // 获取上传的图片路径
    imageSuccessCBK(arr) {
      arr.forEach(async res => {
        const oImage = await this.getImageObject(res, this.imgShowWidth, this.imgShowHeight)
        this.cursorInsert(oImage)
        this.changeDivText()
      })
    },
    /**
     * 删除所有标签属性,支持忽略标签
     * @param {*} str 字符串文本
     * @param {*} ignoreArr 要忽略的标签
     */
    deleteTagAttr(str, tagArr) {
      if (typeof str !== 'string') return
      const reg = new RegExp(`<(?:(${tagArr.join('|')}))(.*?)(?:>|\/>)`, 'gi')
      return str.replace(reg, mstr => {
        return mstr.replace(/(<[A-z]+)(.*)?(\/?>)/gi, '$1$3')
      })
    },
    // 获取内容,需求需要html外层添加div标签,没要求直接获取this.$refs.editor.innerHTML即可
    changeDivText() {
      const html = this.$refs.editor.innerHTML
      const newHtml = html.substring(1, 4) === 'div' ? html : this.deleteTagAttr(this.$refs.editor.outerHTML, ['div'])
      this.$emit('input', newHtml)
    }
  }
}
</script>

<style lang="scss" scoped>
.contentAble{
  border-radius: 10px;
  border: 1px solid #dcdfe6;
  padding: 20px;
  min-height: 200px;
  word-break: break-all;
  overflow: hidden;
  position: relative;
  &:empty::before {
    content: attr(placeholder);
    color: #c0c4cf;
  }
}
.uploadImage{
  padding: 20px 20px 70px 20px
}
.insetImg{
  position: absolute;
  right: 30px;
  bottom: 30px;
}
</style>

使用例子:

import CanableText from '@/components/CanableText'
<CanableText v-model="content" placeholder="请输入内容" :uploadImage="true" />

显示结果:
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值