feat(round-14): GDPR脱敏被遗忘权/展会海报排版/全球雷达热力散点/DeepLink深度路由 (95套件1053测试全绿)

这个提交包含在:
wikiboss
2026-09-21 02:11:17 +08:00
父节点 749ccf2e01
当前提交 45f5f74805
共有 8 个文件被更改,包括 892 次插入 和 0 次删除

查看文件

@@ -0,0 +1,194 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
export type PosterAspectRatio = 'story_9_16' | 'feed_4_5';
export interface PosterCanvasDimensions {
width: number;
height: number;
aspectRatio: '9:16' | '4:5';
}
export interface PosterLayoutOptions {
aspectRatio?: PosterAspectRatio;
cardName: string;
title?: string;
company?: string;
eventName?: string;
boothNumber?: string;
qrCodeSize?: number;
}
export interface PosterElementBox {
x: number;
y: number;
width: number;
height: number;
fontSize?: number;
isMultiLine?: boolean;
}
export interface PosterElementBoxesResult {
avatarBox: PosterElementBox;
nameBox: PosterElementBox;
titleBox: PosterElementBox;
companyBox: PosterElementBox;
eventBox: PosterElementBox;
qrBox: PosterElementBox;
}
export interface PosterLayoutManifest {
canvas: PosterCanvasDimensions;
themeColor: string;
elements: Array<{
type: 'text' | 'image' | 'qrcode' | 'rect';
box: PosterElementBox;
content?: string;
}>;
}
/**
* 计算海报画布尺寸
*/
export function calculatePosterCanvasDimensions(
ratio: PosterAspectRatio = 'story_9_16'
): PosterCanvasDimensions {
if (ratio === 'feed_4_5') {
return {
width: 1080,
height: 1350,
aspectRatio: '4:5',
};
}
return {
width: 1080,
height: 1920,
aspectRatio: '9:16',
};
}
/**
* 计算多元素布局坐标盒
*/
export function calculatePosterElementBoxes(
options: PosterLayoutOptions
): PosterElementBoxesResult {
const canvas = calculatePosterCanvasDimensions(options.aspectRatio);
const W = canvas.width;
const H = canvas.height;
const qrSize = options.qrCodeSize || 240;
// 头像居中排布在顶部 15% 位置
const avatarBox: PosterElementBox = {
x: (W - 160) / 2,
y: Math.round(H * 0.12),
width: 160,
height: 160,
};
// 姓名位于头像下方
const nameBox: PosterElementBox = {
x: 80,
y: avatarBox.y + avatarBox.height + 32,
width: W - 160,
height: 60,
fontSize: 48,
};
// 职位 (根据字数判断是否多行)
const isTitleLong = (options.title || '').length > 35;
const titleBox: PosterElementBox = {
x: 80,
y: nameBox.y + nameBox.height + 16,
width: W - 160,
height: isTitleLong ? 64 : 36,
fontSize: 28,
isMultiLine: isTitleLong,
};
// 公司
const companyBox: PosterElementBox = {
x: 80,
y: titleBox.y + titleBox.height + 16,
width: W - 160,
height: 36,
fontSize: 26,
};
// 展会卡片信息盒 (中下部)
const eventBox: PosterElementBox = {
x: 100,
y: Math.round(H * 0.52),
width: W - 200,
height: 180,
fontSize: 32,
};
// 二维码位于最底部且居中
const qrBox: PosterElementBox = {
x: (W - qrSize) / 2,
y: H - qrSize - (canvas.aspectRatio === '9:16' ? 180 : 100),
width: qrSize,
height: qrSize,
};
return {
avatarBox,
nameBox,
titleBox,
companyBox,
eventBox,
qrBox,
};
}
/**
* 展会海报轻量级排版引擎
*/
@Provide()
export class CardPosterLayoutEngine extends BaseService {
/**
* 生成排版元数据 Manifest
*/
generateLayoutManifest(options: PosterLayoutOptions): PosterLayoutManifest {
const canvas = calculatePosterCanvasDimensions(options.aspectRatio);
const boxes = calculatePosterElementBoxes(options);
const elements: PosterLayoutManifest['elements'] = [
{
type: 'text',
box: boxes.nameBox,
content: options.cardName,
},
{
type: 'text',
box: boxes.titleBox,
content: options.title,
},
{
type: 'text',
box: boxes.companyBox,
content: options.company,
},
{
type: 'qrcode',
box: boxes.qrBox,
content: 'https://omnicard.ai/p/card',
},
];
if (options.eventName) {
elements.push({
type: 'rect',
box: boxes.eventBox,
content: `${options.eventName} | ${options.boothNumber || ''}`,
});
}
return {
canvas,
themeColor: '#0B5CFF', // 钴蓝主色
elements,
};
}
}