- 新增 safeLocalStorage 工具函数增强本地存储安全性 - 简化认证流程,重构用户数据结构和 token 管理 - 修复多个模块的 TypeScript 类型错误和导入问题 - 优化状态管理,重构各模块 store 结构 - 清理冗余代码,移除未使用的组件和函数 - 改进错误处理和边界情况处理 - 更新配置文件以支持最新的类型检查 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
271 lines
7.7 KiB
TypeScript
271 lines
7.7 KiB
TypeScript
/**
|
||
* filekorolheader: URL参数处理工具函数 - 通用URL参数解析和优先级管理
|
||
* 功能:从浏览器URL读取参数、参数优先级处理、类型安全的参数转换
|
||
* 路径:/utils/urlParams
|
||
* 规范:遵循crop-x/docs/开发项目规范.md,TypeScript类型安全,泛型支持
|
||
*/
|
||
|
||
// URL参数解析配置接口
|
||
export interface UrlParamsConfig<T extends Record<string, any>> {
|
||
// 搜索字段配置,定义哪些字段需要从URL中读取
|
||
searchFields: {
|
||
[K in keyof T]?: {
|
||
type: 'string' | 'number' | 'boolean';
|
||
// URL参数名映射,如果不配置则使用字段名
|
||
urlKey?: string;
|
||
// 默认值
|
||
default?: T[K];
|
||
// 是否必需字段
|
||
required?: boolean;
|
||
};
|
||
};
|
||
// 分页字段配置
|
||
paginationFields?: {
|
||
page?: {
|
||
urlKey?: string;
|
||
default?: number;
|
||
};
|
||
size?: {
|
||
urlKey?: string;
|
||
default?: number;
|
||
};
|
||
};
|
||
}
|
||
|
||
// URL参数解析结果接口
|
||
export interface UrlParamsResult<T extends Record<string, any>> {
|
||
// 搜索参数
|
||
searchParams: Partial<T>;
|
||
// 分页参数
|
||
paginationParams: {
|
||
page: number;
|
||
size: number;
|
||
};
|
||
// 是否包含任何URL参数
|
||
hasUrlParams: boolean;
|
||
// 原始URL参数字符串
|
||
rawUrlParams: string;
|
||
}
|
||
|
||
/**
|
||
* 通用URL参数解析函数
|
||
*
|
||
* @param config URL参数配置
|
||
* @returns 解析后的URL参数结果
|
||
*
|
||
* @example
|
||
* interface MyParams {
|
||
* search: string;
|
||
* status: string;
|
||
* category: string;
|
||
* }
|
||
*
|
||
* const config: UrlParamsConfig<MyParams> = {
|
||
* searchFields: {
|
||
* search: { type: 'string', urlKey: 'search', default: '' },
|
||
* status: { type: 'string', urlKey: 'audit_status', default: 'all' },
|
||
* category: { type: 'string', default: 'all' }
|
||
* },
|
||
* paginationFields: {
|
||
* page: { urlKey: 'page', default: 1 },
|
||
* size: { urlKey: 'size', default: 10 }
|
||
* }
|
||
* };
|
||
*
|
||
* const result = parseUrlParams(config);
|
||
* console.log(result.searchParams); // { search: 'keyword', status: 'pending', category: 'all' }
|
||
* console.log(result.paginationParams); // { page: 2, size: 20 }
|
||
*/
|
||
export function parseUrlParams<T extends Record<string, any>>(
|
||
config: UrlParamsConfig<T>
|
||
): UrlParamsResult<T> {
|
||
// 检查是否在浏览器环境
|
||
if (typeof window === 'undefined') {
|
||
return {
|
||
searchParams: {} as Partial<T>,
|
||
paginationParams: {
|
||
page: config.paginationFields?.page?.default ?? 1,
|
||
size: config.paginationFields?.size?.default ?? 10
|
||
},
|
||
hasUrlParams: false,
|
||
rawUrlParams: ''
|
||
};
|
||
}
|
||
|
||
try {
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const rawUrlParams = urlParams.toString();
|
||
|
||
const searchParams: Partial<T> = {};
|
||
let hasUrlParams = rawUrlParams !== '';
|
||
|
||
// 解析搜索字段
|
||
Object.entries(config.searchFields).forEach(([fieldKey, fieldConfig]) => {
|
||
if (!fieldConfig) return;
|
||
|
||
const urlKey = fieldConfig.urlKey || fieldKey;
|
||
const rawValue = urlParams.get(urlKey);
|
||
|
||
if (rawValue !== null) {
|
||
let parsedValue: any = rawValue;
|
||
|
||
// 类型转换
|
||
switch (fieldConfig.type) {
|
||
case 'number':
|
||
parsedValue = parseInt(rawValue, 10);
|
||
if (isNaN(parsedValue)) {
|
||
parsedValue = fieldConfig.default;
|
||
}
|
||
break;
|
||
case 'boolean':
|
||
parsedValue = rawValue === 'true' || rawValue === '1';
|
||
break;
|
||
case 'string':
|
||
default:
|
||
parsedValue = rawValue;
|
||
break;
|
||
}
|
||
|
||
(searchParams as any)[fieldKey] = parsedValue;
|
||
} else if (fieldConfig.default !== undefined) {
|
||
(searchParams as any)[fieldKey] = fieldConfig.default;
|
||
} else if (fieldConfig.required) {
|
||
console.warn(`Required URL parameter '${urlKey}' is missing`);
|
||
}
|
||
});
|
||
|
||
// 解析分页字段
|
||
const pageValue = urlParams.get(config.paginationFields?.page?.urlKey || 'page');
|
||
const sizeValue = urlParams.get(config.paginationFields?.size?.urlKey || 'size');
|
||
|
||
const page = pageValue ? Math.max(1, parseInt(pageValue, 10) || 1) : (config.paginationFields?.page?.default ?? 1);
|
||
const size = sizeValue ? Math.max(1, parseInt(sizeValue, 10) || 10) : (config.paginationFields?.size?.default ?? 10);
|
||
|
||
const paginationParams = { page, size };
|
||
|
||
return {
|
||
searchParams,
|
||
paginationParams,
|
||
hasUrlParams,
|
||
rawUrlParams
|
||
};
|
||
} catch (error) {
|
||
console.error('Failed to parse URL parameters:', error);
|
||
|
||
return {
|
||
searchParams: {} as Partial<T>,
|
||
paginationParams: {
|
||
page: config.paginationFields?.page?.default ?? 1,
|
||
size: config.paginationFields?.size?.default ?? 10
|
||
},
|
||
hasUrlParams: false,
|
||
rawUrlParams: ''
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 参数优先级合并函数
|
||
* 优先级:URL参数 > 函数传入参数 > 默认状态
|
||
*
|
||
* @param urlParams 从URL解析的参数
|
||
* @param functionParams 函数传入的参数
|
||
* @param defaultState 默认状态
|
||
* @returns 合并后的参数
|
||
*/
|
||
export function mergeParamsWithPriority<T extends Record<string, any>>(
|
||
urlParams: Partial<T>,
|
||
functionParams: Partial<T> = {},
|
||
defaultState: T
|
||
): T {
|
||
const result = { ...defaultState };
|
||
|
||
// 先合并函数参数(第二优先级)
|
||
Object.keys(result).forEach(key => {
|
||
if (functionParams[key as keyof T] !== undefined) {
|
||
(result as any)[key] = functionParams[key as keyof T];
|
||
}
|
||
});
|
||
|
||
// 再合并URL参数(第一优先级)
|
||
Object.keys(urlParams).forEach(key => {
|
||
if (urlParams[key as keyof T] !== undefined && urlParams[key as keyof T] !== '') {
|
||
(result as any)[key] = urlParams[key as keyof T];
|
||
}
|
||
});
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 分页参数优先级合并函数
|
||
*
|
||
* @param urlPagination 从URL解析的分页参数
|
||
* @param functionPagination 函数传入的分页参数
|
||
* @param defaultPagination 默认分页状态
|
||
* @param resetPage 是否重置到第一页
|
||
* @returns 合并后的分页参数
|
||
*/
|
||
export function mergePaginationWithPriority(
|
||
urlPagination: { page: number; size: number },
|
||
functionPagination: { page?: number; size?: number } = {},
|
||
defaultPagination: { page: number; size: number },
|
||
resetPage: boolean = false
|
||
): { page: number; size: number } {
|
||
const page = resetPage
|
||
? 1
|
||
: (urlPagination.page || functionPagination.page || defaultPagination.page);
|
||
|
||
const size = urlPagination.size || functionPagination.size || defaultPagination.size;
|
||
|
||
return { page: Math.max(1, page), size: Math.max(1, size) };
|
||
}
|
||
|
||
/**
|
||
* 审核历史页面专用的URL参数配置
|
||
*/
|
||
export const AUDIT_HISTORY_URL_CONFIG: UrlParamsConfig<{
|
||
search: string;
|
||
action: string;
|
||
audit_status: string;
|
||
date_range: string;
|
||
}> = {
|
||
searchFields: {
|
||
search: { type: 'string', default: '' },
|
||
action: { type: 'string', urlKey: 'action', default: 'all' },
|
||
audit_status: { type: 'string', urlKey: 'audit_status', default: 'all' },
|
||
date_range: { type: 'string', urlKey: 'date_range', default: 'all' }
|
||
},
|
||
paginationFields: {
|
||
page: { urlKey: 'page', default: 1 },
|
||
size: { urlKey: 'size', default: 10 }
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 企业审核页面专用的URL参数配置
|
||
*/
|
||
export const ENTERPRISE_AUDIT_URL_CONFIG: UrlParamsConfig<{
|
||
search: string;
|
||
audit_status: string;
|
||
}> = {
|
||
searchFields: {
|
||
search: { type: 'string', default: '' },
|
||
audit_status: { type: 'string', urlKey: 'audit_status', default: 'all' }
|
||
},
|
||
paginationFields: {
|
||
page: { urlKey: 'page', default: 1 },
|
||
size: { urlKey: 'size', default: 10 }
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 默认URL参数配置(通用)
|
||
*/
|
||
export const DEFAULT_URL_CONFIG: UrlParamsConfig<Record<string, any>> = {
|
||
searchFields: {},
|
||
paginationFields: {
|
||
page: { urlKey: 'page', default: 1 },
|
||
size: { urlKey: 'size', default: 10 }
|
||
}
|
||
}; |