可编辑div写聊天框
效果图
写了一个简单的demo
index.vue
<template>
<div class="hello">
<div class="left">
<div class="replyBox">
<emotion @emotion="handleEmotion" :height="200"></emotion>
<div-editable
class="messageC"
v-model="messageCon"
ref="msgc"
></div-editable>
</div>
<el-button
type="primary"
size="mini"
class="send"
:disabled="disabled"
@click="sendMessage"
>发送</el-button
>
</div>
<div class="right">
<div v-for="(item, index) in content" :key="index" v-html="item"></div>
</div>
</div>
</template>
<script>
import DivEditable from "@/components/DivEditable/DivEditable";
//引入表情(这里是整个表情框,不太方便给地址,你们就自己解决下吧)
import Emotion from "@/components/Emotion/index";
export default {
name: "index",
components: {
Emotion,
DivEditable,
},
data() {
return {
messageCon: "", //客服回复内容
content: [], //发送的消息
disabled: true,
};
},
watch: {
messageCon() {
if (!this.messageCon.trim()) {
this.disabled = true;
} else {
this.disabled = false;
}
},
},
methods: {
//发送消息内容change
sendMessage() {
this.content.push(this.messageCon);
this.messageCon = "";
},
handleEmotion(i) {
this.emotionNum = i;
this.$refs.msgc.handleEmotion(i);
},
},
};
</script>
<style scoped>
.hello {
display: flex;
justify-content: center;
}
.left {
position: relative;
width: 500px;
}
.replyBox {
padding: 15px;
box-sizing: border-box;
width: 500px;
height: 200px;
border: 1px solid #ccc;
margin-top: 260px;
}
.send {
margin-top: 20px;
float: right;
}
.right {
margin-left: 30px;
padding: 15px;
box-sizing: border-box;
width: 500px;
height: 800px;
border: 1px solid #ccc;
overflow-y: auto;
}
.right div {
white-space: pre-line;
margin-bottom: 15px;
background: rgba(150, 150, 150, 0.1);
padding: 10px;
box-sizing: border-box;
}
</style>
DivEditable.vue
<template>
<div
class="div-editable"
contenteditable="plaintext-only"
v-html="innerText"
@input="changeText"
@focus="focusFunc"
@blur="blurFunc"
ref="editDiv"
></div>
</template>
<script>
export default {
name: 'DivEditable',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
innerText: this.value,
isChange: true
};
},
watch: {
value(e) {
if (this.isChange) {
this.innerText = this.value;
}
if (!this.value) {
this.$el.innerHTML = '';
}
}
},
mounted() {
this.$refs.editDiv.focus();
},
methods: {
changeText(e) {
this.$emit('input', this.$el.innerHTML.replace(/<div><br><\/div>/g, '<br/>'));
},
blurFunc(val) {
this.isChange = true;
this.$emit('blurFunc');
},
focusFunc() {
this.$refs.editDiv.focus();
this.isChange = false;
this.saveRange();
},
saveRange() {
var selection = window.getSelection ? window.getSelection() : document.selection;
if (!selection.rangeCount) return;
var range = selection.createRange ? selection.createRange() : selection.getRangeAt(0);
window._range = range;
},
//获取表情
handleEmotion(i) {
this.$refs.editDiv.focus();
var img = `<img src="https://.../${i}.gif" align="top">`;//这里接的是表情的img(不方便给地址,你们自己解决下啊)
var selection,
range = window._range;
if (!window.getSelection) {
range.pasteHTML(img);
range.collapse(false);
range.select();
} else {
selection = window.getSelection ? window.getSelection() : document.selection;
range.collapse(false);
var hasR = range.createContextualFragment(img);
var hasR_lastChild = hasR.lastChild;
while (
hasR_lastChild &&
hasR_lastChild.nodeName.toLowerCase() == 'br' &&
hasR_lastChild.previousSibling &&
hasR_lastChild.previousSibling.nodeName.toLowerCase() == 'br'
) {
var e = hasR_lastChild;
hasR_lastChild = hasR_lastChild.previousSibling;
hasR.removeChild(e);
}
range.insertNode(hasR);
if (hasR_lastChild) {
range.setEndAfter(hasR_lastChild);
range.setStartAfter(hasR_lastChild);
}
selection.removeAllRanges();
selection.addRange(range);
}
this.changeText();
}
}
};
</script>
<style>
.div-editable {
width: 100%;
height: 100%;
overflow-y: auto;
word-break: break-all;
outline: none;
user-select: text;
white-space: pre-wrap;
text-align: left;
}
.div-editable[contenteditable='true'] {
user-modify: read-write-plaintext-only;
}
.div-editable[contenteditable='true']:empty:before {
content: attr(placeholder);
display: block;
color: #ccc;
}
</style>