fix: render chat media by URL (images/video/voice) instead of unknown-type placeholder

MessageBubble.vue was keys off the (incomplete) type code, so many messages
with real OSS media URLs (https://wx.oss.yunkecn.com/.../*.webp|*.mp4) showed
as '[未知消息]'. Rendering is now URL-driven: pick the best http URL from
file_url > file_hd_url > file_th_url, then render by extension
(image/video/audio/file). Type code is only used as a last-resort label.

- MessageBubble.vue: URL-driven cascade (URL by ext → duration →
  transcription → text → structured-type placeholder → localized tag).
  Image/video/audio/file children @click.stop so the all-fields drawer
  still opens on bubble click but not on media interactions.
- format.ts: wechatTypeLabel(type_raw) maps known codes (3→图片, 34→语音,
  43→图片/表情, 49→文件/名片, 42→名片, 9→表情, 20→红包/转账).
- ChatTimeline.vue drawer type tag uses wechatTypeLabel for WeChat msgs.
- style.css: compact .yk-msg__audio player for voice-with-URL messages.

Verified against smoke.db: 25 type-3 images + 3 type-43 videos that
previously showed '[未知消息]' now render as images/video. Build clean.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-05 10:23:01 +08:00
parent 0d466c4e4a
commit 8fde1d8597
4 changed files with 192 additions and 58 deletions
+32
View File
@@ -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<number, string> = {
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
// ---------------------------------------------------------------------------