feat(ops): v4.0.7 HQ 活动图快链与勾选导出
HQ 指定一张活动图为勾选主合伙人合成 PNG/zip;城市合伙人页增加快链与单下/导出。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { activityPosterQrSlotPx } from './activity-poster';
|
||||
import {
|
||||
activityPosterPackFileName,
|
||||
activityPosterQrSlotPx,
|
||||
activityPosterTemplateTooLarge,
|
||||
} from './activity-poster';
|
||||
|
||||
describe('activityPosterQrSlotPx', () => {
|
||||
it('uses width for square size', () => {
|
||||
@@ -18,3 +22,45 @@ describe('activityPosterQrSlotPx', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('activityPosterTemplateTooLarge', () => {
|
||||
it('allows 1080x1920', () => {
|
||||
expect(activityPosterTemplateTooLarge(1080, 1920)).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects edge over 2500', () => {
|
||||
expect(activityPosterTemplateTooLarge(2501, 1000)).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects pixel count over 4e6', () => {
|
||||
expect(activityPosterTemplateTooLarge(2500, 2000)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activityPosterPackFileName', () => {
|
||||
it('uses city, company, id', () => {
|
||||
expect(activityPosterPackFileName({
|
||||
cityName: '郑州',
|
||||
companyName: '杜康合伙',
|
||||
partnerName: '张三',
|
||||
partnerId: '12',
|
||||
})).toBe('郑州_杜康合伙_12.png');
|
||||
});
|
||||
|
||||
it('falls back to partner name when company empty', () => {
|
||||
expect(activityPosterPackFileName({
|
||||
cityName: '洛阳',
|
||||
companyName: ' ',
|
||||
partnerName: '李四',
|
||||
partnerId: '9',
|
||||
})).toBe('洛阳_李四_9.png');
|
||||
});
|
||||
|
||||
it('strips path separators', () => {
|
||||
expect(activityPosterPackFileName({
|
||||
cityName: 'a/b',
|
||||
companyName: 'c:d',
|
||||
partnerId: '1',
|
||||
})).toBe('a-b_c-d_1.png');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,6 +55,39 @@ export type ActivityPosterSelectionRequest = {
|
||||
posterId?: string | null;
|
||||
};
|
||||
|
||||
export type ActivityPosterPackRequest = {
|
||||
partnerIds: string[];
|
||||
};
|
||||
|
||||
/** 底图最长边(px);超出则拒绝合成,避免单进程 Sharp 撑爆内存 */
|
||||
export const ACTIVITY_POSTER_MAX_EDGE = 2500;
|
||||
/** 底图像素上限(宽×高) */
|
||||
export const ACTIVITY_POSTER_MAX_PIXELS = 4_000_000;
|
||||
/** 单次 zip 最多合伙人数 */
|
||||
export const ACTIVITY_POSTER_PACK_MAX_PARTNERS = 200;
|
||||
|
||||
export function activityPosterTemplateTooLarge(width: number, height: number): boolean {
|
||||
const w = Math.max(0, Math.round(width));
|
||||
const h = Math.max(0, Math.round(height));
|
||||
return w > ACTIVITY_POSTER_MAX_EDGE || h > ACTIVITY_POSTER_MAX_EDGE || w * h > ACTIVITY_POSTER_MAX_PIXELS;
|
||||
}
|
||||
|
||||
/** zip / 单张下载文件名:`{城市}_{公司或姓名}_{id}.png` */
|
||||
export function activityPosterPackFileName(input: {
|
||||
cityName?: string | null;
|
||||
companyName?: string | null;
|
||||
partnerName?: string | null;
|
||||
partnerId: string;
|
||||
}): string {
|
||||
const label = (input.companyName?.trim() || input.partnerName?.trim() || '').trim();
|
||||
const raw = [input.cityName?.trim(), label, input.partnerId.trim()]
|
||||
.filter(Boolean)
|
||||
.join('_')
|
||||
.replace(/[\\/:*?"<>|]+/g, '-')
|
||||
.replace(/\s+/g, '');
|
||||
return `${raw || input.partnerId}.png`;
|
||||
}
|
||||
|
||||
/** 码栏百分比 → 像素。size 相对图宽,保证正方形;落点夹在画布内。 */
|
||||
export function activityPosterQrSlotPx(
|
||||
width: number,
|
||||
|
||||
Reference in New Issue
Block a user