2026-06-17 23:13:03 +08:00

192 lines
9.2 KiB
TypeScript

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<typeof h5LinkUpdateSchema>;
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<typeof exploreTabSchema>;
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<typeof appBannerSchema>;
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<typeof appSplashScreenSchema>;
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<typeof appPopupSchema>;
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<typeof appVersionSchema>;