124 lines
3.5 KiB
TypeScript
124 lines
3.5 KiB
TypeScript
/**
|
|
* 类型化路由常量
|
|
*
|
|
* 关键设计:
|
|
* - `as const` 让每个值都是字符串字面量类型,可用于 `PageProps<'/literal'>`。
|
|
* - 不导出 Client-only 模块(`use-auth-gate` 走独立路径导入),保证 Server bundle 不被污染。
|
|
* - 动态查询路由通过 `ROUTE_BUILDERS` 函数生成,避免手拼字符串。
|
|
*/
|
|
|
|
import type { PayChannel } from "@/data/schemas/payment";
|
|
import type { AppSubscriptionReturnTo } from "@/router/navigation-types";
|
|
import type { CharacterProfile } from "@/data/constants/character";
|
|
import {
|
|
PAYMENT_ANALYTICS_ENTRY_PARAM,
|
|
PAYMENT_ANALYTICS_REASON_PARAM,
|
|
type PaymentAnalyticsContext,
|
|
} from "@/lib/analytics/payment_analytics_context";
|
|
|
|
/** 静态路由字面量 */
|
|
export const ROUTES = {
|
|
root: "/",
|
|
splash: "/splash",
|
|
chat: "/chat",
|
|
privateRoom: "/private-room",
|
|
tip: "/tip",
|
|
externalEntry: "/external-entry",
|
|
auth: "/auth",
|
|
sidebar: "/sidebar",
|
|
feedback: "/feedback",
|
|
subscription: "/subscription",
|
|
coinsRules: "/coins-rules",
|
|
} as const;
|
|
|
|
export type StaticRoute = (typeof ROUTES)[keyof typeof ROUTES];
|
|
|
|
export interface CharacterRoutes {
|
|
readonly splash: string;
|
|
readonly chat: string;
|
|
readonly privateRoom: string;
|
|
readonly tip: string;
|
|
}
|
|
|
|
export function getCharacterRoutes(
|
|
characterSlug: CharacterProfile["slug"],
|
|
): CharacterRoutes {
|
|
const root = `/characters/${encodeURIComponent(characterSlug)}`;
|
|
return {
|
|
splash: `${root}/splash`,
|
|
chat: `${root}/chat`,
|
|
privateRoom: `${root}/private-room`,
|
|
tip: `${root}/tip`,
|
|
};
|
|
}
|
|
|
|
export type RouteSearchParams = Record<
|
|
string,
|
|
string | readonly string[] | undefined
|
|
>;
|
|
|
|
export function appendRouteSearchParams(
|
|
pathname: string,
|
|
searchParams: RouteSearchParams,
|
|
): string {
|
|
const params = new URLSearchParams();
|
|
for (const [key, value] of Object.entries(searchParams)) {
|
|
if (typeof value === "string") {
|
|
params.set(key, value);
|
|
continue;
|
|
}
|
|
value?.forEach((item) => params.append(key, item));
|
|
}
|
|
const query = params.toString();
|
|
return query ? `${pathname}?${query}` : pathname;
|
|
}
|
|
|
|
/** 动态查询路由构造器 */
|
|
export const ROUTE_BUILDERS = {
|
|
subscription: (
|
|
type: "vip" | "topup",
|
|
options: {
|
|
payChannel?: PayChannel;
|
|
returnTo?: Exclude<AppSubscriptionReturnTo, null>;
|
|
sourceCharacterSlug?: string;
|
|
analytics?: PaymentAnalyticsContext;
|
|
} = {},
|
|
): `/subscription?${string}` => {
|
|
const params = new URLSearchParams({ type });
|
|
if (options.payChannel) params.set("payChannel", options.payChannel);
|
|
if (options.returnTo) params.set("returnTo", options.returnTo);
|
|
if (options.sourceCharacterSlug) {
|
|
params.set("character", options.sourceCharacterSlug);
|
|
}
|
|
if (options.analytics) {
|
|
params.set(
|
|
PAYMENT_ANALYTICS_ENTRY_PARAM,
|
|
options.analytics.entryPoint,
|
|
);
|
|
params.set(
|
|
PAYMENT_ANALYTICS_REASON_PARAM,
|
|
options.analytics.triggerReason,
|
|
);
|
|
}
|
|
return `${ROUTES.subscription}?${params.toString()}` as const;
|
|
},
|
|
authWithRedirect: (redirectTo: string): `/auth?redirect=${string}` =>
|
|
`${ROUTES.auth}?redirect=${encodeURIComponent(redirectTo)}` as const,
|
|
} as const;
|
|
|
|
/** 所有静态路由,供 `proxy.ts` matcher 与未来 sitemap 使用 */
|
|
export const ALL_STATIC_ROUTES: readonly StaticRoute[] = [
|
|
ROUTES.splash,
|
|
ROUTES.chat,
|
|
ROUTES.privateRoom,
|
|
ROUTES.tip,
|
|
ROUTES.externalEntry,
|
|
ROUTES.auth,
|
|
ROUTES.sidebar,
|
|
ROUTES.feedback,
|
|
ROUTES.coinsRules,
|
|
] as const;
|
|
|
|
/** 联合路由类型 */
|
|
export type Route = StaticRoute;
|