import { z } from "zod"; import { validatePublicAppJumpParam } from "@/shared/app-jump/appJumpConfig.js"; const appBannerDisplayScopes = ["home", "room", "recharge", "me"] as const; const requiredH5LinkURLSchema = z .string() .trim() .min(1, "请填写 H5 链接") .max(2048, "H5 链接不能超过 2048 个字符") .refine((value) => !/\s/.test(value), "H5 链接不能包含空白字符"); const h5LinkKeySchema = z .string() .trim() .min(1, "请填写 key") .max(80, "key 不能超过 80 个字符") .regex(/^[A-Za-z0-9_.:-]+$/, "key 只能包含字母、数字、下划线、中横线、点和冒号"); export const h5LinkUpdateSchema = z.object({ key: h5LinkKeySchema, label: z.string().trim().min(1, "请填写配置项").max(80, "配置项不能超过 80 个字符"), url: requiredH5LinkURLSchema, }); export type H5LinkUpdateForm = z.infer; export const exploreTabSchema = z.object({ enabled: z.boolean(), h5Url: requiredH5LinkURLSchema, sortOrder: z.coerce.number().int("排序必须是整数").default(0), tab: z.string().trim().min(1, "请填写 tab").max(40, "tab 不能超过 40 个字符"), }); export type ExploreTabForm = z.infer; export const appBannerSchema = z .object({ bannerType: z.enum(["h5", "app"]), countryCode: z .string() .trim() .transform((value) => value.toUpperCase()) .refine((value) => value === "" || /^[A-Z]{2,3}$/.test(value), "国家编码需为 2-3 位字母"), coverUrl: z .string() .trim() .min(1, "请上传封面图") .max(1024, "封面图地址不能超过 1024 个字符") .refine((value) => !/\s/.test(value), "封面图地址不能包含空白字符"), description: z.string().trim().max(255, "描述不能超过 255 个字符"), displayScope: z.enum(appBannerDisplayScopes).optional(), displayScopes: z.array(z.enum(appBannerDisplayScopes)).min(1, "请选择显示范围"), endsAtMs: z.coerce.number().int().min(0, "投放结束时间不正确").default(0), param: z.string().trim().max(2048, "参数不能超过 2048 个字符"), platform: z.enum(["android", "ios"]), regionId: z.coerce.number().int().min(0, "区域不正确").default(0), roomSmallImageUrl: z .string() .trim() .max(1024, "房间内小屏图地址不能超过 1024 个字符") .refine((value) => !/\s/.test(value), "房间内小屏图地址不能包含空白字符"), sortOrder: z.coerce.number().int("排序必须是整数").default(0), startsAtMs: z.coerce.number().int().min(0, "投放开始时间不正确").default(0), status: z.enum(["active", "disabled", "expired"]), }) .superRefine((value, ctx) => { if (value.bannerType === "h5" && !value.param) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "H5 类型需要填写链接", path: ["param"] }); } if (value.bannerType === "h5" && /\s/.test(value.param)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "H5 链接不能包含空白字符", path: ["param"] }); } if (value.bannerType === "app") { const validation = validatePublicAppJumpParam(value.param); if (!validation.valid) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: validation.message, path: ["param"] }); } } if (value.displayScopes.includes("room") && !value.roomSmallImageUrl) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "房间内显示需要上传房间内小屏图", path: ["roomSmallImageUrl"], }); } if (value.startsAtMs > 0 && value.endsAtMs > 0 && value.startsAtMs >= value.endsAtMs) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "投放结束时间必须晚于开始时间", path: ["endsAtMs"] }); } }); export type AppBannerForm = z.infer; export const appSplashScreenSchema = z .object({ countryCode: z .string() .trim() .transform((value) => value.toUpperCase()) .refine((value) => value === "" || /^[A-Z]{2,3}$/.test(value), "国家编码需为 2-3 位字母"), coverUrl: z .string() .trim() .min(1, "请上传封面图") .max(1024, "封面图地址不能超过 1024 个字符") .refine((value) => !/\s/.test(value), "封面图地址不能包含空白字符"), description: z.string().trim().max(255, "描述不能超过 255 个字符"), displayDurationMs: z.coerce.number().int("展示时长必须是整数毫秒").min(0, "展示时长不能小于 0").default(3000), endsAtMs: z.coerce.number().int().min(0, "投放结束时间不正确").default(0), param: z.string().trim().max(2048, "参数不能超过 2048 个字符"), platform: z.enum(["android", "ios"]), regionId: z.coerce.number().int().min(0, "区域不正确").default(0), sortOrder: z.coerce.number().int("排序必须是整数").default(0), splashType: z.enum(["h5", "app"]), startsAtMs: z.coerce.number().int().min(0, "投放开始时间不正确").default(0), status: z.enum(["active", "disabled", "expired"]), }) .superRefine((value, ctx) => { if (value.splashType === "h5" && !value.param) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "H5 类型需要填写链接", path: ["param"] }); } if (value.splashType === "h5" && /\s/.test(value.param)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "H5 链接不能包含空白字符", path: ["param"] }); } if (value.splashType === "app") { const validation = validatePublicAppJumpParam(value.param); if (!validation.valid) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: validation.message, path: ["param"] }); } } if (value.startsAtMs > 0 && value.endsAtMs > 0 && value.startsAtMs >= value.endsAtMs) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "投放结束时间必须晚于开始时间", path: ["endsAtMs"] }); } }); export type AppSplashScreenForm = z.infer; export const appPopupSchema = z .object({ code: z .string() .trim() .min(1, "请填写弹窗code") .max(80, "弹窗code不能超过 80 个字符") .regex(/^[A-Za-z0-9_.:-]+$/, "弹窗code只能包含字母、数字、下划线、中横线、点和冒号"), displayPeriodDays: z.coerce.number().int("展示周期必须是整数天").min(0, "展示周期不能小于 0").default(0), endsAtMs: z.coerce.number().int().min(0, "生效结束时间不正确").default(0), imageUrl: z .string() .trim() .max(1024, "图片地址不能超过 1024 个字符") .refine((value) => !/\s/.test(value), "图片地址不能包含空白字符"), jumpType: z.enum(["h5", "app"]), jumpUrl: z.string().trim().min(1, "请填写跳转地址").max(2048, "跳转地址不能超过 2048 个字符"), name: z.string().trim().min(1, "请填写弹窗名字").max(80, "弹窗名字不能超过 80 个字符"), sortOrder: z.coerce.number().int("排序必须是整数").default(0), startsAtMs: z.coerce.number().int().min(0, "生效开始时间不正确").default(0), status: z.enum(["active", "disabled", "expired"]), }) .superRefine((value, ctx) => { if (value.jumpType === "h5" && /\s/.test(value.jumpUrl)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "H5 链接不能包含空白字符", path: ["jumpUrl"] }); } if (value.jumpType === "app") { const validation = validatePublicAppJumpParam(value.jumpUrl); if (!validation.valid) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: validation.message, path: ["jumpUrl"] }); } } if (value.startsAtMs > 0 && value.endsAtMs > 0 && value.startsAtMs >= value.endsAtMs) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "生效结束时间必须晚于开始时间", path: ["endsAtMs"] }); } }); export type AppPopupForm = z.infer; export const appVersionSchema = z.object({ buildNumber: z.coerce.number().int("编译版本号必须是整数").positive("编译版本号必须大于 0"), description: z.string().trim().max(1000, "更新描述不能超过 1000 个字符").default(""), downloadUrl: z .string() .trim() .min(1, "请填写下载链接") .max(2048, "下载链接不能超过 2048 个字符") .refine((value) => !/\s/.test(value), "下载链接不能包含空白字符"), forceUpdate: z.boolean(), platform: z.enum(["android", "ios"]), version: z.string().trim().min(1, "请填写版本号").max(40, "版本号不能超过 40 个字符"), }); export type AppVersionForm = z.infer;