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/manager/message/content-preview.vue | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 254 insertions(+), 0 deletions(-)
diff --git a/src/views/im/manager/message/content-preview.vue b/src/views/im/manager/message/content-preview.vue
new file mode 100644
index 0000000..c9b3fdb
--- /dev/null
+++ b/src/views/im/manager/message/content-preview.vue
@@ -0,0 +1,254 @@
+<script lang="ts" setup>
+import { computed } from 'vue';
+
+import { getDictObj } from '#/packages/effects/hooks/src';
+import { IconifyIcon } from '#/packages/icons/src';
+import { formatFileSize, openSafeUrl } from '#/packages/utils/src';
+
+import { Image } from 'ant-design-vue';
+
+import { CardLineLabel } from '#/views/im/home/components/card';
+import { MESSAGE_MERGE_PREVIEW_LINES } from '#/views/im/utils/config';
+import {
+ ImContentType,
+ ImRtcCallEndReason,
+ ImRtcCallMediaType,
+ isFriendChatTip,
+ isGroupNotification,
+ isRtcCallTip,
+} from '#/views/im/utils/constants';
+import {
+ buildFacePreviewText,
+ summarizeMessageContent,
+} from '#/views/im/utils/conversation';
+import {
+ getFileIconInfo,
+ parseMessage,
+ parseRtcCallPayload,
+ resolveFriendNotificationText,
+ resolveGroupNotificationText,
+} from '#/views/im/utils/message';
+import { formatSeconds } from '#/views/im/utils/time';
+
+defineOptions({ name: 'ImManagerMessageContentPreview' });
+
+const props = defineProps<{
+ content?: string;
+ senderNickname?: string;
+ type?: number;
+}>();
+
+const payload = computed<null | Record<string, any>>(() =>
+ parseMessage<Record<string, any>>(props.content ?? ''),
+);
+
+const textContent = computed(() => payload.value?.content || '');
+
+const fileIconInfo = computed(() => getFileIconInfo(payload.value?.name));
+
+const cardPayload = computed(() => {
+ if (props.type !== ImContentType.CARD || !payload.value) {
+ return undefined;
+ }
+ return {
+ ...payload.value,
+ name: payload.value.name || payload.value.nickname || payload.value.userId || '',
+ };
+});
+
+const mergePreviewLines = computed(() => {
+ const messages = payload.value?.messages;
+ if (!Array.isArray(messages)) {
+ return [];
+ }
+ return messages
+ .slice(0, MESSAGE_MERGE_PREVIEW_LINES)
+ .map((item) => `${item.senderNickname}锛�${summarizeMessageContent(item)}`);
+});
+
+const fallbackText = computed(() => {
+ const raw = props.content || '';
+ if (!raw) {
+ return '';
+ }
+ try {
+ const parsed = JSON.parse(raw);
+ if (parsed && typeof parsed === 'object' && parsed.content) {
+ return String(parsed.content);
+ }
+ } catch {}
+ return raw;
+});
+
+const friendTipText = computed(() =>
+ resolveFriendNotificationText({ type: props.type }),
+);
+
+const groupTipText = computed(() =>
+ resolveGroupNotificationText(
+ { type: props.type, content: props.content },
+ (id) => `鐢ㄦ埛(${id})`,
+ props.senderNickname,
+ ),
+);
+
+const rtcCallTipText = computed(() => {
+ const rtcPayload = parseRtcCallPayload(props.content);
+ if (!rtcPayload) {
+ return '';
+ }
+ const mediaLabel =
+ rtcPayload.mediaType === ImRtcCallMediaType.VIDEO ? '瑙嗛' : '璇煶';
+ if (props.type === ImContentType.RTC_CALL_START) {
+ const inviter =
+ rtcPayload.inviterNickname?.trim() ||
+ `鐢ㄦ埛(${rtcPayload.inviterUserId ?? ''})`;
+ return `${inviter} 鍙戣捣浜�${mediaLabel}閫氳瘽`;
+ }
+ const segments = [`${mediaLabel}閫氳瘽宸茬粨鏉焋];
+ if (
+ rtcPayload.endReason &&
+ rtcPayload.endReason !== ImRtcCallEndReason.HANGUP
+ ) {
+ const reason = getDictObj(
+ 'im_rtc_call_end_reason',
+ String(rtcPayload.endReason),
+ )?.label;
+ if (reason) {
+ segments.push(reason);
+ }
+ }
+ const duration = rtcPayload.durationSeconds ?? 0;
+ if (duration > 0) {
+ segments.push(`鏃堕暱 ${formatSeconds(duration)}`);
+ }
+ return segments.join('锛�');
+});
+
+/** 鎵撳紑瑙嗛 */
+function openVideo() {
+ openSafeUrl(payload.value?.url);
+}
+</script>
+
+<template>
+ <span v-if="type === ImContentType.TEXT" class="whitespace-pre-wrap break-all">
+ {{ textContent }}
+ </span>
+
+ <Image
+ v-else-if="type === ImContentType.IMAGE && payload"
+ :height="60"
+ :src="payload.thumbnailUrl || payload.url"
+ :width="60"
+ />
+
+ <span
+ v-else-if="type === ImContentType.FILE && payload"
+ class="inline-flex items-center gap-1.5"
+ >
+ <IconifyIcon
+ :color="fileIconInfo.color"
+ :icon="fileIconInfo.icon"
+ class="size-4"
+ />
+ <span class="max-w-52 truncate">{{ payload.name }}</span>
+ <span v-if="payload.size > 0" class="text-xs text-gray-400">
+ {{ formatFileSize(payload.size) }}
+ </span>
+ </span>
+
+ <span
+ v-else-if="type === ImContentType.VOICE && payload"
+ class="inline-flex items-center gap-1.5"
+ >
+ <IconifyIcon class="size-4" icon="ant-design:audio-outlined" />
+ <span>{{ formatSeconds(payload.duration || 0) }}</span>
+ </span>
+
+ <span
+ v-else-if="type === ImContentType.VIDEO && payload"
+ class="inline-flex items-center gap-1.5"
+ >
+ <button
+ v-if="payload.coverUrl"
+ class="relative size-[60px] overflow-hidden rounded border-0 p-0"
+ type="button"
+ @click="openVideo"
+ >
+ <img :src="payload.coverUrl" class="size-full object-cover" />
+ <IconifyIcon
+ class="absolute inset-0 m-auto size-6 text-white drop-shadow"
+ icon="ant-design:play-circle-filled"
+ />
+ </button>
+ <span v-else class="inline-flex items-center gap-1.5">
+ <IconifyIcon class="size-4" icon="ant-design:video-camera-filled" />
+ <span>[瑙嗛]</span>
+ </span>
+ <span v-if="payload.duration" class="text-xs text-gray-400">
+ {{ formatSeconds(payload.duration) }}
+ </span>
+ <span v-if="payload.size > 0" class="text-xs text-gray-400">
+ {{ formatFileSize(payload.size) }}
+ </span>
+ </span>
+
+ <CardLineLabel
+ v-else-if="type === ImContentType.CARD && cardPayload"
+ :card="cardPayload"
+ :icon-size="16"
+ />
+
+ <span
+ v-else-if="type === ImContentType.FACE && payload"
+ class="inline-flex items-center gap-1.5"
+ >
+ <img
+ v-if="payload.url"
+ :alt="payload.name || '琛ㄦ儏'"
+ :src="payload.url"
+ class="size-[30px] rounded object-contain"
+ draggable="false"
+ />
+ <span>{{ buildFacePreviewText(payload) }}</span>
+ </span>
+
+ <span
+ v-else-if="type === ImContentType.MERGE && payload"
+ class="inline-flex flex-col gap-0.5 align-middle"
+ >
+ <span>[鑱婂ぉ璁板綍] {{ payload.title }}</span>
+ <span
+ v-for="(line, index) in mergePreviewLines"
+ :key="index"
+ class="truncate text-xs text-gray-400"
+ >
+ {{ line }}
+ </span>
+ </span>
+
+ <span v-else-if="type === ImContentType.RECALL" class="text-xs text-gray-400">
+ [娑堟伅宸叉挙鍥瀅
+ </span>
+ <span v-else-if="type === ImContentType.READ" class="text-xs text-gray-400">
+ [宸茶鍥炴墽]
+ </span>
+ <span v-else-if="type === ImContentType.RECEIPT" class="text-xs text-gray-400">
+ [鍥炴墽]
+ </span>
+ <span v-else-if="isGroupNotification(type ?? -1)" class="text-xs text-gray-400">
+ {{ groupTipText }}
+ </span>
+ <span v-else-if="isFriendChatTip(type ?? -1)" class="text-xs text-gray-400">
+ {{ friendTipText }}
+ </span>
+ <span
+ v-else-if="isRtcCallTip(type ?? -1)"
+ class="inline-flex items-center gap-1.5 text-xs text-gray-400"
+ >
+ <IconifyIcon class="size-4 rotate-[135deg]" icon="ant-design:phone-outlined" />
+ <span>{{ rtcCallTipText }}</span>
+ </span>
+ <span v-else class="whitespace-pre-wrap break-all">{{ fallbackText }}</span>
+</template>
--
Gitblit v1.9.3