feat(im): 优化 EmojiPicker.vue 组件

im
YunaiV 2026-04-26 19:49:44 +08:00
parent 20c6631e7a
commit 43771b0f47
1 changed files with 101 additions and 0 deletions

View File

@ -0,0 +1,101 @@
<template>
<!--
表情选择器
当前实现简化为
- 直接用 Unicode emoji插入到输入框即所见即所得
- 调用方通过 v-model:visible 控制显隐通过 @select 接收选中的 emoji 字符
- 定位由调用方决定通常是浮在表情按钮上方
-->
<div
v-if="visible"
ref="rootRef"
class="absolute z-100 w-80 p-2 rounded-md bg-[var(--el-bg-color)] shadow-[0_4px_16px_rgba(0,0,0,0.12)]"
@click.stop
>
<el-scrollbar max-height="240px">
<div class="grid grid-cols-10 gap-0.5">
<button
v-for="emoji in EMOJI_LIST"
:key="emoji"
class="p-1 text-xl leading-none bg-transparent border-none rounded cursor-pointer transition-colors hover:bg-[var(--el-fill-color)]"
type="button"
@click="handleSelect(emoji)"
>
{{ emoji }}
</button>
</div>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, useTemplateRef, watch } from 'vue'
defineOptions({ name: 'ImEmojiPicker' })
const props = defineProps<{
visible: boolean
}>()
const emit = defineEmits<{
'update:visible': [value: boolean]
select: [emoji: string]
}>()
const rootRef = useTemplateRef<HTMLDivElement>('rootRef')
/** 常用 emoji 列表Unicode 表情,所见即所得;不依赖图片资源) */
const EMOJI_LIST = [
'😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊',
'😋', '😎', '😍', '😘', '😗', '😙', '😚', '🙂', '🤗', '🤩',
'🤔', '🤨', '😐', '😑', '😶', '🙄', '😏', '😣', '😥', '😮',
'🤐', '😯', '😪', '😫', '😴', '😌', '😛', '😜', '😝', '🤤',
'😒', '😓', '😔', '😕', '🙃', '🤑', '😲', '☹️', '🙁', '😖',
'😞', '😟', '😤', '😢', '😭', '😦', '😧', '😨', '😩', '🤯',
'😬', '😰', '😱', '🥵', '🥶', '😳', '🤪', '😵', '😡', '😠',
'🤬', '😷', '🤒', '🤕', '🤢', '🤮', '🤧', '😇', '🤠', '🥳',
'👍', '👎', '👌', '✌️', '🤞', '🤟', '🤘', '🤙', '👈', '👉',
'👆', '👇', '✋', '🤚', '🖐', '🖖', '👋', '🤝', '🙏', '💪',
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '💔', '💕', '💖',
'🎉', '🎊', '🎁', '🎂', '🍰', '🌹', '🌷', '🌸', '🎵', '🎶',
'⭐', '🌟', '✨', '💫', '🔥', '💯', '✅', '❌', '⚠️', '❓'
]
function handleSelect(emoji: string) {
emit('select', emoji)
emit('update:visible', false)
}
/** 点击面板外部关闭:组件根节点已经 @click.stop所以面板内点击不会触发 */
function handleDocumentClick(e: MouseEvent) {
if (!props.visible || !rootRef.value) {
return
}
if (!rootRef.value.contains(e.target as Node)) {
emit('update:visible', false)
}
}
/** 仅在面板可见时监听,避免长期占用全局事件 */
watch(
() => props.visible,
(visible) => {
if (visible) {
document.addEventListener('click', handleDocumentClick)
} else {
document.removeEventListener('click', handleDocumentClick)
}
}
)
onMounted(() => {
if (props.visible) {
document.addEventListener('click', handleDocumentClick)
}
})
onUnmounted(() => {
document.removeEventListener('click', handleDocumentClick)
})
</script>