✨ feat(im): 优化 EmojiPicker.vue 组件
parent
20c6631e7a
commit
43771b0f47
|
|
@ -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>
|
||||||
|
|
||||||
Loading…
Reference in New Issue