使用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" />
显示结果: