+
+
-
- [视频]
+
+
+
+
+
+
+
+ {{ formatDuration(message.duration) }}
-
-
+
+
+
+
+
- 语音{{ message.duration ? ` (${formatDuration(message.duration)})` : '' }}
+ 语音 ({{ formatDuration(message.duration) }})
{{ message.transcription_text }}
-
-
-
-
- 下载文件
-
-
- [文件]
-
+
+
+ {{ message.transcription_text }}
-
+
+
+ {{ message.text || '(空消息)' }}
+
+
+
+
+ [图片]
+
+
+ [视频]
+
+
+ [语音]
+
+
+ [文件]
+
+
+
-
- {{ message.text || `[未知消息${message.type_raw != null ? ` (${message.type_raw})` : ''}]` }}
+
+ [{{ typeTagLabel }}]
diff --git a/yunkes/src/style.css b/yunkes/src/style.css
index 581ba26..034505f 100644
--- a/yunkes/src/style.css
+++ b/yunkes/src/style.css
@@ -214,6 +214,14 @@ a {
display: block;
}
+/* Inline audio player for voice messages with an http URL. Keeps the native
+ * control compact so it sits inside a chat row. */
+.yk-msg__audio {
+ max-width: 240px;
+ height: 32px;
+ display: block;
+}
+
.yk-msg__chip {
display: inline-flex;
align-items: center;
diff --git a/yunkes/src/utils/format.ts b/yunkes/src/utils/format.ts
index 0ee1609..a701691 100644
--- a/yunkes/src/utils/format.ts
+++ b/yunkes/src/utils/format.ts
@@ -46,6 +46,38 @@ export function channelLabel(channel?: string | null): string {
}
}
+// ---------------------------------------------------------------------------
+// WeChat message type labels
+// ---------------------------------------------------------------------------
+
+/**
+ * 中文 label for the upstream WeChat `type_raw` numeric code. The label is
+ * best-effort — different upstream sources disagree on some codes (e.g. 43 is
+ * variously 图片/表情/视频). Rendering is URL-driven (see MessageBubble.vue),
+ * so this label only informs the unknown-fallback bubble and the message
+ * detail drawer tag — never the actual media display.
+ *
+ * WhatsApp type codes (0→文字, 1→图片) are intentionally NOT mapped here:
+ * the structured `type` field already classifies them, and WA media uses a
+ * `mediaName` filename rather than an http URL, so it can't render without the
+ * download endpoint and degrades to a placeholder either way.
+ */
+const WECHAT_TYPE_LABELS: Record
= {
+ 3: '图片',
+ 34: '语音',
+ 42: '名片',
+ 43: '图片/表情',
+ 49: '文件/名片',
+ 9: '表情',
+ 20: '红包/转账',
+}
+
+/** 中文 label for a WeChat type_raw code; `未知(typeRaw)` when unknown. */
+export function wechatTypeLabel(typeRaw?: number | null): string {
+ if (typeRaw == null) return '未知'
+ return WECHAT_TYPE_LABELS[typeRaw] ?? `未知(${typeRaw})`
+}
+
// ---------------------------------------------------------------------------
// Phone / voice call rendering
// ---------------------------------------------------------------------------
diff --git a/yunkes/src/views/ChatTimeline.vue b/yunkes/src/views/ChatTimeline.vue
index a98818c..18eef22 100644
--- a/yunkes/src/views/ChatTimeline.vue
+++ b/yunkes/src/views/ChatTimeline.vue
@@ -6,7 +6,7 @@ import { ArrowLeft } from '@element-plus/icons-vue'
import { getMessages } from '@/api'
import type { Channel, Message } from '@/api/types'
import MessageBubble from '@/components/MessageBubble.vue'
-import { channelLabel, formatTime } from '@/utils/format'
+import { channelLabel, formatTime, wechatTypeLabel } from '@/utils/format'
import { buildMessageSections } from '@/utils/messageFields'
/**
@@ -238,12 +238,12 @@ onMounted(loadInitial)
{{ channelLabel(channelTyped) }}
- {{ drawerMessage.type }}
+ {{ drawerMessage.channel === 'wechat' ? wechatTypeLabel(drawerMessage.type_raw) : (drawerMessage.type || '消息') }}
({{ drawerMessage.type_raw }})