From 5367b3b4d92588c4e76728e6bd4ad6aae0cbb967 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 23 七月 2026 13:05:25 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0

---
 src/views/im/home/pages/conversation/components/message/material-bubble.vue |  170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 170 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/pages/conversation/components/message/material-bubble.vue b/src/views/im/home/pages/conversation/components/message/material-bubble.vue
new file mode 100644
index 0000000..9f75df4
--- /dev/null
+++ b/src/views/im/home/pages/conversation/components/message/material-bubble.vue
@@ -0,0 +1,170 @@
+<script lang="ts" setup>
+import { computed, ref } from 'vue'
+
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+import { openSafeUrl } from '#/packages/utils/src'
+
+import { Modal, Spin } from 'ant-design-vue'
+
+import { getChannelMaterial } from '#/api/im/channel/material'
+import { useChannelStore } from '#/views/im/home/store/channelStore'
+import { useConversationStore } from '#/views/im/home/store/conversationStore'
+import { ImConversationType } from '#/views/im/utils/constants'
+import { type MaterialMessage, parseMessage } from '#/views/im/utils/message'
+
+const props = defineProps<{
+  content: string
+}>()
+
+const conversationStore = useConversationStore()
+const channelStore = useChannelStore()
+
+/** 褰撳墠鏄惁鍦ㄥ叕浼楀彿 / 棰戦亾浼氳瘽閲岋細鍐冲畾璧板ぇ鍗$墖杩樻槸绱у噾杞彂鍗$墖 */
+const isChannelView = computed(
+  () => conversationStore.activeConversation?.type === ImConversationType.CHANNEL
+)
+
+/** 鍙嶅簭鍒楀寲 content JSON 涓� payload 瀵硅薄 */
+const payload = computed<MaterialMessage>(
+  () => parseMessage<MaterialMessage>(props.content) ?? {}
+)
+
+/** 鏉ユ簮棰戦亾锛涚揣鍑戝崱搴曢儴娓叉煋澶村儚 + 鍚嶇О */
+const sourceChannel = computed(() =>
+  payload.value.channelId ? channelStore.getChannel(payload.value.channelId) : undefined
+)
+
+const detailVisible = ref(false)
+const detailLoading = ref(false)
+const detailHtml = ref('')
+
+// 鐐瑰嚮琛屼负锛歶rl 闈炵┖璺冲閾撅紱涓虹┖鍒欐寜 payload.materialId 鎷夊瘜鏂囨湰姝f枃锛屽叏灞� dialog 娓叉煋
+const onClick = async () => {
+  if (payload.value.url) {
+    openSafeUrl(payload.value.url)
+    return
+  }
+  if (!payload.value.materialId) {
+    return
+  }
+  detailVisible.value = true
+  detailLoading.value = true
+  detailHtml.value = ''
+  try {
+    const material = await getChannelMaterial(payload.value.materialId)
+    detailHtml.value = material?.content ?? ''
+  } catch (error) {
+    console.error('[Material] 鎷夊彇姝f枃澶辫触', error)
+  } finally {
+    detailLoading.value = false
+  }
+}
+</script>
+
+<template>
+  <!-- 鍏紬鍙蜂細璇濆唴澶у崱鐗囷細瀵归綈寰俊鍏紬鍙峰崟鍥炬枃鍗★紙灏侀潰 9:5 + 涓嬫柟鐧藉簳鍔犵矖鏍囬鏉★級 -->
+  <div
+    v-if="isChannelView"
+    class="material-card cursor-pointer w-full overflow-hidden rounded-lg bg-[var(--ant-color-bg-container)] border border-solid border-[var(--im-border-color-lighter)]"
+    @click="onClick"
+  >
+    <img v-if="payload.coverUrl" class="block w-full h-[200px] object-cover" :src="payload.coverUrl" />
+    <div class="px-3.5 py-3 text-15px font-600 leading-[1.4] text-[var(--ant-color-text)] line-clamp-2">
+      {{ payload.title || '(鏃犳爣棰�)' }}
+    </div>
+  </div>
+
+  <!-- 绉佽亰 / 缇よ亰閲岃杞彂鐨勭礌鏉愮揣鍑戝崱鐗囷細鏍囬 + 鎽樿鍦ㄥ乏銆佸皬灏侀潰鍦ㄥ彸銆佸簳閮ㄩ閬撳ご鍍� + 鍚嶇О锛堝榻愬井淇″叕浼楀彿杞彂鍗★級 -->
+  <div
+    v-else
+    class="material-card cursor-pointer flex flex-col w-[260px] px-3.5 pt-3 pb-2.5 rounded-lg bg-[var(--ant-color-bg-container)] border border-solid border-[var(--im-border-color-lighter)]"
+    @click="onClick"
+  >
+    <div class="flex gap-2.5 items-start">
+      <div class="flex flex-1 flex-col gap-1.5 min-w-0">
+        <div class="text-15px font-600 leading-[1.4] text-[var(--ant-color-text)] line-clamp-2 break-all">
+          {{ payload.title || '(鏃犳爣棰�)' }}
+        </div>
+        <div
+          v-if="payload.summary"
+          class="text-12px leading-[1.5] text-[var(--ant-color-text-secondary)] line-clamp-2 break-all"
+        >
+          {{ payload.summary }}
+        </div>
+      </div>
+      <img
+        v-if="payload.coverUrl"
+        class="flex-shrink-0 w-[60px] h-[60px] object-cover rounded bg-[var(--ant-color-fill-secondary)]"
+        :src="payload.coverUrl"
+      />
+    </div>
+    <div class="flex items-center gap-1.5 mt-2.5 pt-2 border-t border-t-solid border-[var(--im-border-color-lighter)] text-12px text-[var(--ant-color-text-secondary)]">
+      <img
+        v-if="sourceChannel?.avatar"
+        class="w-4 h-4 rounded-full object-cover flex-shrink-0"
+        :src="sourceChannel.avatar"
+      />
+      <Icon v-else icon="ep:promotion" :size="14" />
+      <span class="truncate">{{ sourceChannel?.name || '棰戦亾娑堟伅' }}</span>
+    </div>
+  </div>
+
+  <!-- 瀵屾枃鏈鎯咃細鍏ㄥ睆寮圭獥鎸夐渶鎸傝浇锛屽叧闂悗閲婃斁瀵屾枃鏈� DOM -->
+  <Modal
+    v-model:open="detailVisible"
+    :title="payload.title || '璇︽儏'"
+    :footer="null"
+    width="100vw"
+    wrap-class-name="im-material-detail-modal"
+    destroy-on-close
+  >
+    <Spin :spinning="detailLoading" wrapper-class-name="w-full">
+      <div class="material-detail-body max-w-[720px] mx-auto px-5 pt-6 pb-20 min-h-[60vh]">
+        <div class="text-[22px] font-600 leading-[1.4] text-[var(--ant-color-text)] mb-5">
+          {{ payload.title || '' }}
+        </div>
+        <div
+          v-if="detailHtml"
+          class="article-content text-15px leading-[1.75] text-[var(--ant-color-text)]"
+          v-dompurify-html="detailHtml"
+        ></div>
+        <div
+          v-else-if="!detailLoading"
+          class="text-center text-[var(--ant-color-text-secondary)] mt-20"
+        >
+          鏆傛棤姝f枃
+        </div>
+      </div>
+    </Spin>
+  </Modal>
+</template>
+
+<style scoped lang="scss">
+/* hover 闃村奖 + transition锛氬悎鍐欎竴澶勶紝鍐欐硶鏇寸揣鍑� */
+.material-card {
+  transition: box-shadow 0.15s ease;
+
+  &:hover {
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  }
+}
+
+/* :deep 绌块�� v-dompurify-html 娓叉煋鐨勫唴宓� DOM锛涚粺涓�鎺у埗瀵屾枃鏈噷鐨� img / p / hN 鎺掔増 */
+.article-content {
+  :deep(img) {
+    max-width: 100%;
+    height: auto;
+  }
+
+  :deep(p) {
+    margin: 12px 0;
+  }
+
+  :deep(h1),
+  :deep(h2),
+  :deep(h3) {
+    margin: 20px 0 12px;
+    font-weight: 600;
+  }
+}
+</style>

--
Gitblit v1.9.3