效果图

文件图

直接贴代码
emotion.vue
<template>
<div class="emotion-container beauty-scroll-livechat">
<div class="emotion-btn" @click="toggleEmotionShow">
<span class="iconfont icon-biaoqing1"></span>
</div>
<div
class="emotion-box"
v-if="initEmotionImg"
v-show="emotionShow"
:style="{ width: `${width}px`, height: `${height}px` }"
>
<div class="every-emotion-line" v-for="(line, index) in emotionConfigList" :key="index">
<div
class="every-emotion-item"
v-for="(itm, idx) in line"
:key="index * rowCount + idx"
@click="selectEmotion(itm)"
>
<img :src="transEmotionToImg(itm)" />
</div>
</div>
</div>
</div>
</template>
<script>
import { nextTick, reactive, toRefs } from 'vue';
import { emotionList, emotionConfigList } from './emotionData';
import { insertEmotion } from './emotionMethod';
export default {
name: 'Emotion',
props: {
value: {
type: [Number, String],
required: true,
default: '',
},
elem: {
required: true,
default: null,
},
width: {
type: Number,
default: 450,
},
height: {
type: Number,
default: 200,
},
},
setup(props, { emit }) {
const state = reactive({
emotionShow: false,
initEmotionImg: false,
rowCount: emotionConfigList[0].length,
});
function transEmotionToImg(info) {
return `https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/${emotionList.indexOf(info)}.gif`;
}
function selectEmotion(name) {
let res = insertEmotion(props.elem, props.value, `#${name};`);
emit('update:value', res);
toggleEmotionShow();
}
function toggleEmotionShow() {
state.emotionShow = !state.emotionShow;
if (!state.emotionShow) {
props.elem.focus();
}
if (!state.initEmotionImg) {
state.initEmotionImg = true;
}
}
return {
emotionConfigList,
selectEmotion,
toggleEmotionShow,
transEmotionToImg,
...toRefs(state),
};
},
};
</script>
<style scoped lang="less">
.emotion-container {
position: relative;
z-index: 1400;
.emotion-btn {
display: inline-block;
color: #333;
user-select: none;
cursor: pointer;
transition: all 0.2s;
.iconfont {
font-size: 26px;
line-height: normal;
}
&:hover {
color: @tc-main;
}
}
.emotion-box {
position: absolute;
top: 40px;
left: 0;
padding: 10px 10px;
box-sizing: border-box;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.95);
box-shadow: 0 0 4px rgba(@tc-main-rgb, 0.4);
overflow-x: hidden;
overflow-y: auto;
user-select: none;
.every-emotion-line {
display: grid;
grid-template-columns: repeat(8, 1fr);
.every-emotion-item {
padding: 6px 0;
text-align: center;
line-height: 0;
cursor: pointer;
}
}
}
}
</style>
emotionMethod.js
import { nextTick } from 'vue';
import { emotionList, emotionConfigList } from './emotionData';
function matchNameToImg(value) {
let word = value.replace(/\#|\;/gi, '');
let index = emotionList.indexOf(word);
return `<img src="https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/${index}.gif" style="vertical-align:bottom" />`;
}
function transEmotion(value) {
return value.replace(/\#[\u4E00-\u9FA5]{1,3}\;/gi, matchNameToImg);
}
function getCursorPosition(elem) {
let caretPos = 0;
if (document.selection) {
elem.focus();
let sel = document.selection.createRange();
sel.moveStart('character', -elem.value.length);
caretPos = sel.text.length;
} else if (elem.selectionStart || elem.selectionStart === 0) {
caretPos = elem.selectionStart;
}
return caretPos;
}
function setCursorPosition(elem, posi) {
if (elem.setSelectionRange) {
elem.focus();
elem.setSelectionRange(posi, posi);
} else if (elem.createTextRange) {
var range = elem.createTextRange();
range.collapse(true);
range.moveEnd('character', posi);
range.moveStart('character', posi);
range.select();
}
}
function insertEmotion(elem, value, emotion) {
let posi = getCursorPosition(elem);
let resValue = '';
if (posi >= 0) {
let startSection = value.slice(0, posi);
let endSection = value.slice(posi);
resValue = `${startSection}${emotion}${endSection}`;
nextTick(() => {
let timer = setTimeout(() => {
clearTimeout(timer);
setCursorPosition(elem, posi + emotion.length);
}, 10);
});
} else {
resValue = `${value}${emotion}`;
}
elem.focus();
return resValue;
}
export { transEmotion, insertEmotion };
emotionData.js
export const emotionList = [
'微笑',
'撇嘴',
'色',
'发呆',
'得意',
'流泪',
'害羞',
'闭嘴',
'睡',
'大哭',
'尴尬',
'发怒',
'调皮',
'呲牙',
'惊讶',
'难过',
'酷',
'冷汗',
'抓狂',
'吐',
'偷笑',
'可爱',
'白眼',
'傲慢',
'饥饿',
'困',
'惊恐',
'流汗',
'憨笑',
'大兵',
'奋斗',
'咒骂',
'疑问',
'嘘',
'晕',
'折磨',
'衰',
'骷髅',
'敲打',
'再见',
'擦汗',
'抠鼻',
'鼓掌',
'糗大了',
'坏笑',
'左哼哼',
'右哼哼',
'哈欠',
'鄙视',
'委屈',
'快哭了',
'阴险',
'亲亲',
'吓',
'可怜',
'菜刀',
'西瓜',
'啤酒',
'篮球',
'乒乓',
'咖啡',
'饭',
'猪头',
'玫瑰',
'凋谢',
'示爱',
'爱心',
'心碎',
'蛋糕',
'闪电',
'炸弹',
'刀',
'足球',
'瓢虫',
'便便',
'月亮',
'太阳',
'礼物',
'拥抱',
'强',
'弱',
'握手',
'胜利',
'抱拳',
'勾引',
'拳头',
'差劲',
'爱你',
'NO',
'OK',
'爱情',
'飞吻',
'跳跳',
'发抖',
'怄火',
'转圈',
'磕头',
'回头',
'跳绳',
'挥手',
'激动',
'街舞',
'献吻',
'左太极',
'右太极',
];
export const emotionConfigList = [
['微笑', '撇嘴', '色', '发呆', '得意', '流泪', '害羞', '闭嘴'],
['睡', '大哭', '尴尬', '发怒', '调皮', '呲牙', '惊讶', '难过'],
['酷', '冷汗', '抓狂', '吐', '偷笑', '可爱', '白眼', '傲慢'],
['饥饿', '困', '惊恐', '流汗', '憨笑', '大兵', '奋斗', '咒骂'],
['疑问', '嘘', '晕', '折磨', '衰', '骷髅', '敲打', '再见'],
['擦汗', '抠鼻', '鼓掌', '糗大了', '坏笑', '左哼哼', '右哼哼', '哈欠'],
['鄙视', '委屈', '快哭了', '阴险', '亲亲', '吓', '可怜', '菜刀'],
['西瓜', '啤酒', '篮球', '乒乓', '咖啡', '饭', '猪头', '玫瑰'],
['凋谢', '示爱', '爱心', '心碎', '蛋糕', '闪电', '炸弹', '刀'],
['足球', '瓢虫', '便便', '月亮', '太阳', '礼物', '拥抱', '强'],
['弱', '握手', '胜利', '抱拳', '勾引', '拳头', '差劲', '爱你'],
['NO', 'OK', '爱情', '飞吻', '跳跳', '发抖', '怄火', '转圈'],
['磕头', '回头', '跳绳', '挥手', '激动', '街舞', '左太极', '右太极'],
];
如何调用
<div v-html="transEmotion(commentValue)"></div> //transEmotion方法用于渲染带有表情标识的文本
<div class="comment-add">
<textarea ref="textareaElem" v-model="commentValue" />
<emotion v-model:value="commentValue" :elem="textareaElem"></emotion> //组件调用
</div>
<script>
import { transEmotion } from '@/components/regional/emotion/emotionMethod';
import Emotion from '@/components/regional/emotion/emotion';
export default {
components: {
Emotion,
},
setup() {
const state = reactive({
commentValue: '',
textareaElem: null,
});
return {
transEmotion,
...toRefs(state),
}
}
}
</script>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
2022-12-28 uniapp iOS微信分享要求分享图必须小于20k限制的解决