refactor: 优化认证系统和组件类型安全性
- 新增 safeLocalStorage 工具函数增强本地存储安全性 - 简化认证流程,重构用户数据结构和 token 管理 - 修复多个模块的 TypeScript 类型错误和导入问题 - 优化状态管理,重构各模块 store 结构 - 清理冗余代码,移除未使用的组件和函数 - 改进错误处理和边界情况处理 - 更新配置文件以支持最新的类型检查 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
110
src/utils/storage.ts
Normal file
110
src/utils/storage.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 安全的本地存储工具 - 解决SSR环境localStorage不存在的问题
|
||||
*/
|
||||
|
||||
// 安全的localStorage操作
|
||||
export const safeLocalStorage = {
|
||||
// 获取数据
|
||||
getItem(key: string): string | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
} catch (error) {
|
||||
console.warn('Failed to get item from localStorage:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// 设置数据
|
||||
setItem(key: string, value: string): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
} catch (error) {
|
||||
console.warn('Failed to set item to localStorage:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 删除数据
|
||||
removeItem(key: string): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch (error) {
|
||||
console.warn('Failed to remove item from localStorage:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 清空所有数据
|
||||
clear(): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
localStorage.clear();
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear localStorage:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 安全的sessionStorage操作
|
||||
export const safeSessionStorage = {
|
||||
// 获取数据
|
||||
getItem(key: string): string | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return sessionStorage.getItem(key);
|
||||
} catch (error) {
|
||||
console.warn('Failed to get item from sessionStorage:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// 设置数据
|
||||
setItem(key: string, value: string): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sessionStorage.setItem(key, value);
|
||||
} catch (error) {
|
||||
console.warn('Failed to set item to sessionStorage:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 删除数据
|
||||
removeItem(key: string): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sessionStorage.removeItem(key);
|
||||
} catch (error) {
|
||||
console.warn('Failed to remove item from sessionStorage:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 清空所有数据
|
||||
clear(): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sessionStorage.clear();
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear sessionStorage:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 检查是否在浏览器环境中
|
||||
export const isBrowser = typeof window !== 'undefined';
|
||||
271
src/utils/urlParams.ts
Normal file
271
src/utils/urlParams.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* 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 }
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user