@ -1 +0,0 @@ |
||||
Subproject commit e9d7f5bf799bf2d3d3df068f35290eba414237ad |
||||
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 323 KiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 246 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 258 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 312 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 273 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 347 KiB |
|
After Width: | Height: | Size: 424 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 990 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
@ -1,21 +1,167 @@ |
||||
<template> |
||||
<!-- 模板不变,只需: --> |
||||
<!-- $elder → elder --> |
||||
<!-- v-lazy="..." → 改用 DynamicImage 组件或 @error 处理 --> |
||||
<div class="smile-emoticon-box" :class="{ elder: !!elder }"> |
||||
<div class="emoticon-panel"> |
||||
<div |
||||
class="emoticon-pages" |
||||
:style="{ width: scrollWidth + 'px', transform: `translateX(-${scrollPosition}px)` }" |
||||
> |
||||
<div |
||||
class="emoticon-page" |
||||
v-for="(page, pageIndex) in faces" |
||||
:key="pageIndex" |
||||
:style="{ width: pageWidth + 'px' }" |
||||
> |
||||
<button |
||||
type="button" |
||||
class="emoticon-item" |
||||
v-for="(face, faceIndex) in page" |
||||
:key="`${pageIndex}-${faceIndex}-${face.id}`" |
||||
@click.stop="chooseFace(face)" |
||||
> |
||||
<img :src="publicPath + face.src" :alt="face.title" /> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="emoticon-dots" v-if="faces.length > 1"> |
||||
<span |
||||
v-for="(_, pageIndex) in faces" |
||||
:key="pageIndex" |
||||
class="dot" |
||||
:class="{ active: active === pageIndex }" |
||||
@click.stop="active = pageIndex" |
||||
></span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { ref, computed, inject } from 'vue' |
||||
import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue' |
||||
import faceJSON from '@/mock/face.json' |
||||
|
||||
const elder = inject('elder', false) |
||||
defineOptions({ name: 'EmoticonBox' }) |
||||
|
||||
const emit = defineEmits(['onFaceChoose']) |
||||
|
||||
const publicPath = ref(import.meta.env.BASE_URL) |
||||
const elder = inject('elder', ref(false)) |
||||
const publicPath = import.meta.env.BASE_URL || '/' |
||||
const active = ref(0) |
||||
const faceData = ref(faceJSON) |
||||
const faceIndex = ref(0) |
||||
const pageWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 375) |
||||
const pageSize = computed(() => (elder?.value ? 16 : 20)) |
||||
|
||||
const flatFaces = computed(() => { |
||||
const list = [] |
||||
;(faceJSON || []).forEach((group) => { |
||||
;(group.faceList || []).forEach((face) => { |
||||
if (face?.title && face?.src) list.push(face) |
||||
}) |
||||
}) |
||||
return list |
||||
}) |
||||
|
||||
const faces = computed(() => { |
||||
const size = pageSize.value || 20 |
||||
const pages = [] |
||||
const source = flatFaces.value |
||||
for (let i = 0; i < source.length; i += size) { |
||||
pages.push(source.slice(i, i + size)) |
||||
} |
||||
return pages.length ? pages : [[]] |
||||
}) |
||||
|
||||
const scrollWidth = computed(() => faces.value.length * pageWidth.value) |
||||
const scrollPosition = computed(() => active.value * pageWidth.value) |
||||
|
||||
function chooseFace(face) { |
||||
if (!face?.title) return |
||||
emit('onFaceChoose', `[${face.title}]`) |
||||
} |
||||
|
||||
function updatePageWidth() { |
||||
pageWidth.value = window.innerWidth || document.documentElement.clientWidth || 375 |
||||
if (active.value > faces.value.length - 1) active.value = 0 |
||||
} |
||||
|
||||
onMounted(() => { |
||||
updatePageWidth() |
||||
window.addEventListener('resize', updatePageWidth) |
||||
}) |
||||
|
||||
onBeforeUnmount(() => { |
||||
window.removeEventListener('resize', updatePageWidth) |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.smile-emoticon-box { |
||||
width: 100%; |
||||
background-color: #efefef; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.emoticon-panel { |
||||
padding: 12px 8px 8px; |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
.emoticon-pages { |
||||
display: flex; |
||||
transition: transform 0.25s ease; |
||||
} |
||||
|
||||
.emoticon-page { |
||||
flex-shrink: 0; |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
align-content: flex-start; |
||||
padding: 0 4px; |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
.emoticon-item { |
||||
width: 20%; |
||||
height: 48px; |
||||
border: 0; |
||||
background: transparent; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
padding: 0; |
||||
} |
||||
|
||||
.smile-emoticon-box.elder .emoticon-item { |
||||
width: 25%; |
||||
height: 56px; |
||||
} |
||||
|
||||
.emoticon-item img { |
||||
width: 28px; |
||||
height: 28px; |
||||
object-fit: contain; |
||||
} |
||||
|
||||
.smile-emoticon-box.elder .emoticon-item img { |
||||
width: 34px; |
||||
height: 34px; |
||||
} |
||||
|
||||
.emoticon-dots { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
gap: 6px; |
||||
padding-top: 6px; |
||||
} |
||||
|
||||
.dot { |
||||
width: 6px; |
||||
height: 6px; |
||||
border-radius: 50%; |
||||
background: #c7c7c7; |
||||
} |
||||
|
||||
const faces = computed(() => { /* ... 逻辑不变 ... */ }) |
||||
const scrollWidth = computed(() => faces.value.length * document.body.scrollWidth) |
||||
const scrollPosition = computed(() => active.value * document.body.scrollWidth) |
||||
</script> |
||||
.dot.active { |
||||
background: #025cea; |
||||
} |
||||
</style> |
||||
|
||||