前段儿时间有个需求,实现输入框既能输入文字又能输入图片,当时想到了微信的聊天输入框,于是经过查阅资料,使用div的contenteditable属性,将其值设为true,就可以实现类似输入框的功能,输入文字,而图片也可以通过html的innerHTML解析功能直接回显,详细代码如下:
<template>
<a-form-model ref="form" :model="form" :label-col="{ span: 5 }" :wrapper-col="{ span: 16 }">
<a-form-model-item prop="comment">
<div class="flex flexDiv">
<div ref="editBox" class="divInput" contenteditable="true" id="editBox"></div>
<img
@mouseenter.stop="handleMouseEnter"
@mouseleave.stop="handleMouseLeave"
:src="smile"
width="28"
class="smileContainer cursorhand"
/>
<div v-show="showTooltip" ref="popoverContent" @mouseenter="clearCloseTimeout" @mouseleave="handleMouseLeave">
<div class="triangle"></div>
<div
class="flex emojiStyle emojiContainer"
v-infinite-scroll.stop="handleInfiniteOnLoad"
:infinite-scroll-disabled="busy"
:infinite-scroll-distance="10"
>
<a-avatar
v-for="(v, i) in emojiList"
:key="i"
:src="v.emojiIcon"
shape="square"
class="avatar"
@click.stop="insertImg(i)"
/>
</div>
</div>
</div>
</a-form-model-item>
<div class="bottom-control">
<a-space>
<a-button type="primary" @click="submitForm"> 保存 </a-button>
</a-space>
</div>
</a-form-model>
</template>
<script>
import smile from '@/assets/images/smile.png'
import infiniteScroll from 'vue-infinite-scroll'
export default {
directives: { infiniteScroll },
data() {
return {
smile,
actived: -1,
emojiList: [],
form: {
comment: ''
},
busy: false,
showTooltip: false,
pageNum: 0
}
},
methods: {
handleMouseEnter() {
this.clearCloseTimeout()
this.showTooltip = true // 鼠标进入时显示弹出内容
},
handleMouseLeave(event) {
this.closeTimeout = setTimeout(() => {
this.showTooltip = false
}, 300) // 鼠标离开时延时300毫秒关闭
},
clearCloseTimeout() {
// 清除已设定的延时关闭,防止弹窗意外关闭
if (this.closeTimeout) {
clearTimeout(this.closeTimeout)
this.closeTimeout = null
}
},
handleInfiniteOnLoad() {
if (this.emojiList.length == this.total) return
this.pageNum++
this.getList()
},
// 获取图片数据
getList() {
// 这里替换成自己的接口请求
setTimeout((response) => {
this.emojiList = this.emojiList.concat(response.data.records)
this.total = response.data.total
})
},
// 插入按钮
insertImg(i) {
this.actived = i
this.$refs.editBox.focus()
const imgStr = `<img src="${this.emojiList[i].emojiIcon}" style="width:20px"/>`
document.execCommand('insertHTML', true, imgStr)
this.$nextTick(() => {
const innerHTML = this.$refs.editBox.innerHTML
this.$refs.editBox.innerHTML = innerHTML.replace('<br>', '')
if (innerHTML.indexOf('</span>') != -1) {
const divHtml =
innerHTML.substring(0, innerHTML.indexOf('<span')) +
innerHTML.substring(innerHTML.indexOf('</span>') + 7, innerHTML.length)
this.$refs.editBox.innerHTML = divHtml
}
setTimeout(() => {
this.keepCursorEnd(this.$refs.editBox)
}, 1)
})
},
keepCursorEnd(obj) {
// ie11 10 9 firefox safari
if (window.getSelection) {
// 解决firefox不获取焦点无法定位问题
obj.focus()
// 创建range
const range = window.getSelection()
// range 选择obj下所有子内容
range.selectAllChildren(obj)
// 光标移至最后
range.collapseToEnd()
} else if (document.selection) {
// ie10 9 8 7 6 5
// 创建选择对象
const range = document.selection.createRange()
// range定位到obj
range.moveToElementText(obj)
// 光标移至最后
range.collapse(false)
range.select()
}
},
filterImg(val) {
// 处理评论消息中的表情
const reg = /\[\!(http.*?)\!\]/g // 带http的捕获
const reg2 = /\[\!(.*?)\!\]/g // 不带http的捕获
const str = val.replace(reg, `<img src="$1" style="width:20px">`)
return str.replace(reg2, `<img src="$1" style="width:20px">`)
},
filterStr(str) {
// 正则表达式匹配文本和 <img> 标签
const regex = /(<img[^>]*src="([^"]+)"[^>]*>|[^<]+)/g
let match
let result = ''
while ((match = regex.exec(str)) !== null) {
if (match[1].startsWith('<img')) {
// 如果是 <img> 标签,提取 src 属性值
result += `[!${match[2]}!]`
} else {
// 否则是纯文本部分
result += match[0]
}
}
return result
},
/** 提交按钮 */
submitForm: function () {
this.form.comment = this.filterStr(this.$refs.editBox.innerHTML)
console.log('输出最终的内容', this.form.comment)
}
}
}
</script>
<style lang="less" scoped>
.flexDiv {
position: relative;
}
.divInput {
flex: 1;
overflow: hidden;
outline: none;
border: 1px solid #d9d9d9;
padding-left: 11px;
padding-right: 36px;
border-radius: 2px;
}
.divInput:hover {
border-color: #40a9ff;
}
.divInput:focus {
border-color: #40a9ff;
border-right-width: 1px !important;
outline: 0;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.emojiStyle {
flex-wrap: wrap;
max-height: 200px;
overflow: auto;
.avatar {
margin: 0 10px 10px;
cursor: pointer;
}
.avatar:hover {
border: 2px solid red;
}
}
.emojiContainer {
position: absolute;
top: 51px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: 3;
padding: 14px 16px;
width: 93.5%;
right: 0;
background: #fff;
}
.smileContainer {
position: absolute;
right: 8px;
top: 6.5px;
}
.triangle {
right: 18px;
transform: translateX(-50%) rotate(45deg);
top: 48px;
border-top-color: #fff;
border-right-color: transparent;
border-bottom-color: transparent;
border-left-color: #fff;
box-shadow: -2px -2px 5px rgba(0, 0, 0, 0.06);
position: absolute;
display: block;
width: 8.48528137px;
height: 8.48528137px;
background: transparent;
border-style: solid;
border-width: 4px;
transform: rotate(45deg);
z-index: 4;
}
</style>
该功能还有些小缺陷,欢迎大佬们完善。