feat(ops,store,partner): 用户权益列 + 门店合同多图上传

- admin 用户列表新增剩余/已用/累计好客权益金额三列(benefitCoupon groupBy 聚合,排除 VOID)
- admin/partner 门店入驻合同支持多张照片与 PDF(CommonResource 多记录,复用 ENV 多图逻辑)
- 新增 contract-urls.util 归一化工具,向后兼容旧 contractUrl 字段并标记 deprecated

需求1/2/3
This commit is contained in:
2026-08-12 22:48:02 +08:00
parent 478291e2b3
commit 88053353ab
13 changed files with 356 additions and 59 deletions
+25 -4
View File
@@ -28,7 +28,8 @@ export type StoreDraftForm = {
benefitUsageRule: string;
coverUrl: string;
envPhotoUrls: string[];
contractUrl: string;
/** 签约合同,支持多张照片 / PDF */
contractUrls: string[];
bankAccountName: string;
bankAccountNo: string;
bankBranch: string;
@@ -70,7 +71,7 @@ export const defaultStoreForm = (): StoreDraftForm => ({
benefitUsageRule: '',
coverUrl: '',
envPhotoUrls: ['', '', ''],
contractUrl: '',
contractUrls: [],
bankAccountName: '',
bankAccountNo: '',
bankBranch: '',
@@ -88,6 +89,9 @@ function timeToMinutes(hhmm: string): number {
export const MIN_ENV_PHOTO_COUNT = 3;
/** 签约合同最多可上传的照片 / PDF 数量 */
export const MAX_CONTRACT_COUNT = 20;
export function normalizeStringArray(urls: unknown, minLen: number): string[] {
const arr = Array.isArray(urls) ? urls.map((u) => String(u ?? '')) : [];
while (arr.length < minLen) arr.push('');
@@ -118,6 +122,22 @@ export function normalizeStoreDraftForm(raw: Partial<StoreDraftForm> | null | un
closeTime2: String(raw.closeTime2 ?? base.closeTime2),
avgPrice: String(raw.avgPrice ?? base.avgPrice),
envPhotoUrls: normalizeStringArray(raw.envPhotoUrls, MIN_ENV_PHOTO_COUNT),
// 兼容旧草稿:单个 contractUrl 迁移为数组
contractUrls: (() => {
const list = Array.isArray(raw.contractUrls)
? raw.contractUrls
: [(raw as { contractUrl?: unknown }).contractUrl];
const seen = new Set<string>();
const out: string[] = [];
for (const item of list) {
const url = String(item ?? '').trim();
if (!url || seen.has(url)) continue;
seen.add(url);
out.push(url);
if (out.length >= MAX_CONTRACT_COUNT) break;
}
return out;
})(),
packages: Array.isArray(raw.packages)
? raw.packages.map((p, i) => ({
name: String((p as { name?: string }).name ?? ''),
@@ -224,12 +244,13 @@ export function validateStoreStep1(
}
export function validateStoreStep2(
form: Pick<StoreDraftForm, 'coverUrl' | 'envPhotoUrls' | 'contractUrl'>,
form: Pick<StoreDraftForm, 'coverUrl' | 'envPhotoUrls' | 'contractUrls'>,
): string | null {
if (!form.coverUrl.trim()) return '请上传门头照';
const envCount = form.envPhotoUrls.filter((u) => u.trim()).length;
if (envCount < MIN_ENV_PHOTO_COUNT) return `请上传至少 ${MIN_ENV_PHOTO_COUNT} 张环境照片`;
if (!form.contractUrl.trim()) return '请上传签约合同';
const contractCount = (form.contractUrls ?? []).filter((u) => u.trim()).length;
if (contractCount < 1) return '请上传签约合同';
return null;
}