From 27cd042df9aca0383a49f3514bc21958dd890912 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 六月 2026 15:42:23 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加IM即时通讯模块
---
src/views/im/home/pages/conversation/components/message/reply-preview.vue | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 234 insertions(+), 0 deletions(-)
diff --git a/src/views/im/home/pages/conversation/components/message/reply-preview.vue b/src/views/im/home/pages/conversation/components/message/reply-preview.vue
new file mode 100644
index 0000000..6ada968
--- /dev/null
+++ b/src/views/im/home/pages/conversation/components/message/reply-preview.vue
@@ -0,0 +1,234 @@
+<script lang="ts" setup>
+import { computed } from 'vue'
+
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+import { formatFileSize } from '#/packages/utils/src'
+
+import { CardLineLabel } from '#/views/im/home/components/card'
+import { ImContentType } from '#/views/im/utils/constants'
+import { getClientConversationId } from '#/views/im/utils/db'
+import {
+ type AudioMessage,
+ type CardMessage,
+ type FaceMessage,
+ type FileMessage,
+ getFileIconInfo,
+ type ImageMessage,
+ type MaterialMessage,
+ parseMessage,
+ type QuoteMessage,
+ type TextMessage,
+ type VideoMessage
+} from '#/views/im/utils/message'
+import { formatSeconds } from '#/views/im/utils/time'
+import { getSenderDisplayName } from '#/views/im/utils/user'
+
+import { useConversationStore } from '../../../../store/conversationStore'
+import { useMessageStore } from '../../../../store/messageStore'
+
+defineOptions({ name: 'ImReplyPreview' })
+
+const props = withDefaults(
+ defineProps<{
+ /** 姘旀场鍐呬负 true 鏀寔鐐瑰嚮璺宠浆,杈撳叆鏉′负 false */
+ clickable?: boolean
+ /** 杈撳叆鏉′负 true 鏄剧ず 脳 鍏抽棴鎸夐挳 */
+ closable?: boolean
+ /** 鑷繁鍙戦�佺殑姘旀场涓� true锛屾妸绔栫嚎闀滃儚鍒板彸渚э紝涓庢皵娉″悓渚� */
+ mirrored?: boolean
+ quote: QuoteMessage
+ }>(),
+ {
+ clickable: false,
+ closable: false,
+ mirrored: false
+ }
+)
+
+const emit = defineEmits<{
+ close: []
+ locate: [messageId: number]
+}>()
+
+const MAX_TEXT_PREVIEW_LEN = 60 // 鏂囨湰鎽樿鍦ㄥ紩鐢ㄥ潡閲屽睍绀虹殑鏈�澶у瓧绗︽暟
+
+const conversationStore = useConversationStore()
+const messageStore = useMessageStore()
+
+/** 鍦ㄥ綋鍓嶄細璇濇秷鎭垪琛ㄩ噷鏌ユ壘鍘熸秷鎭�,浠呯敤浜庡疄鏃跺垽鏂槸鍚﹀凡鎾ゅ洖;鎽樿 / 缂╃暐鍥鹃兘浠� quote.content 鐩存帴娲剧敓 */
+const liveMessage = computed(() => {
+ const conversation = conversationStore.activeConversation
+ if (!conversation || !props.quote.messageId) {
+ return undefined
+ }
+ return messageStore
+ .getMessages(getClientConversationId(conversation.type, conversation.targetId))
+ .find((message) => message.id === props.quote.messageId)
+})
+
+/** 鍛戒腑鏈湴缂撳瓨涓� type === RECALL 鎵嶅垽瀹氫负宸叉挙鍥�;涓嶅湪缂撳瓨鐨勫綋蹇収浠嶆湁鏁� */
+const isRecalled = computed(() => liveMessage.value?.type === ImContentType.RECALL)
+
+/** 娓叉煋鏃跺疄鏃剁畻,涓庢皵娉′笂鏂规樉绀哄悕璧板悓涓�濂楄鍒�,閬垮厤澶囨敞鍙樻洿鍚庡紩鐢ㄥ潡闄堟棫 */
+const senderName = computed(() => {
+ const conversation = conversationStore.activeConversation
+ if (!conversation) {
+ return ''
+ }
+ return getSenderDisplayName(props.quote.senderId, conversation.type, conversation.targetId)
+})
+
+/** quote.content 瑙f瀽涓�娆$紦瀛橈紝璁╁涓� computed 澶嶇敤锛岄暱浼氳瘽姣忔潯寮曠敤姘旀场灏戜竴娆� JSON.parse */
+type AnyQuotePayload = Partial<
+ AudioMessage &
+ CardMessage &
+ FaceMessage &
+ FileMessage &
+ ImageMessage &
+ MaterialMessage &
+ TextMessage &
+ VideoMessage
+>
+const parsedPayload = computed(() => parseMessage<AnyQuotePayload>(props.quote.content))
+
+const isText = computed(() => props.quote.type === ImContentType.TEXT)
+const isFile = computed(() => props.quote.type === ImContentType.FILE)
+const isVoice = computed(() => props.quote.type === ImContentType.VOICE)
+const isCard = computed(() => props.quote.type === ImContentType.CARD)
+const isFace = computed(() => props.quote.type === ImContentType.FACE)
+const isMaterial = computed(() => props.quote.type === ImContentType.MATERIAL)
+
+/** 鏂囨湰瓒呰繃 MAX_TEXT_PREVIEW_LEN 鎴柇锛岄暱鍐呭涓嶆拺鐖嗗紩鐢ㄥ潡 */
+const textPreview = computed(() => {
+ const text = parsedPayload.value?.content ?? ''
+ return text.length <= MAX_TEXT_PREVIEW_LEN ? text : `${text.slice(0, Math.max(0, MAX_TEXT_PREVIEW_LEN))}鈥
+})
+
+/** 鏂囦欢 icon锛氭寜鎵╁睍鍚嶆寫鑹诧紝璺熶富姘旀场娓叉煋鍚屾簮 */
+const fileIcon = computed(() => getFileIconInfo(parsedPayload.value?.name))
+
+/** 缂╃暐鍥� URL锛氬浘鐗� / 瑙嗛 / 琛ㄦ儏璐村浘 / 棰戦亾绱犳潗灏侀潰浠� quote.content 鐩存帴鍙栵紝涓嶄緷璧栨湰鍦扮紦瀛� */
+const thumbnailUrl = computed<string | undefined>(() => {
+ if (isRecalled.value) {
+ return undefined
+ }
+ const { type } = props.quote
+ if (type === ImContentType.IMAGE) {
+ return parsedPayload.value?.thumbnailUrl || parsedPayload.value?.url
+ }
+ if (type === ImContentType.VIDEO || type === ImContentType.MATERIAL) {
+ return parsedPayload.value?.coverUrl
+ }
+ if (type === ImContentType.FACE) {
+ return parsedPayload.value?.url
+ }
+ return undefined
+})
+
+/** 浠� clickable 涓旀湭鎾ゅ洖鏃惰Е鍙戣烦杞� */
+function onClick() {
+ if (!props.clickable || isRecalled.value) {
+ return
+ }
+ emit('locate', props.quote.messageId)
+}
+</script>
+
+<template>
+ <!--
+ 寮曠敤棰勮鍧楋紙瀵归綈寰俊 PC锛夛細2px 绔栫嚎浣滀负銆屽紩鐢ㄣ�嶈瑙夋爣璇� + 绱у噾鍐呭棰勮
+ - clickable=true锛堟皵娉″唴锛夛細鐐瑰嚮瑙﹀彂 locate emit锛涙挙鍥炴�佺鐢ㄨ烦杞�
+ - closable=true锛堣緭鍏ユ潯锛夛細灏鹃儴 脳 鍏抽棴鎸夐挳
+ - mirrored=true锛堣嚜宸卞彂閫佺殑姘旀场锛夛細绔栫嚎闀滃儚鍒板彸渚э紝涓庢皵娉″悓渚�
+ - 鍐呭棰勮锛堜笌涓绘皵娉″悓婧愶紝涓嶇缉鍐欐垚銆孾鏂囦欢]/[璇煶]銆嶏級锛�
+ - 鏂囨湰锛氭埅鏂悗绾枃瀛�
+ - 鍥剧墖 / 瑙嗛锛氱缉鐣ュ浘锛圛MAGE 鐢� thumbnailUrl/url锛孷IDEO 鐢� coverUrl锛�
+ - 鏂囦欢锛歠ile icon + 鏂囦欢鍚� + 澶у皬
+ - 璇煶锛歛udio icon + 鏃堕暱
+ - 鎾ゅ洖锛堝懡涓湰鍦扮紦瀛樹笖 type === RECALL锛夛細銆屽師娑堟伅宸叉挙鍥炪�嶆枩浣撶伆瀛�
+ -->
+ <div
+ class="flex w-fit gap-1.5 items-center min-w-0 py-0.5 text-12px text-[var(--ant-color-text-secondary)] rounded transition-colors"
+ :class="[
+ mirrored
+ ? 'pl-1 pr-2 border-r-2 border-r-solid border-r-[var(--ant-color-border)]'
+ : 'pl-2 pr-1 border-l-2 border-l-solid border-l-[var(--ant-color-border)]',
+ {
+ 'cursor-pointer hover:text-[var(--ant-color-text)]': clickable && !isRecalled,
+ 'hover:bg-[var(--ant-color-fill-secondary)]': (clickable && !isRecalled) || closable
+ }
+ ]"
+ @click="onClick"
+ >
+ <span class="flex-shrink-0">{{ senderName }}:</span>
+
+ <!-- 鎾ゅ洖闄嶇骇 -->
+ <span v-if="isRecalled" class="italic">鍘熸秷鎭凡鎾ゅ洖</span>
+
+ <!-- 鏂囨湰 -->
+ <span v-else-if="isText" class="min-w-0 line-clamp-2 break-words">{{ textPreview }}</span>
+
+ <!-- 鏂囦欢锛歩con + 鏂囦欢鍚� + 澶у皬 -->
+ <template v-else-if="isFile">
+ <Icon :icon="fileIcon.icon" :color="fileIcon.color" :size="14" class="flex-shrink-0" />
+ <span v-if="parsedPayload?.name" class="min-w-0 line-clamp-2 break-words">
+ {{ parsedPayload.name }}
+ </span>
+ <span
+ v-if="parsedPayload?.size"
+ class="flex-shrink-0 text-[var(--ant-color-text-placeholder)]"
+ >
+ {{ formatFileSize(parsedPayload.size) }}
+ </span>
+ </template>
+
+ <!-- 璇煶锛歛udio icon + 鏃堕暱 -->
+ <template v-else-if="isVoice">
+ <Icon icon="ant-design:audio-outlined" :size="14" class="flex-shrink-0" />
+ <span v-if="parsedPayload?.duration" class="flex-shrink-0">
+ {{ formatSeconds(parsedPayload.duration) }}
+ </span>
+ </template>
+
+ <!-- 鍚嶇墖 -->
+ <CardLineLabel
+ v-else-if="isCard"
+ :card="parsedPayload"
+ class="min-w-0 line-clamp-2 break-words"
+ />
+
+ <!-- 琛ㄦ儏璐村浘锛氱缉鐣ュ浘 + name锛堟棤 name 浠呮樉绀� [琛ㄦ儏]锛� -->
+ <template v-else-if="isFace">
+ <span class="flex-shrink-0">[琛ㄦ儏]</span>
+ <span v-if="parsedPayload?.name" class="min-w-0 line-clamp-2 break-words">
+ {{ parsedPayload.name }}
+ </span>
+ </template>
+
+ <!-- 棰戦亾绱犳潗锛歔棰戦亾] + 鏍囬 + 灏侀潰缂╃暐鍥� -->
+ <template v-else-if="isMaterial">
+ <span class="flex-shrink-0">[棰戦亾]</span>
+ <span v-if="parsedPayload?.title" class="min-w-0 line-clamp-2 break-words">
+ {{ parsedPayload.title }}
+ </span>
+ </template>
+
+ <!-- 鍥剧墖 / 瑙嗛 / 琛ㄦ儏璐村浘 / 棰戦亾绱犳潗缂╃暐鍥� -->
+ <img
+ v-if="thumbnailUrl"
+ :src="thumbnailUrl"
+ class="flex-shrink-0 object-cover w-6 h-6 rounded"
+ alt=""
+ />
+
+ <!-- 鍏抽棴鎸夐挳 -->
+ <button
+ v-if="closable"
+ type="button"
+ class="flex-shrink-0 inline-flex items-center justify-center w-4 h-4 cursor-pointer rounded-full bg-transparent border-none text-[var(--ant-color-text-secondary)] hover:bg-[var(--ant-color-fill)] hover:text-[var(--ant-color-text)]"
+ @click.stop="emit('close')"
+ >
+ <Icon icon="ant-design:close-outlined" :size="10" />
+ </button>
+ </div>
+</template>
--
Gitblit v1.9.3