| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- <script setup lang="ts">
- import dayjs from 'dayjs'
- import { computed, ref, watch } from 'vue'
- import type { Conversation } from '@tencentcloud/lite-chat'
- import { useImPopup } from '~/composables/useImPopup'
- import TitleIcon from '~/assets/icons/tab-active.svg'
- import CloseListIcon from '~/assets/icons/im/close-list.svg'
- const { state, close } = useImPopup()
- const { ready, conversationList, conversationLoading, conversationError, getConversationList, bizUserInfoMap } = useChat()
- type ViewMode = 'list' | 'detail'
- const view = ref<ViewMode>('list')
- const activeConversationId = ref<string | null>(null)
- const list = computed(() => conversationList.value
- ? conversationList.value.filter((conv) => {
- return conv.userProfile?.userID !== '10000'
- })
- : [])
- const activeConversation = computed(() => {
- const id = activeConversationId.value
- if (!id) return null
- return list.value.find(c => c.conversationID === id) ?? null
- })
- const getConversationName = (conv: Conversation) => {
- const userNo = conv?.userProfile?.userID
- const biz = userNo ? bizUserInfoMap.value?.[userNo] : undefined
- return biz?.nickname || conv?.remark || conv?.userProfile?.nick || conv?.groupProfile?.name || conv?.conversationID || ''
- }
- const getConversationAvatar = (conv: Conversation) => {
- const userNo = conv?.userProfile?.userID
- const biz = userNo ? bizUserInfoMap.value?.[userNo] : undefined
- return biz?.avatar || conv?.userProfile?.avatar || conv?.groupProfile?.avatar || '/avatar.png'
- }
- const getConversationLastText = (conv: Conversation) => {
- const msg = conv?.lastMessage
- if (!msg) return ''
- const text = msg?.payload?.text
- if (typeof text === 'string' && text.trim()) return text
- const type = String(msg?.type || '').toLowerCase()
- if (type.includes('image')) return '[Image]'
- if (type.includes('video')) return '[Video]'
- if (type.includes('file')) return '[File]'
- if (type.includes('audio')) return '[Audio]'
- return '[Message]'
- }
- const getConversationTimeLabel = (conv: Conversation) => {
- const sec = Number(conv?.lastMessage?.lastTime)
- if (!Number.isFinite(sec) || sec <= 0) return ''
- const t = dayjs.unix(sec)
- if (dayjs().diff(t, 'second') < 60) return '现在'
- if (dayjs().isSame(t, 'day')) return t.format('HH:mm')
- return t.format('MM/DD')
- }
- const fetchConversations = async () => {
- if (!state.visible) return
- if (!ready.value) return
- await getConversationList()
- }
- const activeName = computed(() => activeConversation.value ? getConversationName(activeConversation.value) : '')
- const activeAvatar = computed(() => activeConversation.value ? getConversationAvatar(activeConversation.value) : '/avatar.png')
- const activeBizUserInfo = computed(() => {
- const userNo = activeConversation.value?.userProfile?.userID
- if (!userNo) return undefined
- return bizUserInfoMap.value?.[userNo]
- })
- const openConversationDetail = (conv: Conversation) => {
- activeConversationId.value = conv.conversationID
- view.value = 'detail'
- }
- const backToList = () => {
- view.value = 'list'
- activeConversationId.value = null
- }
- watch(
- () => [state.visible, ready.value] as const,
- ([visible, isReady]) => {
- if (visible && isReady) fetchConversations()
- },
- { immediate: true },
- )
- const handleUpdateShow = (value: boolean) => {
- if (!value) close()
- }
- watch(
- () => state.visible,
- (visible) => {
- if (!visible) {
- view.value = 'list'
- activeConversationId.value = null
- }
- },
- )
- </script>
- <template>
- <van-popup
- :show="state.visible"
- position="bottom"
- teleport="body"
- @update:show="handleUpdateShow"
- >
- <ImConversationDetail
- v-if="view === 'detail'"
- :conversation="activeConversation"
- :name="activeName"
- :avatar="activeAvatar"
- :biz-user-info="activeBizUserInfo"
- @back="backToList"
- @close="close"
- />
- <div
- v-else
- class="im-popup"
- >
- <div class="im-popup__bg" />
- <div class="im-popup__header">
- <div class="im-popup__title">
- <TitleIcon class="im-popup__title-icon" />
- <span class="im-popup__title-text">
- Message
- </span>
- </div>
- <button
- class="im-popup__close"
- type="button"
- @click="close"
- >
- <CloseListIcon />
- </button>
- </div>
- <div class="im-popup__content">
- <div
- v-if="!ready"
- class="im-popup__placeholder"
- >
- IM is not ready.
- </div>
- <div
- v-else-if="conversationLoading"
- class="im-popup__placeholder"
- >
- Loading...
- </div>
- <div
- v-else-if="conversationError"
- class="im-popup__placeholder"
- >
- {{ conversationError }}
- </div>
- <div
- v-else-if="list.length === 0"
- class="im-popup__placeholder"
- >
- <img
- src="~/assets/images/common/empty.png"
- alt="empty"
- >
- No conversations.
- </div>
- <div
- v-else
- class="im-popup__list"
- >
- <ImChatItem
- v-for="conv in list"
- :key="conv.conversationID"
- :avatar="getConversationAvatar(conv)"
- :name="getConversationName(conv)"
- :time="getConversationTimeLabel(conv)"
- :message="getConversationLastText(conv)"
- :unread="conv.unreadCount"
- @click="openConversationDetail(conv)"
- />
- </div>
- </div>
- </div>
- </van-popup>
- </template>
- <style scoped lang="scss">
- .im-popup {
- height: 100vh;
- padding: 16px 16px 20px;
- position: relative;
- display: flex;
- flex-direction: column;
- &__bg {
- @include size(100%, 173px);
- @include bg('~/assets/images/home/header-bg.png', 100% 173px);
- position: absolute;
- inset: 0;
- z-index: 0;
- }
- &__header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- position: relative;
- z-index: 1;
- }
- &__title {
- font-size: 24px;
- font-family: var(--font-title);
- font-weight: 600;
- color: #000;
- display: flex;
- align-items: center;
- justify-content: center;
- min-width: 0;
- &-icon {
- position: absolute;
- z-index: 0;
- }
- &-text {
- position: relative;
- z-index: 1;
- }
- }
- &__close {
- border: none;
- background: transparent;
- }
- &__content {
- display: flex;
- flex-direction: column;
- gap: 10px;
- position: relative;
- z-index: 1;
- flex: 1 1 auto;
- min-height: 0;
- overflow: auto;
- }
- &__list {
- margin-top: 10px;
- display: flex;
- flex-direction: column;
- gap: 14px;
- }
- &__placeholder {
- margin-top: 40px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 12px;
- font-size: 12px;
- color: rgba(0, 0, 0, 0.70);
- img {
- @include size(100px);
- }
- }
- }
- </style>
|