9.16下午更新 包含im
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div
|
||||
class="p2p-msg-receipt-wrapper"
|
||||
v-if="
|
||||
conversationType ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P &&
|
||||
p2pMsgReceiptVisible
|
||||
"
|
||||
>
|
||||
<div v-if="p2pMsgRotateDeg == 360" class="icon-read-wrapper">
|
||||
<Icon type="icon-read" :size="16"></Icon>
|
||||
</div>
|
||||
<div v-else class="sector">
|
||||
<span
|
||||
class="cover-1"
|
||||
:style="`transform: rotate(${p2pMsgRotateDeg}deg)`"
|
||||
></span>
|
||||
<span
|
||||
:class="p2pMsgRotateDeg >= 180 ? 'cover-2 cover-3' : 'cover-2'"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/** 会话列表已读未读组件 */
|
||||
|
||||
import { computed } from 'vue'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import { V2NIMConst } from '@/utils/im/nim'
|
||||
|
||||
import {
|
||||
V2NIMConversationForUI,
|
||||
V2NIMLocalConversationForUI,
|
||||
} from '@xkit-yx/im-store-v2/dist/types/types'
|
||||
|
||||
import { V2NIMConversationType } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMConversationService'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
conversation: V2NIMConversationForUI | V2NIMLocalConversationForUI
|
||||
}>(),
|
||||
{}
|
||||
)
|
||||
|
||||
/** 是否需要显示 p2p 消息、p2p会话列表消息已读未读,默认 false*/
|
||||
const p2pMsgReceiptVisible = uni.$UIKitStore.localOptions.p2pMsgReceiptVisible
|
||||
|
||||
/** 会话类型 */
|
||||
const conversationType =
|
||||
uni.$UIKitNIM.V2NIMConversationIdUtil.parseConversationType(
|
||||
props.conversation.conversationId
|
||||
) as unknown as V2NIMConversationType
|
||||
|
||||
const p2pMsgRotateDeg = computed(() => {
|
||||
return (props?.conversation?.msgReceiptTime || 0) >=
|
||||
(props?.conversation?.lastMessage?.messageRefer?.createTime || 0)
|
||||
? 360
|
||||
: 0
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.p2p-msg-receipt-wrapper {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
overflow: hidden;
|
||||
line-height: 18px;
|
||||
vertical-align: bottom;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.icon-read-wrapper {
|
||||
margin: 0px 3px 0px 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.sector {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 2px solid #4c84ff;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
margin: 0px 3px 0 0;
|
||||
|
||||
.cover-1,
|
||||
.cover-2 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.cover-1 {
|
||||
background-color: #4c84ff;
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
.cover-3 {
|
||||
right: 0;
|
||||
background-color: #4c84ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="
|
||||
props.lastMessage.lastMessageState ===
|
||||
V2NIMConst.V2NIMLastMessageState.V2NIM_MESSAGE_STATUS_REVOKE
|
||||
"
|
||||
>
|
||||
{{ t('recall') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION
|
||||
"
|
||||
>
|
||||
{{ t('conversationNotificationText') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.sendingState ===
|
||||
V2NIMConst.V2NIMMessageSendingState.V2NIM_MESSAGE_SENDING_STATE_FAILED
|
||||
"
|
||||
>
|
||||
{{ t('conversationSendFailText') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_FILE
|
||||
"
|
||||
>
|
||||
{{ translateMsg('fileMsgText') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE
|
||||
"
|
||||
>
|
||||
{{ translateMsg('imgMsgText') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM
|
||||
"
|
||||
>
|
||||
{{ props.lastMessage.text || translateMsg('customMsgText') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO
|
||||
"
|
||||
>
|
||||
{{ translateMsg('audioMsgText') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL
|
||||
"
|
||||
>
|
||||
{{ translateMsg('callMsgText') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_LOCATION
|
||||
"
|
||||
>
|
||||
{{ translateMsg('geoMsgText') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_ROBOT
|
||||
"
|
||||
>
|
||||
{{ translateMsg('robotMsgText') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TIPS
|
||||
"
|
||||
>
|
||||
{{ translateMsg('tipMsgText') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_VIDEO
|
||||
"
|
||||
>
|
||||
{{ translateMsg('videoMsgText') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
props.lastMessage.messageType ===
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TEXT
|
||||
"
|
||||
class="msg-conversation-text-wrap"
|
||||
>
|
||||
<template v-for="item in textArr" :key="item.key">
|
||||
<template v-if="item.type === 'text'">
|
||||
<span class="msg-conversation-text">{{ item.value }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'emoji'">
|
||||
<span
|
||||
:class="
|
||||
isWxApp
|
||||
? 'msg-conversation-text-emoji-wx'
|
||||
: 'msg-conversation-text-emoji'
|
||||
"
|
||||
>
|
||||
<Icon :type="EMOJI_ICON_MAP_CONFIG[item.value]" :size="16" />
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/** 会话列表Item 外漏消息组件 */
|
||||
import { computed } from 'vue'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import { V2NIMConst } from '@/utils/im/nim'
|
||||
import { t } from '@/utils/im/i18n'
|
||||
import { V2NIMLastMessage } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMConversationService'
|
||||
import { EMOJI_ICON_MAP_CONFIG, emojiRegExp } from '@/utils/im/emoji'
|
||||
import { isWxApp } from '@/utils/im/index'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
lastMessage: V2NIMLastMessage
|
||||
}>(),
|
||||
{}
|
||||
)
|
||||
|
||||
/** 筛选出文本和表情 */
|
||||
const parseTextWithEmoji = (text: string) => {
|
||||
if (!text) return []
|
||||
const matches: {
|
||||
type: 'emoji' | 'text'
|
||||
value: string
|
||||
index: number
|
||||
}[] = []
|
||||
let match
|
||||
const regexEmoji = emojiRegExp
|
||||
|
||||
while ((match = regexEmoji.exec(text)) !== null) {
|
||||
matches.push({
|
||||
type: 'emoji',
|
||||
value: match[0],
|
||||
index: match.index,
|
||||
})
|
||||
const fillText = ' '.repeat(match[0].length)
|
||||
text = text.replace(match[0], fillText)
|
||||
}
|
||||
|
||||
text = text.replace(regexEmoji, ' ')
|
||||
|
||||
if (text) {
|
||||
text
|
||||
.split(' ')
|
||||
.filter((item) => item.trim())
|
||||
.map((item) => {
|
||||
const index = text?.indexOf(item)
|
||||
matches.push({
|
||||
type: 'text',
|
||||
value: item,
|
||||
index,
|
||||
})
|
||||
const fillText = ' '.repeat(item.length)
|
||||
text = text.replace(item, fillText)
|
||||
})
|
||||
}
|
||||
|
||||
return matches
|
||||
.sort((a, b) => a.index - b.index)
|
||||
.map((item, index) => {
|
||||
return {
|
||||
...item,
|
||||
key: index + item.type,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 解析的消息数组 */
|
||||
const textArr = computed(() => {
|
||||
return parseTextWithEmoji(props.lastMessage.text as string)
|
||||
})
|
||||
|
||||
/** 消息映射 */
|
||||
const translateMsg = (key: string): string => {
|
||||
const text =
|
||||
{
|
||||
textMsgText: t('textMsgText'),
|
||||
customMsgText: t('customMsgText'),
|
||||
audioMsgText: t('audioMsgText'),
|
||||
videoMsgText: t('videoMsgText'),
|
||||
fileMsgText: t('fileMsgText'),
|
||||
callMsgText: t('callMsgText'),
|
||||
geoMsgText: t('geoMsgText'),
|
||||
imgMsgText: t('imgMsgText'),
|
||||
notiMsgText: t('notiMsgText'),
|
||||
robotMsgText: t('robotMsgText'),
|
||||
tipMsgText: t('tipMsgText'),
|
||||
unknowMsgText: t('unknowMsgText'),
|
||||
}[key] || ''
|
||||
return `[${text}]`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.msg-conversation-text {
|
||||
font-size: 13px !important;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
width: 100%;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.msg-conversation-text-wrap {
|
||||
width: 80%;
|
||||
line-height: 22px;
|
||||
height: 22px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.msg-conversation-text-emoji {
|
||||
display: inline-flex;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.msg-conversation-text-emoji-wx {
|
||||
display: inline-flex;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,496 @@
|
||||
<template>
|
||||
<view
|
||||
:class="[
|
||||
'conversation-item-container',
|
||||
{
|
||||
'show-action-list': showMoreActions,
|
||||
'stick-on-top': conversation.stickTop,
|
||||
},
|
||||
]"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchmove="handleTouchMove"
|
||||
@click="handleConversationItemClick()"
|
||||
>
|
||||
<view class="conversation-item-content">
|
||||
<view class="conversation-item-left">
|
||||
<!-- 会话Item未读数 -->
|
||||
<view class="unread" v-if="unread">
|
||||
<view class="dot" v-if="isMute"></view>
|
||||
<view class="badge" v-else>{{ unread }}</view>
|
||||
</view>
|
||||
<!-- 会话头像 -->
|
||||
<Avatar :account="to" :avatar="teamAvatar" />
|
||||
<!-- 用户在线离线状态 -->
|
||||
<view
|
||||
class="login-state-icon"
|
||||
v-if="
|
||||
loginStateVisible &&
|
||||
!isAiUser &&
|
||||
conversation.type ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P &&
|
||||
status
|
||||
"
|
||||
></view>
|
||||
<view
|
||||
class="unlogin-state-icon"
|
||||
v-if="
|
||||
loginStateVisible &&
|
||||
!isAiUser &&
|
||||
conversation.type ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P &&
|
||||
!status
|
||||
"
|
||||
></view>
|
||||
</view>
|
||||
<view class="conversation-item-right">
|
||||
<!-- 会话名称 -->
|
||||
<view class="conversation-item-top">
|
||||
<Appellation
|
||||
class="conversation-item-title"
|
||||
v-if="
|
||||
conversation.type ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P
|
||||
"
|
||||
:account="to"
|
||||
/>
|
||||
<span v-else class="conversation-item-title">
|
||||
{{ sessionName }}
|
||||
</span>
|
||||
<span class="conversation-item-time">{{ date }}</span>
|
||||
</view>
|
||||
<view class="conversation-item-desc">
|
||||
<!-- 是否有人@ -->
|
||||
<span v-if="beMentioned" class="beMentioned">
|
||||
{{ '[' + t('someoneText') + '@' + t('meText') + ']' }}
|
||||
</span>
|
||||
<!-- 会话最后一条消息是否已读 -->
|
||||
<!-- <ConversationItemIsRead
|
||||
v-if="showConversationUnread"
|
||||
:conversation="props.conversation"
|
||||
></ConversationItemIsRead> -->
|
||||
<!-- 会话最后一条消息外露 -->
|
||||
<span
|
||||
v-if="props.conversation.lastMessage"
|
||||
class="conversation-item-desc-content"
|
||||
>
|
||||
<LastMsgContent :lastMessage="props.conversation.lastMessage" />
|
||||
</span>
|
||||
<!-- 消息免打扰 -->
|
||||
<span class="conversation-item-desc-ait">
|
||||
<Icon
|
||||
v-if="isMute"
|
||||
iconClassName="conversation-item-desc-state"
|
||||
type="icon-xiaoximiandarao"
|
||||
color="#ccc"
|
||||
/>
|
||||
</span>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 消息右键操作列表 -->
|
||||
<view class="right-action-list">
|
||||
<view
|
||||
v-for="action in moreActions"
|
||||
:key="action.type"
|
||||
:class="['right-action-item', action.class]"
|
||||
@click="() => handleClick(action.type)"
|
||||
>
|
||||
{{ action.name }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/** 会话列表Item组件 */
|
||||
import Avatar from '@/components/Avatar.vue'
|
||||
import Appellation from '@/components/Appellation.vue'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import { computed, onUnmounted, withDefaults } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { t } from '@/utils/im/i18n'
|
||||
import { V2NIMConst } from '@/utils/im/nim'
|
||||
import {
|
||||
V2NIMConversationForUI,
|
||||
V2NIMLocalConversationForUI,
|
||||
} from '@xkit-yx/im-store-v2/dist/types/types'
|
||||
import ConversationItemIsRead from './conversation-item-isRead.vue'
|
||||
import LastMsgContent from './conversation-item-last-msg-content.vue'
|
||||
import { ref } from 'vue'
|
||||
import { autorun } from 'mobx'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
conversation: V2NIMConversationForUI | V2NIMLocalConversationForUI
|
||||
showMoreActions?: boolean
|
||||
}>(),
|
||||
{ showMoreActions: false }
|
||||
)
|
||||
|
||||
const emit = defineEmits(['click', 'delete', 'stickyToTop', 'leftSlide'])
|
||||
|
||||
/** 右滑操作列表 */
|
||||
const moreActions = computed(() => {
|
||||
return [
|
||||
{
|
||||
name: props.conversation.stickTop
|
||||
? t('deleteStickTopText')
|
||||
: t('addStickTopText'),
|
||||
class: 'action-top',
|
||||
type: 'action-top',
|
||||
},
|
||||
{
|
||||
name: t('deleteSessionText'),
|
||||
class: 'action-delete',
|
||||
type: 'action-delete',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
/** 对话方 */
|
||||
const to = computed(() => {
|
||||
const res = uni.$UIKitNIM.V2NIMConversationIdUtil.parseConversationTargetId(
|
||||
props.conversation.conversationId
|
||||
)
|
||||
return res
|
||||
})
|
||||
|
||||
/** 全局配置的是否需要展示在线离线状态 */
|
||||
const loginStateVisible = uni.$UIKitStore.localOptions.loginStateVisible
|
||||
|
||||
/** 当前会话方在线离线状态 */
|
||||
const status = ref<boolean>(false)
|
||||
|
||||
/** 右滑操作点击 */
|
||||
const handleClick = (type: string) => {
|
||||
if (type === 'action-top') {
|
||||
emit('stickyToTop', props.conversation)
|
||||
} else {
|
||||
emit('delete', props.conversation)
|
||||
}
|
||||
}
|
||||
|
||||
/** 群头像 */
|
||||
const teamAvatar = computed(() => {
|
||||
if (
|
||||
props.conversation.type ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM
|
||||
) {
|
||||
const { avatar } = props.conversation
|
||||
return avatar
|
||||
}
|
||||
})
|
||||
|
||||
/** 会话昵称 */
|
||||
const sessionName = computed(() => {
|
||||
if (props.conversation.name) {
|
||||
return props.conversation.name
|
||||
}
|
||||
return props.conversation.conversationId
|
||||
})
|
||||
|
||||
/** 是否是机器人 */
|
||||
const isAiUser = ref(false)
|
||||
|
||||
/** 时间 */
|
||||
const date = computed(() => {
|
||||
const time =
|
||||
props.conversation.lastMessage?.messageRefer.createTime ||
|
||||
props.conversation.updateTime
|
||||
// 如果最后一条消息时间戳不存在,则会话列表不显示
|
||||
if (!time) {
|
||||
return ''
|
||||
}
|
||||
const _d = dayjs(time)
|
||||
const isCurrentDay = _d.isSame(dayjs(), 'day')
|
||||
const isCurrentYear = _d.isSame(dayjs(), 'year')
|
||||
return _d.format(
|
||||
isCurrentDay ? 'HH:mm' : isCurrentYear ? 'MM-DD HH:mm' : 'YYYY-MM-DD HH:mm'
|
||||
)
|
||||
})
|
||||
|
||||
const max = 99
|
||||
|
||||
/** 未读数 */
|
||||
const unread = computed(() => {
|
||||
return props.conversation.unreadCount > 0
|
||||
? props.conversation.unreadCount > max
|
||||
? `${max}+`
|
||||
: props.conversation.unreadCount + ''
|
||||
: ''
|
||||
})
|
||||
|
||||
/** 是否免打扰 */
|
||||
const isMute = computed(() => {
|
||||
return !!props.conversation.mute
|
||||
})
|
||||
|
||||
/** 是否被@ */
|
||||
const beMentioned = computed(() => {
|
||||
return !!props.conversation.aitMsgs?.length
|
||||
})
|
||||
|
||||
/** 是否展示 未读数 */
|
||||
const showConversationUnread = computed(() => {
|
||||
const myUserAccountId = uni.$UIKitNIM.V2NIMLoginService.getLoginUser()
|
||||
if (
|
||||
props.conversation.type ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P
|
||||
) {
|
||||
return (
|
||||
props?.conversation?.lastMessage?.messageRefer.senderId ===
|
||||
myUserAccountId &&
|
||||
props?.conversation?.lastMessage?.messageType !==
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL &&
|
||||
props?.conversation?.lastMessage?.messageType !==
|
||||
V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION &&
|
||||
props?.conversation?.lastMessage?.sendingState ===
|
||||
V2NIMConst.V2NIMMessageSendingState
|
||||
.V2NIM_MESSAGE_SENDING_STATE_SUCCEEDED &&
|
||||
props?.conversation?.lastMessage?.lastMessageState !==
|
||||
V2NIMConst.V2NIMLastMessageState.V2NIM_MESSAGE_STATUS_REVOKE
|
||||
)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// 左滑显示 action 动画
|
||||
let startX = 0,
|
||||
startY = 0
|
||||
// 开始左滑
|
||||
function handleTouchStart(event: TouchEvent) {
|
||||
startX = event.changedTouches[0].pageX
|
||||
startY = event.changedTouches[0].pageY
|
||||
}
|
||||
|
||||
// 左滑
|
||||
function handleTouchMove(event: TouchEvent) {
|
||||
const moveEndX = event.changedTouches[0].pageX
|
||||
const moveEndY = event.changedTouches[0].pageY
|
||||
const X = moveEndX - startX + 20
|
||||
const Y = moveEndY - startY
|
||||
if (Math.abs(X) > Math.abs(Y) && X > 0) {
|
||||
emit('leftSlide', null)
|
||||
} else if (Math.abs(X) > Math.abs(Y) && X < 0) {
|
||||
emit('leftSlide', props.conversation)
|
||||
}
|
||||
}
|
||||
|
||||
/** 会话列表点击事件 */
|
||||
function handleConversationItemClick() {
|
||||
if (props.showMoreActions) {
|
||||
emit('leftSlide', null)
|
||||
return
|
||||
}
|
||||
emit('click', props.conversation)
|
||||
}
|
||||
|
||||
/** 监听是否是Ai 数字人 */
|
||||
const isAiUserWatch = autorun(() => {
|
||||
isAiUser.value = uni.$UIKitStore.aiUserStore.isAIUser(to.value)
|
||||
})
|
||||
|
||||
/** 监听会话方在线离线状态 */
|
||||
const statusWatch = autorun(() => {
|
||||
if (
|
||||
props.conversation.type ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P
|
||||
) {
|
||||
const stateMap = uni.$UIKitStore?.subscriptionStore.stateMap
|
||||
|
||||
if (
|
||||
stateMap.get(to.value) &&
|
||||
uni.$UIKitStore.localOptions.loginStateVisible
|
||||
) {
|
||||
status.value =
|
||||
stateMap.get(to.value)?.statusType ===
|
||||
V2NIMConst.V2NIMUserStatusType.V2NIM_USER_STATUS_TYPE_LOGIN
|
||||
} else {
|
||||
status.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
isAiUserWatch()
|
||||
statusWatch()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$cellHeight: 72px;
|
||||
|
||||
.conversation-item-container {
|
||||
position: relative;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&.show-action-list {
|
||||
transform: translateX(-200px);
|
||||
}
|
||||
|
||||
&.stick-on-top {
|
||||
background: #f3f5f7;
|
||||
}
|
||||
|
||||
.beMentioned {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.right-action-list {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -200px;
|
||||
bottom: 0;
|
||||
width: 200px;
|
||||
white-space: nowrap;
|
||||
|
||||
.right-action-item {
|
||||
width: 100px;
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
height: $cellHeight;
|
||||
line-height: $cellHeight;
|
||||
}
|
||||
|
||||
.action-top {
|
||||
background: #337eff;
|
||||
}
|
||||
|
||||
.action-delete {
|
||||
background: #a8abb6;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
height: $cellHeight;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.conversation-item-left {
|
||||
position: relative;
|
||||
|
||||
.conversation-item-badge {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-item-right {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.conversation-item-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.conversation-item-title {
|
||||
overflow: hidden; //超出的文本隐藏
|
||||
text-overflow: ellipsis; //溢出用省略号显示
|
||||
white-space: nowrap; //溢出不换行
|
||||
}
|
||||
|
||||
.conversation-item-time {
|
||||
font-size: 12px;
|
||||
color: #cccccc;
|
||||
text-align: right;
|
||||
width: 90px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-item-desc {
|
||||
font-size: 13px;
|
||||
color: #999999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 22px;
|
||||
|
||||
.conversation-item-desc-content {
|
||||
overflow: hidden; //超出的文本隐藏
|
||||
text-overflow: ellipsis; //溢出用省略号显示
|
||||
white-space: nowrap; //溢出不换行
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.conversation-item-desc-state {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.dot {
|
||||
background-color: #ff4d4f;
|
||||
color: #fff;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background-color: #ff4d4f;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
line-height: 19px;
|
||||
border-radius: 10px;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
z-index: 99;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.unread {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: -2px;
|
||||
z-index: 99;
|
||||
}
|
||||
.conversation-item-desc-ait {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.login-state-icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
box-sizing: content-box;
|
||||
background-color: #84ed85;
|
||||
border: 2px solid #fff;
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.unlogin-state-icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
box-sizing: content-box;
|
||||
background-color: #d4d9da;
|
||||
border: 2px solid #fff;
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,570 @@
|
||||
<template>
|
||||
<div class="conversation-wrapper">
|
||||
<div
|
||||
v-if="addDropdownVisible"
|
||||
class="dropdown-mark"
|
||||
@touchstart="hideAddDropdown"
|
||||
></div>
|
||||
<div class="navigation-bar">
|
||||
<div :class="isWxApp ? 'button-box-mp' : 'button-box'">
|
||||
<!-- #ifdef MP -->
|
||||
<image
|
||||
src="https://yx-web-nosdn.netease.im/common/9ae07d276ba2833b678a4077960e2d1e/Group 1899.png"
|
||||
class="button-icon"
|
||||
@tap="showAddDropdown"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP -->
|
||||
<div class="button-icon-add" @tap="showAddDropdown">
|
||||
<Icon type="icon-More" />
|
||||
</div>
|
||||
<!-- #endif -->
|
||||
<div v-if="addDropdownVisible" class="dropdown-container">
|
||||
<div class="add-menu-list">
|
||||
<div class="add-menu-item" @tap="onDropdownClick('addFriend')">
|
||||
<Icon type="icon-tianjiahaoyou" :style="{ marginRight: '5px' }" />
|
||||
{{ t('addFriendText') }}
|
||||
</div>
|
||||
<div class="add-menu-item" @tap="onDropdownClick('createGroup')">
|
||||
<Icon
|
||||
type="icon-chuangjianqunzu"
|
||||
:style="{ marginRight: '5px' }"
|
||||
/>
|
||||
{{ t('createTeamText') }}
|
||||
</div>
|
||||
<div
|
||||
class="add-menu-item"
|
||||
@tap="onDropdownClick('createDiscussion')"
|
||||
>
|
||||
<Icon
|
||||
type="icon-chuangjianqunzu"
|
||||
:style="{ marginRight: '5px' }"
|
||||
/>
|
||||
{{ t('createDiscussionText') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block"></div>
|
||||
<NetworkAlert />
|
||||
<div v-if="!conversationList || conversationList.length === 0">
|
||||
|
||||
<div class="conversation-search" @tap="goToSearchPage">
|
||||
<div class="search-input-wrapper">
|
||||
<div class="search-icon-wrapper">
|
||||
<Icon
|
||||
iconClassName="search-icon"
|
||||
:size="16"
|
||||
color="#A6ADB6"
|
||||
type="icon-sousuo"
|
||||
></Icon>
|
||||
</div>
|
||||
<div class="search-input">{{ t('searchText') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 页面初始化的过程中,sessionList编译到小程序和h5出现sessionList为undefined的情况,即使给了默认值为空数组,故在此处进行判断 -->
|
||||
<Empty
|
||||
v-if="!conversationList || conversationList.length === 0"
|
||||
:text="t('conversationEmptyText')"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="conversation-list-wrapper">
|
||||
<div class="security-tip">
|
||||
<div>
|
||||
{{ t('securityTipText') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="conversation-search" @click="goToSearchPage">
|
||||
<div class="search-input-wrapper">
|
||||
<div class="search-icon-wrapper">
|
||||
<Icon
|
||||
iconClassName="search-icon"
|
||||
:size="16"
|
||||
color="#A6ADB6"
|
||||
type="icon-sousuo"
|
||||
></Icon>
|
||||
</div>
|
||||
<div class="search-input">{{ t('searchText') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 此处的key如果用conversationId,会在ios上渲染存在问题,会出现会话列表显示undefined -->
|
||||
<div
|
||||
v-for="conversation in conversationList"
|
||||
:key="conversation.renderKey"
|
||||
>
|
||||
<ConversationItem
|
||||
:key="conversation.renderKey"
|
||||
:showMoreActions="
|
||||
currentMoveSessionId === conversation.conversationId
|
||||
"
|
||||
:conversation="conversation"
|
||||
@delete="handleSessionItemDeleteClick"
|
||||
@stickyToTop="handleSessionItemStickTopChange"
|
||||
@click="handleSessionItemClick"
|
||||
@leftSlide="handleSessionItemLeftSlide"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/** 会话列表主界面 */
|
||||
import { onUnmounted, ref, watch } from 'vue'
|
||||
import { autorun } from 'mobx'
|
||||
import { onShow, onHide } from '@dcloudio/uni-app'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import NetworkAlert from '@/components/NetworkAlert.vue'
|
||||
import Empty from '@/components/Empty.vue'
|
||||
import ConversationItem from './conversation-item.vue'
|
||||
import { setContactTabUnread, setTabUnread } from '@/utils/im/msg'
|
||||
import { t } from '@/utils/im/i18n'
|
||||
import { customNavigateTo } from '@/utils/im/customNavigate'
|
||||
|
||||
import { V2NIMConst } from '@/utils/im/nim'
|
||||
import { isWxApp } from '@/utils/im/index'
|
||||
import { trackInit } from '@/utils/im/reporter'
|
||||
|
||||
import {
|
||||
V2NIMConversationForUI,
|
||||
V2NIMLocalConversationForUI,
|
||||
} from '@xkit-yx/im-store-v2/dist/types/types'
|
||||
|
||||
/**会话列表 */
|
||||
const conversationList = ref<
|
||||
(
|
||||
| (V2NIMConversationForUI & { renderKey: string })
|
||||
| (V2NIMLocalConversationForUI & { renderKey: string })
|
||||
)[]
|
||||
>([])
|
||||
|
||||
/** 右上角更多 */
|
||||
const addDropdownVisible = ref(false)
|
||||
|
||||
/** 当前左滑会话ID */
|
||||
const currentMoveSessionId = ref('')
|
||||
|
||||
/**是否是云端会话 */
|
||||
const enableV2CloudConversation =
|
||||
uni.$UIKitStore?.sdkOptions?.enableV2CloudConversation
|
||||
|
||||
/** 会话左滑 */
|
||||
const handleSessionItemLeftSlide = (
|
||||
conversation: V2NIMConversationForUI | V2NIMLocalConversationForUI | null
|
||||
) => {
|
||||
// 微信小程序点击也会触发左滑事件,但此时 conversation 为 null
|
||||
if (conversation) {
|
||||
currentMoveSessionId.value = conversation.conversationId
|
||||
} else {
|
||||
currentMoveSessionId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
let flag = false
|
||||
|
||||
// 点击会话
|
||||
const handleSessionItemClick = async (
|
||||
conversation: V2NIMConversationForUI | V2NIMLocalConversationForUI
|
||||
) => {
|
||||
console.log(conversation)
|
||||
if (flag) return
|
||||
currentMoveSessionId.value = ''
|
||||
try {
|
||||
flag = true
|
||||
// 处理@消息相关
|
||||
if (
|
||||
conversation.type ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM ||
|
||||
conversation.type ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_SUPER_TEAM
|
||||
) {
|
||||
if (enableV2CloudConversation) {
|
||||
await uni.$UIKitStore.conversationStore?.markConversationReadActive(
|
||||
conversation.conversationId
|
||||
)
|
||||
} else {
|
||||
await uni.$UIKitStore.localConversationStore?.markConversationReadActive(
|
||||
conversation.conversationId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await uni.$UIKitStore.uiStore.selectConversation(
|
||||
conversation.conversationId
|
||||
)
|
||||
customNavigateTo({
|
||||
url: '/pages_chat/chat/index',
|
||||
})
|
||||
} catch {
|
||||
uni.showToast({
|
||||
title: t('selectSessionFailText'),
|
||||
icon: 'error',
|
||||
})
|
||||
} finally {
|
||||
flag = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除会话
|
||||
const handleSessionItemDeleteClick = async (
|
||||
conversation: V2NIMConversationForUI | V2NIMLocalConversationForUI
|
||||
) => {
|
||||
try {
|
||||
if (enableV2CloudConversation) {
|
||||
await uni.$UIKitStore.conversationStore?.deleteConversationActive(
|
||||
conversation.conversationId
|
||||
)
|
||||
} else {
|
||||
await uni.$UIKitStore.localConversationStore?.deleteConversationActive(
|
||||
conversation.conversationId
|
||||
)
|
||||
}
|
||||
currentMoveSessionId.value = ''
|
||||
} catch {
|
||||
uni.showToast({
|
||||
title: t('deleteSessionFailText'),
|
||||
icon: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 置顶会话
|
||||
const handleSessionItemStickTopChange = async (
|
||||
conversation: V2NIMConversationForUI | V2NIMLocalConversationForUI
|
||||
) => {
|
||||
if (conversation.stickTop) {
|
||||
try {
|
||||
if (enableV2CloudConversation) {
|
||||
await uni.$UIKitStore?.conversationStore?.stickTopConversationActive(
|
||||
conversation.conversationId,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
await uni.$UIKitStore?.localConversationStore?.stickTopConversationActive(
|
||||
conversation.conversationId,
|
||||
false
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
uni.showToast({
|
||||
title: t('deleteStickTopFailText'),
|
||||
icon: 'error',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if (enableV2CloudConversation) {
|
||||
await uni.$UIKitStore?.conversationStore?.stickTopConversationActive(
|
||||
conversation.conversationId,
|
||||
true
|
||||
)
|
||||
} else {
|
||||
await uni.$UIKitStore?.localConversationStore?.stickTopConversationActive(
|
||||
conversation.conversationId,
|
||||
true
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
uni.showToast({
|
||||
title: t('addStickTopFailText'),
|
||||
icon: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 显示添加好友、群聊 Dropdown */
|
||||
const showAddDropdown = () => {
|
||||
addDropdownVisible.value = true
|
||||
}
|
||||
|
||||
/** 隐藏添加好友、群聊 Dropdown */
|
||||
const hideAddDropdown = () => {
|
||||
addDropdownVisible.value = false
|
||||
}
|
||||
|
||||
/** 点击Dropdown */
|
||||
const onDropdownClick = (
|
||||
urlType: 'addFriend' | 'createGroup' | 'createDiscussion'
|
||||
) => {
|
||||
const urlMap = {
|
||||
// 添加好友
|
||||
addFriend: '/pages/User/friend/add-friend',
|
||||
// 创建群聊
|
||||
createGroup: '/pages/Team/team-create/index',
|
||||
// 创建讨论组和创建群聊复用一个页面,仅在创建群接口时,群扩展字段添加im_ui_kit_group参数区分,讨论组本质也是群,只是少了群的一些能力,旨在于快速创建讨论
|
||||
createDiscussion: `/pages/Team/team-create/index?createDiscussion=${true}`,
|
||||
}
|
||||
addDropdownVisible.value = false
|
||||
customNavigateTo({
|
||||
url: urlMap[urlType],
|
||||
})
|
||||
}
|
||||
|
||||
/** 跳转至搜索页面 */
|
||||
const goToSearchPage = () => {
|
||||
customNavigateTo({
|
||||
url: '/pages/Conversation/conversation-search/index',
|
||||
})
|
||||
}
|
||||
|
||||
/** 订阅当前会话方在线离线状态 */
|
||||
const subscribeUserStatus = (
|
||||
conversations: (V2NIMConversationForUI | V2NIMLocalConversationForUI)[]
|
||||
) => {
|
||||
const loginStateVisible = uni.$UIKitStore.localOptions.loginStateVisible
|
||||
if (loginStateVisible) {
|
||||
// 订阅会话列表中 单聊的在线离线状态
|
||||
const accounts = conversations
|
||||
.filter(
|
||||
(item) =>
|
||||
item.type ===
|
||||
V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P
|
||||
)
|
||||
.map((item) => {
|
||||
return uni.$UIKitNIM?.V2NIMConversationIdUtil.parseConversationTargetId(
|
||||
item.conversationId
|
||||
)
|
||||
})
|
||||
// 将 accounts 拆分成多个长度不超过 100 的子数组
|
||||
const chunkSize = 100
|
||||
|
||||
const length = accounts.length
|
||||
|
||||
for (let i = 0; i < length; i += chunkSize) {
|
||||
const chunk = accounts.slice(i, i + chunkSize)
|
||||
|
||||
if (chunk.length > 0) {
|
||||
uni.$UIKitStore.subscriptionStore.subscribeUserStatusActive(chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trackInit('ConversationUIKit')
|
||||
|
||||
/** 监听会话列表数据变更,实时更新 conversationList */
|
||||
const conversationListWatch = autorun(() => {
|
||||
const _conversationList = enableV2CloudConversation
|
||||
? uni.$UIKitStore?.uiStore?.conversations
|
||||
: uni.$UIKitStore?.uiStore?.localConversations
|
||||
|
||||
conversationList.value = _conversationList
|
||||
?.map(
|
||||
(conversation: V2NIMConversationForUI | V2NIMLocalConversationForUI) => {
|
||||
return {
|
||||
...conversation,
|
||||
// 为什么要加一个renderKey 直接在渲染的时候写 :key = conversation.conversationId 不就行了吗?
|
||||
// 如果不加,在渲染的时候,就会出现会话列表显示undefined,uniapp 很奇怪的问题
|
||||
renderKey: conversation.conversationId,
|
||||
}
|
||||
}
|
||||
)
|
||||
.sort(
|
||||
(
|
||||
a: V2NIMConversationForUI | V2NIMLocalConversationForUI,
|
||||
b: V2NIMConversationForUI | V2NIMLocalConversationForUI
|
||||
) => b.sortOrder - a.sortOrder
|
||||
)
|
||||
|
||||
setTabUnread()
|
||||
})
|
||||
|
||||
/** 连接状态监听 断网重连后重新订阅 */
|
||||
const connectWatch = autorun(() => {
|
||||
if (
|
||||
uni.$UIKitStore?.connectStore.loginStatus ===
|
||||
V2NIMConst.V2NIMLoginStatus.V2NIM_LOGIN_STATUS_LOGINED &&
|
||||
uni.$UIKitStore?.connectStore.connectStatus ===
|
||||
V2NIMConst.V2NIMConnectStatus.V2NIM_CONNECT_STATUS_CONNECTED
|
||||
) {
|
||||
subscribeUserStatus(conversationList?.value)
|
||||
}
|
||||
})
|
||||
|
||||
/** 监听系统消息未读 */
|
||||
const getTotalUnreadMsgsCountWatch = autorun(() => {
|
||||
// 为了监听会触发
|
||||
uni.$UIKitStore?.sysMsgStore?.getTotalUnreadMsgsCount()
|
||||
setContactTabUnread()
|
||||
})
|
||||
|
||||
// 监听数组长度变化
|
||||
watch(
|
||||
() => conversationList?.value?.length, // 监听 length 属性
|
||||
() => {
|
||||
subscribeUserStatus(conversationList?.value)
|
||||
}
|
||||
)
|
||||
|
||||
// 监听会话列表数据变更,实时订阅在线离线状态
|
||||
onShow(() => {
|
||||
if (conversationList.value?.length) {
|
||||
subscribeUserStatus(conversationList?.value)
|
||||
}
|
||||
})
|
||||
|
||||
onHide(() => {
|
||||
addDropdownVisible.value = false
|
||||
currentMoveSessionId.value = ''
|
||||
})
|
||||
|
||||
/**卸载监听 */
|
||||
onUnmounted(() => {
|
||||
conversationListWatch()
|
||||
getTotalUnreadMsgsCountWatch()
|
||||
connectWatch()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/common.scss';
|
||||
|
||||
.conversation-wrapper {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.navigation-bar {
|
||||
position: fixed;
|
||||
|
||||
height: 60px;
|
||||
border-bottom: 1rpx solid #e9eff5;
|
||||
padding: 0 20px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: var(--status-bar-height);
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
z-index: 999;
|
||||
}
|
||||
.conversation-search {
|
||||
display: none;
|
||||
align-items: center;
|
||||
height: 54px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
padding: 10px;
|
||||
}
|
||||
.security-tip {
|
||||
padding: 0 10px;
|
||||
background: #fff5e1;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
white-space: wrap;
|
||||
color: #eb9718;
|
||||
text-align: left;
|
||||
display: none;
|
||||
align-items: center;
|
||||
}
|
||||
.search-input-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 34px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 10px;
|
||||
background: #f3f5f7;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.search-input {
|
||||
margin-left: 5px;
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
}
|
||||
.search-icon-wrapper {
|
||||
height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.block {
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
display: none;
|
||||
padding-top: var(--status-bar-height);
|
||||
}
|
||||
|
||||
.conversation-list-wrapper {
|
||||
height: calc(100% - 60px - var(--status-bar-height));
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.logo-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
|
||||
.logo-img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.button-icon-add {
|
||||
position: relative;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.dropdown-mark {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
position: absolute;
|
||||
// #ifdef MP
|
||||
top: -105px;
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
top: 100%;
|
||||
// #endif
|
||||
right: 30px;
|
||||
min-width: 122px;
|
||||
min-height: 40px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e6e6e6;
|
||||
box-shadow: 0px 4px 7px rgba(133, 136, 140, 0.25);
|
||||
border-radius: 8px;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.add-menu-list {
|
||||
padding: 15px 10px;
|
||||
|
||||
.add-menu-item {
|
||||
white-space: nowrap;
|
||||
font-size: 16px;
|
||||
padding-left: 5px;
|
||||
margin-bottom: 10px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-block {
|
||||
width: 100%;
|
||||
height: 72px;
|
||||
}
|
||||
</style>
|
||||
@@ -28,8 +28,9 @@
|
||||
:refresher-triggered="isRefreshing"
|
||||
@refresherrefresh="onRefresh"
|
||||
>
|
||||
<ConversationList />
|
||||
<!-- 消息项 -->
|
||||
<view class="message-item" v-for="(item, index) in messageList" :key="item.id || index" @click="openMessage(item)">
|
||||
<!-- <view class="message-item" v-for="(item, index) in messageList" :key="item.id || index" @click="openMessage(item)">
|
||||
<view class="message-avatar">
|
||||
<view class="avatar-placeholder" v-if="!item.avatar">
|
||||
<uni-icons type="person" size="32" color="#ffffff"></uni-icons>
|
||||
@@ -48,14 +49,14 @@
|
||||
</view>
|
||||
|
||||
<!-- 空状态提示 -->
|
||||
<view class="empty-state" v-if="messageList.length === 0 && !isRefreshing">
|
||||
<!-- <view class="empty-state" v-if="messageList.length === 0 && !isRefreshing">
|
||||
<uni-icons type="chat" size="80" color="#cccccc"></uni-icons>
|
||||
<text class="empty-text">暂无患者消息</text>
|
||||
<text class="empty-subtext">下拉刷新获取最新申请</text>
|
||||
<view class="debug-actions">
|
||||
<button class="debug-btn" @click="getApplyList">测试API调用</button>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</scroll-view>
|
||||
|
||||
<!-- 患者列表区域 -->
|
||||
@@ -141,7 +142,10 @@
|
||||
<up-image :src="lineImg" width="14rpx" height="140rpx" ></up-image>
|
||||
</view>
|
||||
<view class="right-content">
|
||||
<view class="note">{{ item.note }}</view>
|
||||
<view class="leftcontent">
|
||||
<view class="note">{{ item.note }}</view>
|
||||
<view class="name">{{ item.patientname }}</view>
|
||||
</view>
|
||||
<uni-icons type="forward" size="20" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
@@ -207,7 +211,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, getCurrentInstance, computed } from 'vue';
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import { onShow,onLoad} from "@dcloudio/uni-app";
|
||||
import dayImg from "@/static/visit_data11.png"
|
||||
import planImg from "@/static/visitplan.png"
|
||||
import api from '@/api/api.js';
|
||||
@@ -217,6 +221,7 @@
|
||||
import pinyin from 'pinyin';
|
||||
import dayjs from 'dayjs'
|
||||
import lineImg from "@/static/item_visitplan_fg.png"
|
||||
import ConversationList from './conversation-list/index.vue'
|
||||
|
||||
const goPatientDetail = (uuid) => {
|
||||
navTo({
|
||||
@@ -581,13 +586,21 @@
|
||||
// 使用 up-index-list 后不再需要手动滚动联动逻辑
|
||||
|
||||
// 页面显示时加载数据
|
||||
onShow(() => {
|
||||
activeTab.value='message';
|
||||
onLoad(() => {
|
||||
|
||||
loadMessageList();
|
||||
computeListHeight();
|
||||
getApplyList();
|
||||
patientListByGBK();
|
||||
getFollowUpList();
|
||||
|
||||
});
|
||||
onShow(() => {
|
||||
followUpList.value = [];
|
||||
page.value = 1;
|
||||
followUpHasMore.value = true;
|
||||
followUpLoading.value = false;
|
||||
followUpRefreshing.value = false;
|
||||
getFollowUpList(true);
|
||||
});
|
||||
|
||||
// 加载消息列表
|
||||
@@ -598,7 +611,7 @@
|
||||
const goFollowDetail = (raw) => {
|
||||
if(!raw) return;
|
||||
navTo({
|
||||
url: `/pages_app/followDetail/followDetail?followUpUuid=${encodeURIComponent(raw.uuid || '')}&patient_name=${encodeURIComponent(raw.patient_name || '')}`
|
||||
url: `/pages_app/followDetail/followDetail?followUpUuid=${encodeURIComponent(raw.uuid || '')}&patient_name=${raw.patientname}`
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -1081,7 +1094,11 @@
|
||||
font-size: 30rpx;
|
||||
color:#333;
|
||||
}
|
||||
|
||||
.right-content .name{
|
||||
margin-top: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color:#8B2316;
|
||||
}
|
||||
/* 加载状态样式 */
|
||||
.load-more {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user