feat: 媒体库 blob 去重存储与品牌/注册等前端调整
新增: - blob_store 二进制去重存储 + blob_dedup 测试 其它: - 后端 settings/auth/media_library/upload/operations 服务与 handler 调整 - 前端品牌(BrandLockup/BrandCropModal/BrandSeoPanel)、注册、媒体库、分析详情页、Header/SiteChrome 等更新
This commit is contained in:
@@ -28,8 +28,8 @@ export type BrandSlot = "logo_light" | "logo_dark" | "favicon";
|
||||
/** 页眉品牌形式 */
|
||||
export type BrandMark = "image_text" | "image" | "text";
|
||||
|
||||
/** 图槽画幅。方标是默认;其余是页眉里常见的横版比例 */
|
||||
export type BrandLogoSize = "sq" | "2x1" | "3x1" | "4x1" | "16x9";
|
||||
/** 图槽画幅。方标是默认;其余是页眉里常见的横版比例;auto=原图比例自适应宽度 */
|
||||
export type BrandLogoSize = "sq" | "2x1" | "3x1" | "4x1" | "16x9" | "auto";
|
||||
|
||||
export const BRAND_LOGO_SIZES: { id: BrandLogoSize; label: string; w: number; h: number }[] = [
|
||||
{ id: "sq", label: "方标", w: 36, h: 36 },
|
||||
@@ -37,6 +37,7 @@ export const BRAND_LOGO_SIZES: { id: BrandLogoSize; label: string; w: number; h:
|
||||
{ id: "3x1", label: "3:1", w: 120, h: 40 },
|
||||
{ id: "4x1", label: "4:1", w: 176, h: 44 },
|
||||
{ id: "16x9", label: "16:9", w: 96, h: 54 },
|
||||
{ id: "auto", label: "原图", w: 0, h: 40 },
|
||||
];
|
||||
|
||||
export function normalizeBrandMark(raw: unknown): BrandMark {
|
||||
@@ -47,10 +48,38 @@ export function normalizeBrandMark(raw: unknown): BrandMark {
|
||||
|
||||
export function normalizeBrandLogoSize(raw: unknown): BrandLogoSize {
|
||||
const s = String(raw ?? "").trim();
|
||||
if (s === "sq" || s === "2x1" || s === "3x1" || s === "4x1" || s === "16x9") return s;
|
||||
if (s === "sq" || s === "2x1" || s === "3x1" || s === "4x1" || s === "16x9" || s === "auto") return s;
|
||||
return "sq";
|
||||
}
|
||||
|
||||
/** 各固定画幅的宽高比(auto 不在此表,代表自由比例) */
|
||||
export const BRAND_LOGO_SIZE_ASPECT: Partial<Record<BrandLogoSize, number>> = {
|
||||
sq: 1,
|
||||
"2x1": 2,
|
||||
"3x1": 3,
|
||||
"4x1": 4,
|
||||
"16x9": 16 / 9,
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据图片原始比例推荐画幅:
|
||||
* 与某个固定画幅差距 ≤15% 时推荐该画幅,否则推荐 auto(原图自适应)。
|
||||
*/
|
||||
export function suggestLogoSize(naturalW: number, naturalH: number): BrandLogoSize {
|
||||
if (!naturalW || !naturalH) return "auto";
|
||||
const ratio = naturalW / naturalH;
|
||||
let best: BrandLogoSize = "sq";
|
||||
let bestDelta = Infinity;
|
||||
for (const [id, a] of Object.entries(BRAND_LOGO_SIZE_ASPECT) as [BrandLogoSize, number][]) {
|
||||
const delta = Math.abs(ratio - a) / a;
|
||||
if (delta < bestDelta) {
|
||||
bestDelta = delta;
|
||||
best = id;
|
||||
}
|
||||
}
|
||||
return bestDelta <= 0.15 ? best : "auto";
|
||||
}
|
||||
|
||||
export function brandLogoBox(size: BrandLogoSize): { w: number; h: number } {
|
||||
const found = BRAND_LOGO_SIZES.find((item) => item.id === size);
|
||||
return found ? { w: found.w, h: found.h } : { w: 36, h: 36 };
|
||||
@@ -72,6 +101,13 @@ export function normalizeBrandLogoFit(raw: unknown): BrandLogoFit {
|
||||
return "contain";
|
||||
}
|
||||
|
||||
/** 标语是否在页眉显示;缺省 true(与后端缺省一致) */
|
||||
export function normalizeBrandSloganVisible(raw: unknown): boolean {
|
||||
if (typeof raw === "boolean") return raw;
|
||||
if (typeof raw === "string") return raw.trim().toLowerCase() !== "false";
|
||||
return true;
|
||||
}
|
||||
|
||||
export const BRAND_LIMITS = {
|
||||
wordmark: 16,
|
||||
slogan: 32,
|
||||
|
||||
Reference in New Issue
Block a user