feat: 媒体流图片自适应显示(单图/2图/Telegram式相册)

- 单图气泡按真实比例显示:横图限高260px、过高裁上下;竖图完整铺满宽度不裁、无黑边
- 2张同类相册:都竖图左右并排、都横图上下堆叠,按比例不裁
- 3+张相册:Telegram式马赛克拼贴(竖主图占左+其余堆右 / 横主图占顶+其余排底)
- 图片比例优先用后端width/height,缺失时从加载后的naturalWidth/Height读取
- 新增 constants/media.ts 统一尺寸规范;albumLayout 纯算法附单测

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
TerryM
2026-05-29 22:16:55 +08:00
parent a7792c117d
commit 0035457c6d
10 changed files with 431 additions and 58 deletions

39
src/constants/media.ts Normal file
View File

@@ -0,0 +1,39 @@
/**
* Media sizing spec (single source of truth).
*
* Goal: show images at their real aspect ratio (no top/bottom cropping) while
* keeping them bounded so an oversized upload can't take over the screen.
*
* Width is intentionally NOT touched here: a single image still fills 100% of
* the bubble, whose width stays bounded by the existing message-container
* max-widths. Only the height adapts (and is clamped).
*
* How it's applied to a single-image bubble (behaviour depends on orientation):
* - Landscape / square (width >= height): the frame fills the full width and
* follows the real ratio, capped at SINGLE_IMAGE_MAX_HEIGHT. Taller-than-cap
* images are cropped top/bottom (object-cover) — never any left/right bars.
* - Portrait (height > width): no height cap. The frame follows the real ratio
* so the whole image shows — full width, uncropped, no side bars — which
* makes the bubble tall. (Product chose "complete image" over "compact".)
* - When width/height are unknown (no backend value and not yet loaded), we
* fall back to the legacy fixed heights below so layout stays predictable.
*/
/** Height cap for landscape/square images (cropped top/bottom beyond this). */
export const SINGLE_IMAGE_MAX_HEIGHT = 260;
/**
* Telegram-style multi-image albums. Cells are sized to each image's real
* ratio; the whole mosaic is capped so a tall portrait-led album stays
* reasonable (cells crop slightly via object-cover beyond the cap).
*/
export const ALBUM_MAX_HEIGHT = 420;
/** Gap between album tiles, in px. */
export const ALBUM_GAP = 3;
/**
* Legacy responsive heights, used only when an attachment has no width/height.
* Mirrors the previous fixed-height behaviour.
*/
export const SINGLE_IMAGE_FALLBACK_HEIGHT_CLASS =
"h-[180px] min-[440px]:h-[210px] md:h-[260px] lg:h-[300px]";