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:
2025-11-13 14:52:52 +08:00
parent dfc29ce01f
commit 68d9d97142
29 changed files with 1122 additions and 3001 deletions

110
src/utils/storage.ts Normal file
View 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
View File

@@ -0,0 +1,271 @@
/**
* filekorolheader: URL参数处理工具函数 - 通用URL参数解析和优先级管理
* 功能从浏览器URL读取参数、参数优先级处理、类型安全的参数转换
* 路径:/utils/urlParams
* 规范遵循crop-x/docs/开发项目规范.mdTypeScript类型安全泛型支持
*/
// 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 }
}
};