feat(ops): v4.0.2 活动图模板、合伙人选择持久化与用户管理主图
HQ 上传底图与码栏;合伙人单选写入 partner_account.activity_poster_id,用户管理下次登录仍显示同一张图。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { activityPosterQrSlotPx } from './activity-poster';
|
||||
|
||||
describe('activityPosterQrSlotPx', () => {
|
||||
it('uses width for square size', () => {
|
||||
expect(activityPosterQrSlotPx(1000, 2000, 10, 20, 18)).toEqual({
|
||||
left: 100,
|
||||
top: 400,
|
||||
size: 180,
|
||||
});
|
||||
});
|
||||
|
||||
it('clamps slot inside the canvas', () => {
|
||||
expect(activityPosterQrSlotPx(100, 80, 90, 90, 30)).toEqual({
|
||||
left: 70,
|
||||
top: 50,
|
||||
size: 30,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
export const ACTIVITY_POSTER_STATUSES = ['ACTIVE', 'DISABLED'] as const;
|
||||
export type ActivityPosterStatus = (typeof ACTIVITY_POSTER_STATUSES)[number];
|
||||
|
||||
export const ACTIVITY_POSTER_STATUS_LABELS: Record<ActivityPosterStatus, string> = {
|
||||
ACTIVE: '上架',
|
||||
DISABLED: '下架',
|
||||
};
|
||||
|
||||
export const DEFAULT_ACTIVITY_POSTER_QR_SLOT = {
|
||||
qrXPct: 78,
|
||||
qrYPct: 78,
|
||||
qrSizePct: 18,
|
||||
} as const;
|
||||
|
||||
export type ActivityPosterQrSlot = {
|
||||
qrXPct: number;
|
||||
qrYPct: number;
|
||||
qrSizePct: number;
|
||||
};
|
||||
|
||||
export type ActivityPosterItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
copyText: string;
|
||||
imageUrl: string;
|
||||
qrXPct: number;
|
||||
qrYPct: number;
|
||||
qrSizePct: number;
|
||||
sortOrder: number;
|
||||
status: ActivityPosterStatus;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type ActivityPosterUpsertRequest = {
|
||||
title: string;
|
||||
copyText?: string;
|
||||
imageUrl: string;
|
||||
qrXPct: number;
|
||||
qrYPct: number;
|
||||
qrSizePct: number;
|
||||
sortOrder?: number;
|
||||
status?: ActivityPosterStatus;
|
||||
};
|
||||
|
||||
export type ActivityPosterStatusRequest = {
|
||||
status: ActivityPosterStatus;
|
||||
};
|
||||
|
||||
export type ActivityPosterSelection = {
|
||||
posterId: string | null;
|
||||
};
|
||||
|
||||
export type ActivityPosterSelectionRequest = {
|
||||
posterId?: string | null;
|
||||
};
|
||||
|
||||
/** 码栏百分比 → 像素。size 相对图宽,保证正方形;落点夹在画布内。 */
|
||||
export function activityPosterQrSlotPx(
|
||||
width: number,
|
||||
height: number,
|
||||
qrXPct: number,
|
||||
qrYPct: number,
|
||||
qrSizePct: number,
|
||||
): { left: number; top: number; size: number } {
|
||||
const w = Math.max(1, Math.round(width));
|
||||
const h = Math.max(1, Math.round(height));
|
||||
const size = Math.max(1, Math.min(w, Math.round((qrSizePct / 100) * w)));
|
||||
const left = Math.max(0, Math.min(w - size, Math.round((qrXPct / 100) * w)));
|
||||
const top = Math.max(0, Math.min(h - size, Math.round((qrYPct / 100) * h)));
|
||||
return { left, top, size };
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export const HQ_LIST_COLUMN_KEYS = [
|
||||
'orders',
|
||||
'promo-codes',
|
||||
'promo-code-users',
|
||||
'activity-posters',
|
||||
'wecom-bots',
|
||||
'wecom-pushes',
|
||||
'llm-configs',
|
||||
|
||||
@@ -6,6 +6,7 @@ export const HQ_PERMISSION_CATALOG = [
|
||||
{ key: 'products', label: '商品管理', group: '业务' },
|
||||
{ key: 'orders', label: '订单管理', group: '业务' },
|
||||
{ key: 'promo_codes', label: '推广码', group: '业务' },
|
||||
{ key: 'activity_posters', label: '活动图', group: '业务' },
|
||||
{ key: 'stores', label: '门店列表', group: '业务' },
|
||||
{ key: 'store_audits', label: '门店审核', group: '业务' },
|
||||
{ key: 'store_ratings', label: '门店评价', group: '业务' },
|
||||
@@ -178,6 +179,7 @@ export const HQ_ROLE_DEFAULT_PERMISSIONS: Record<HqAdminRoleValue, HqPermissionK
|
||||
'products',
|
||||
'orders',
|
||||
'promo_codes',
|
||||
'activity_posters',
|
||||
...OPS_STORE_KEYS,
|
||||
'store_categories_delete',
|
||||
'partners',
|
||||
|
||||
@@ -22,6 +22,7 @@ export * from './hq-permissions';
|
||||
export * from './hq-list-columns';
|
||||
export * from './partner';
|
||||
export * from './partner-assoc';
|
||||
export * from './activity-poster';
|
||||
export * from './shop';
|
||||
export * from './city-partner';
|
||||
export * from './city-warehouse';
|
||||
|
||||
@@ -16,6 +16,8 @@ export type PartnerAssocSummary = {
|
||||
userCount: number;
|
||||
companyName?: string | null;
|
||||
name: string;
|
||||
/** 所选活动图;空表示用户管理主图只用关联码 */
|
||||
activityPosterId?: string | null;
|
||||
};
|
||||
|
||||
export type PartnerAssocStats = {
|
||||
|
||||
Reference in New Issue
Block a user