121 行
3.7 KiB
Plaintext
121 行
3.7 KiB
Plaintext
/**
|
|
* OmniCard AI (CRMmp) - 移动端 App / H5 智能数字名片 Universal Link 深度链接路由分发解析器
|
|
* 严格遵循 AGENTS.md 与 uts 强类型跨端开发规范
|
|
*/
|
|
|
|
export interface DeepLinkRouteInfo {
|
|
isValid: boolean;
|
|
cardId?: number;
|
|
cardSlug?: string;
|
|
sceneMode: 'business' | 'social' | 'exhibition';
|
|
locale: string;
|
|
focusTab?: string;
|
|
source: string;
|
|
rawUrl?: string;
|
|
}
|
|
|
|
/**
|
|
* 解析 URL 参数为 Key-Value 映射
|
|
*/
|
|
function parseQueryParams(queryString: string): Record<string, string> {
|
|
const params: Record<string, string> = {};
|
|
if (!queryString) return params;
|
|
|
|
const pairs = queryString.replace(/^\?/, '').split('&');
|
|
for (const pair of pairs) {
|
|
if (!pair) continue;
|
|
const [key, value] = pair.split('=');
|
|
if (key) {
|
|
params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : '';
|
|
}
|
|
}
|
|
return params;
|
|
}
|
|
|
|
/**
|
|
* 解析 Universal Link 或 App Custom Scheme
|
|
*/
|
|
export function parseDeepLink(url?: string): DeepLinkRouteInfo {
|
|
const fallback: DeepLinkRouteInfo = {
|
|
isValid: false,
|
|
sceneMode: 'business',
|
|
locale: 'en',
|
|
source: 'direct',
|
|
};
|
|
|
|
if (!url || typeof url !== 'string' || !url.trim()) {
|
|
return fallback;
|
|
}
|
|
|
|
const clean = url.trim();
|
|
|
|
// 1. 检查是否为 Universal Link (如 https://omnicard.ai/p/xxx 或 /c/xxx)
|
|
if (clean.startsWith('http://') || clean.startsWith('https://')) {
|
|
const isDomainMatch = clean.includes('omnicard.ai') || clean.includes('omni.me') || clean.includes('/p/');
|
|
if (!isDomainMatch) {
|
|
return fallback;
|
|
}
|
|
|
|
const questionIdx = clean.indexOf('?');
|
|
const pathPart = questionIdx !== -1 ? clean.slice(0, questionIdx) : clean;
|
|
const queryPart = questionIdx !== -1 ? clean.slice(questionIdx + 1) : '';
|
|
|
|
const query = parseQueryParams(queryPart);
|
|
|
|
// 提取 slug (例如 /p/alex-zheng -> alex-zheng)
|
|
const pMatch = pathPart.match(/\/p\/([^/?#]+)/);
|
|
const slug = pMatch ? pMatch[1] : undefined;
|
|
|
|
return {
|
|
isValid: true,
|
|
cardSlug: slug,
|
|
cardId: query.id ? Number(query.id) : undefined,
|
|
sceneMode: (query.scene === 'social' || query.scene === 'exhibition') ? query.scene : 'exhibition',
|
|
locale: query.lang || 'en',
|
|
focusTab: query.tab,
|
|
source: query.from || 'direct',
|
|
rawUrl: clean,
|
|
};
|
|
}
|
|
|
|
// 2. 检查是否为 App Scheme (omnicard://card/view?id=...)
|
|
if (clean.startsWith('omnicard://')) {
|
|
const questionIdx = clean.indexOf('?');
|
|
const queryPart = questionIdx !== -1 ? clean.slice(questionIdx + 1) : '';
|
|
const query = parseQueryParams(queryPart);
|
|
|
|
return {
|
|
isValid: true,
|
|
cardId: query.id ? Number(query.id) : undefined,
|
|
cardSlug: query.slug,
|
|
sceneMode: (query.scene === 'social' || query.scene === 'exhibition') ? query.scene : 'business',
|
|
locale: query.lang || 'en',
|
|
focusTab: query.tab,
|
|
source: query.from || 'direct',
|
|
rawUrl: clean,
|
|
};
|
|
}
|
|
|
|
return fallback;
|
|
}
|
|
|
|
/**
|
|
* 将深度链接解析结果构建为应用内路由路径
|
|
*/
|
|
export function buildAppPageRoute(info: DeepLinkRouteInfo): string {
|
|
if (!info || !info.isValid) {
|
|
return '/pages/owner/hall/index';
|
|
}
|
|
|
|
const queryParts: string[] = [];
|
|
if (info.cardSlug) queryParts.push(`slug=${encodeURIComponent(info.cardSlug)}`);
|
|
if (info.cardId) queryParts.push(`id=${info.cardId}`);
|
|
if (info.sceneMode) queryParts.push(`scene=${info.sceneMode}`);
|
|
if (info.locale) queryParts.push(`lang=${info.locale}`);
|
|
if (info.focusTab) queryParts.push(`tab=${info.focusTab}`);
|
|
if (info.source) queryParts.push(`source=${info.source}`);
|
|
|
|
const queryString = queryParts.length > 0 ? `?${queryParts.join('&')}` : '';
|
|
return `/pages/portal/index${queryString}`;
|
|
}
|