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:
54
src/components/messageStream/utils/albumLayout.test.ts
Normal file
54
src/components/messageStream/utils/albumLayout.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { computeAlbumLayout } from "./albumLayout";
|
||||
|
||||
describe("computeAlbumLayout", () => {
|
||||
it("returns null for fewer than 2 images", () => {
|
||||
expect(computeAlbumLayout([])).toBeNull();
|
||||
expect(computeAlbumLayout([1.5])).toBeNull();
|
||||
});
|
||||
|
||||
it("places two portraits side by side (1 row, 2 cols)", () => {
|
||||
const layout = computeAlbumLayout([0.7, 0.8])!;
|
||||
expect(layout.gridTemplateRows).toBe("1fr");
|
||||
expect(layout.tiles).toHaveLength(2);
|
||||
// Both tiles occupy the single row, adjacent columns.
|
||||
expect(layout.tiles[0]).toMatchObject({ colStart: 1, colEnd: 2 });
|
||||
expect(layout.tiles[1]).toMatchObject({ colStart: 2, colEnd: 3 });
|
||||
// Album width = sum of ratios when height = 1.
|
||||
expect(layout.aspectRatio).toBeCloseTo(0.7 + 0.8, 4);
|
||||
});
|
||||
|
||||
it("stacks two landscapes vertically (2 rows, 1 col)", () => {
|
||||
const layout = computeAlbumLayout([1.6, 1.4])!;
|
||||
expect(layout.gridTemplateColumns).toBe("1fr");
|
||||
expect(layout.tiles[0]).toMatchObject({ rowStart: 1, rowEnd: 2 });
|
||||
expect(layout.tiles[1]).toMatchObject({ rowStart: 2, rowEnd: 3 });
|
||||
expect(layout.aspectRatio).toBeCloseTo(1 / (1 / 1.6 + 1 / 1.4), 4);
|
||||
});
|
||||
|
||||
it("puts a portrait primary on the left with the rest stacked right", () => {
|
||||
const layout = computeAlbumLayout([0.6, 1.2, 1.3])!;
|
||||
// Primary spans both right-hand rows in column 1.
|
||||
expect(layout.tiles[0]).toEqual({
|
||||
colStart: 1,
|
||||
colEnd: 2,
|
||||
rowStart: 1,
|
||||
rowEnd: 3,
|
||||
});
|
||||
expect(layout.tiles[1]).toMatchObject({ colStart: 2, rowStart: 1 });
|
||||
expect(layout.tiles[2]).toMatchObject({ colStart: 2, rowStart: 2 });
|
||||
});
|
||||
|
||||
it("puts a landscape primary on top with the rest in a row below", () => {
|
||||
const layout = computeAlbumLayout([1.8, 0.9, 1.1])!;
|
||||
// Primary spans both bottom columns in row 1.
|
||||
expect(layout.tiles[0]).toEqual({
|
||||
colStart: 1,
|
||||
colEnd: 3,
|
||||
rowStart: 1,
|
||||
rowEnd: 2,
|
||||
});
|
||||
expect(layout.tiles[1]).toMatchObject({ rowStart: 2, colStart: 1 });
|
||||
expect(layout.tiles[2]).toMatchObject({ rowStart: 2, colStart: 2 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user