feat: 添加@types/jest依赖并优化类型安全
refactor: 重构代码减少any类型使用,增加类型定义 fix: 修复TypeScript编译错误和类型不匹配问题 docs: 更新代码审查修复总结文档 style: 优化代码格式和注释 perf: 添加性能优化工具函数和虚拟滚动组件 test: 完善测试相关配置和类型定义 build: 更新package-lock.json文件
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
"umi": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"msw": "^2.12.13",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -287,14 +287,15 @@ const HierarchySelector: React.FC<HierarchySelectorProps> = ({
|
||||
};
|
||||
|
||||
// 处理Cascader选择
|
||||
const handleCascaderChange = (selectedPath: string[]) => {
|
||||
setSelectedKeys(selectedPath);
|
||||
const handleCascaderChange = (selectedPath: string | string[]) => {
|
||||
const pathArray = Array.isArray(selectedPath) ? selectedPath : [];
|
||||
setSelectedKeys(pathArray);
|
||||
|
||||
const selection: HierarchySelection = {
|
||||
hierarchyPath: selectedPath.join('/'),
|
||||
hierarchyPath: pathArray.join('/'),
|
||||
};
|
||||
|
||||
selectedPath.forEach((id, index) => {
|
||||
pathArray.forEach((id, index) => {
|
||||
if (index === 0) selection.tenantId = id;
|
||||
else if (index === 1) selection.departmentId = id;
|
||||
else if (index === 2) selection.shopId = id;
|
||||
@@ -364,7 +365,7 @@ const HierarchySelector: React.FC<HierarchySelectorProps> = ({
|
||||
style={{ flex: 1 }}
|
||||
options={convertToCascaderOptions(hierarchyData)}
|
||||
value={selectedKeys}
|
||||
onChange={handleCascaderChange as any}
|
||||
onChange={handleCascaderChange}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
showSearch
|
||||
|
||||
@@ -115,7 +115,9 @@ interface Widget {
|
||||
id: string;
|
||||
type: 'metric' | 'chart' | 'table' | 'alert';
|
||||
title: string;
|
||||
config: any;
|
||||
config: {
|
||||
[key: string]: any;
|
||||
};
|
||||
position: { x: number; y: number; w: number; h: number };
|
||||
}
|
||||
|
||||
@@ -178,7 +180,7 @@ export const Analytics: React.FC = () => {
|
||||
const [dashboardModalVisible, setDashboardModalVisible] = useState(false);
|
||||
const [exportModalVisible, setExportModalVisible] = useState(false);
|
||||
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
|
||||
const [selectedItem, setSelectedItem] = useState<any>(null);
|
||||
const [selectedItem, setSelectedItem] = useState<unknown>(null);
|
||||
|
||||
const [alertForm] = Form.useForm();
|
||||
const [dashboardForm] = Form.useForm();
|
||||
@@ -342,8 +344,8 @@ export const Analytics: React.FC = () => {
|
||||
message.success('告警规则已删除');
|
||||
};
|
||||
|
||||
const handleViewDetail = (item: any, type: string) => {
|
||||
setSelectedItem({ ...item, type });
|
||||
const handleViewDetail = (item: unknown, type: string) => {
|
||||
setSelectedItem({ ...(item as object), type });
|
||||
setDetailDrawerVisible(true);
|
||||
};
|
||||
|
||||
@@ -480,7 +482,7 @@ export const Analytics: React.FC = () => {
|
||||
<Option key={key} value={key}>{PLATFORM_MAP[key].text}</Option>
|
||||
))}
|
||||
</Select>
|
||||
<RangePicker onChange={(dates) => setDateRange(dates as any)} />
|
||||
<RangePicker onChange={(dates) => setDateRange(dates ? [dates[0]?.format('YYYY-MM-DD') || '', dates[1]?.format('YYYY-MM-DD') || ''] : null)} />
|
||||
<Button icon={<ReloadOutlined />} onClick={handleRefresh}>刷新</Button>
|
||||
<Button type="primary" icon={<DownloadOutlined />} onClick={handleExport}>导出</Button>
|
||||
</Space>
|
||||
@@ -719,7 +721,7 @@ export const Analytics: React.FC = () => {
|
||||
>
|
||||
{selectedItem && (
|
||||
<Descriptions bordered column={1}>
|
||||
{Object.entries(selectedItem).map(([key, value]) => (
|
||||
{Object.entries(selectedItem as object).map(([key, value]) => (
|
||||
<Descriptions.Item key={key} label={key}>
|
||||
{typeof value === 'object' ? JSON.stringify(value) : String(value)}
|
||||
</Descriptions.Item>
|
||||
|
||||
@@ -15,7 +15,7 @@ const Ads: React.FC = () => {
|
||||
platform: '',
|
||||
status: '',
|
||||
search: '',
|
||||
dateRange: null as any,
|
||||
dateRange: null as [Date, Date] | null,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -117,7 +117,7 @@ const Ads: React.FC = () => {
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: any, record: Ad) => (
|
||||
render: (_: unknown, record: Ad) => (
|
||||
<div>
|
||||
{record.status === 'active' ? (
|
||||
<Button
|
||||
|
||||
@@ -158,8 +158,8 @@ export const MaterialUpload: React.FC = () => {
|
||||
type: file.type?.startsWith('image') ? 'image' :
|
||||
file.type?.startsWith('video') ? 'video' : 'document',
|
||||
size: file.size || 0,
|
||||
url: URL.createObjectURL(file as any),
|
||||
thumbnail: file.type?.startsWith('image') ? URL.createObjectURL(file as any) : undefined,
|
||||
url: URL.createObjectURL(file.originFileObj || file as Blob),
|
||||
thumbnail: file.type?.startsWith('image') ? URL.createObjectURL(file.originFileObj || file as Blob) : undefined,
|
||||
uploadedAt: new Date().toISOString(),
|
||||
uploadedBy: 'Current User',
|
||||
status: 'READY',
|
||||
@@ -210,7 +210,7 @@ export const MaterialUpload: React.FC = () => {
|
||||
dataIndex: 'thumbnail',
|
||||
key: 'thumbnail',
|
||||
width: 80,
|
||||
render: (_: any, record: MaterialFile) => (
|
||||
render: (_: unknown, record: MaterialFile) => (
|
||||
record.type === 'image' ? (
|
||||
<Image
|
||||
src={record.thumbnail}
|
||||
@@ -279,7 +279,7 @@ export const MaterialUpload: React.FC = () => {
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
width: 120,
|
||||
render: (_: any, record: MaterialFile) => (
|
||||
render: (_: unknown, record: MaterialFile) => (
|
||||
<Space>
|
||||
<Tooltip title="Preview">
|
||||
<Button type="text" icon={<EyeOutlined />} onClick={() => handlePreview(record)} />
|
||||
|
||||
@@ -210,7 +210,7 @@ const ReturnMonitor: React.FC = () => {
|
||||
const [filters, setFilters] = useState({
|
||||
platform: '',
|
||||
status: '',
|
||||
dateRange: null as any,
|
||||
dateRange: null as [dayjs.Dayjs, dayjs.Dayjs] | null,
|
||||
minReturnRate: 0,
|
||||
maxReturnRate: 100,
|
||||
});
|
||||
@@ -293,7 +293,7 @@ const ReturnMonitor: React.FC = () => {
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
render: (_: any, record: ReturnData) => (
|
||||
render: (_: unknown, record: ReturnData) => (
|
||||
<Space size="middle">
|
||||
<Button type="link" icon={<ExclamationCircleOutlined />} onClick={() => handleViewDetail(record)}>
|
||||
Details
|
||||
@@ -318,7 +318,13 @@ const ReturnMonitor: React.FC = () => {
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleFilter = (values: any) => {
|
||||
const handleFilter = (values: {
|
||||
platform?: string;
|
||||
status?: string;
|
||||
dateRange?: [dayjs.Dayjs, dayjs.Dayjs] | null;
|
||||
minReturnRate?: number;
|
||||
maxReturnRate?: number;
|
||||
}) => {
|
||||
setFilters({
|
||||
platform: values.platform || '',
|
||||
status: values.status || '',
|
||||
|
||||
@@ -407,7 +407,7 @@ class MockArbitrageDataSource implements IArbitrageDataSource {
|
||||
taxCost: 1,
|
||||
exchangeRate: 0.14
|
||||
},
|
||||
risk_level: ['LOW', 'MEDIUM', 'HIGH'][Math.floor(Math.random() * 3)] as any,
|
||||
risk_level: ['LOW', 'MEDIUM', 'HIGH'][Math.floor(Math.random() * 3)] as 'LOW' | 'MEDIUM' | 'HIGH' | 'BLOCK',
|
||||
opportunity_score: Math.floor(Math.random() * 40) + 50,
|
||||
status: 'PENDING',
|
||||
execution_mode: 'MANUAL',
|
||||
|
||||
@@ -168,7 +168,7 @@ class MockCertificateDataSource extends BaseMockDataSource<Certificate, Certific
|
||||
}
|
||||
|
||||
const updates: Partial<Certificate> = {
|
||||
status: status as any,
|
||||
status: status as 'APPROVED' | 'PENDING' | 'REJECTED' | 'EXPIRED',
|
||||
};
|
||||
|
||||
if (status === 'APPROVED') {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// FE-OP003: 前端性能优化
|
||||
import React from 'react';
|
||||
|
||||
// 防抖函数
|
||||
export const debounce = <T extends (...args: any[]) => any>(
|
||||
@@ -34,11 +33,6 @@ export const throttle = <T extends (...args: any[]) => any>(
|
||||
};
|
||||
};
|
||||
|
||||
// 懒加载组件
|
||||
export const lazyLoadComponent = (component: () => Promise<any>) => {
|
||||
return React.lazy(component);
|
||||
};
|
||||
|
||||
// 图片懒加载
|
||||
export const lazyLoadImage = (img: HTMLImageElement, src: string) => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
@@ -113,100 +107,4 @@ export const measurePerformance = (name: string, fn: () => void) => {
|
||||
fn();
|
||||
const end = performance.now();
|
||||
console.log(`${name} took ${end - start}ms`);
|
||||
};
|
||||
|
||||
// 优化渲染性能
|
||||
export const useOptimizedRender = (deps: any[]) => {
|
||||
return React.useMemo(() => deps, deps);
|
||||
};
|
||||
|
||||
// 优化列表渲染
|
||||
export const optimizeListRender = <T>(
|
||||
items: T[],
|
||||
renderItem: (item: T, index: number) => React.ReactNode,
|
||||
keyExtractor: (item: T) => string | number
|
||||
) => {
|
||||
return items.map((item, index) => {
|
||||
const key = keyExtractor(item);
|
||||
return React.useMemo(() => renderItem(item, index), [item, index, renderItem]);
|
||||
});
|
||||
};
|
||||
|
||||
// 虚拟滚动组件
|
||||
export const VirtualList: React.FC<{
|
||||
items: any[];
|
||||
itemHeight: number;
|
||||
containerHeight: number;
|
||||
renderItem: (item: any, index: number) => React.ReactNode;
|
||||
keyExtractor: (item: any) => string | number;
|
||||
}> = ({ items, itemHeight, containerHeight, renderItem, keyExtractor }) => {
|
||||
const [scrollTop, setScrollTop] = React.useState(0);
|
||||
|
||||
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
setScrollTop(e.currentTarget.scrollTop);
|
||||
};
|
||||
|
||||
const startIndex = Math.floor(scrollTop / itemHeight);
|
||||
const endIndex = Math.min(
|
||||
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
|
||||
items.length
|
||||
);
|
||||
|
||||
const visibleItems = items.slice(startIndex, endIndex);
|
||||
const offsetY = startIndex * itemHeight;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: containerHeight,
|
||||
overflowY: 'auto',
|
||||
position: 'relative',
|
||||
}}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: items.length * itemHeight,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: offsetY,
|
||||
left: 0,
|
||||
right: 0,
|
||||
}}
|
||||
>
|
||||
{visibleItems.map((item, index) => {
|
||||
const actualIndex = startIndex + index;
|
||||
return (
|
||||
<div
|
||||
key={keyExtractor(item)}
|
||||
style={{
|
||||
height: itemHeight,
|
||||
}}
|
||||
>
|
||||
{renderItem(item, actualIndex)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
debounce,
|
||||
throttle,
|
||||
lazyLoadComponent,
|
||||
lazyLoadImage,
|
||||
memoize,
|
||||
batchProcess,
|
||||
preloadResource,
|
||||
measurePerformance,
|
||||
useOptimizedRender,
|
||||
optimizeListRender,
|
||||
VirtualList,
|
||||
};
|
||||
};
|
||||
124
dashboard/src/utils/PerformanceOptimization.tsx
Normal file
124
dashboard/src/utils/PerformanceOptimization.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
// FE-OP003: 前端性能优化
|
||||
import React from 'react';
|
||||
|
||||
// 懒加载组件
|
||||
export const lazyLoadComponent = (component: () => Promise<any>) => {
|
||||
return React.lazy(component);
|
||||
};
|
||||
|
||||
// 优化渲染性能
|
||||
export const useOptimizedRender = (deps: any[]) => {
|
||||
return React.useMemo(() => deps, deps);
|
||||
};
|
||||
|
||||
// 优化列表渲染
|
||||
export const optimizeListRender = <T>(
|
||||
items: T[],
|
||||
renderItem: (item: T, index: number) => React.ReactNode,
|
||||
keyExtractor: (item: T) => string | number
|
||||
) => {
|
||||
return items.map((item, index) => {
|
||||
const key = keyExtractor(item);
|
||||
return React.useMemo(() => renderItem(item, index), [item, index, renderItem]);
|
||||
});
|
||||
};
|
||||
|
||||
// 虚拟滚动组件
|
||||
export const VirtualList: React.FC<{
|
||||
items: any[];
|
||||
itemHeight: number;
|
||||
containerHeight: number;
|
||||
renderItem: (item: any, index: number) => React.ReactNode;
|
||||
keyExtractor: (item: any) => string | number;
|
||||
}> = ({ items, itemHeight, containerHeight, renderItem, keyExtractor }) => {
|
||||
const [scrollTop, setScrollTop] = React.useState(0);
|
||||
|
||||
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
setScrollTop(e.currentTarget.scrollTop);
|
||||
};
|
||||
|
||||
const startIndex = Math.floor(scrollTop / itemHeight);
|
||||
const endIndex = Math.min(
|
||||
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
|
||||
items.length
|
||||
);
|
||||
|
||||
const visibleItems = items.slice(startIndex, endIndex);
|
||||
const offsetY = startIndex * itemHeight;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: containerHeight,
|
||||
overflowY: 'auto',
|
||||
position: 'relative',
|
||||
}}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: items.length * itemHeight,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: offsetY,
|
||||
left: 0,
|
||||
right: 0,
|
||||
}}
|
||||
>
|
||||
{visibleItems.map((item, index) => {
|
||||
const actualIndex = startIndex + index;
|
||||
return (
|
||||
<div
|
||||
key={keyExtractor(item)}
|
||||
style={{
|
||||
height: itemHeight,
|
||||
}}
|
||||
>
|
||||
{renderItem(item, actualIndex)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 导入非React工具函数
|
||||
import {
|
||||
debounce,
|
||||
throttle,
|
||||
lazyLoadImage,
|
||||
memoize,
|
||||
batchProcess,
|
||||
preloadResource,
|
||||
measurePerformance
|
||||
} from './PerformanceOptimization';
|
||||
|
||||
export {
|
||||
debounce,
|
||||
throttle,
|
||||
lazyLoadImage,
|
||||
memoize,
|
||||
batchProcess,
|
||||
preloadResource,
|
||||
measurePerformance
|
||||
};
|
||||
|
||||
export default {
|
||||
debounce,
|
||||
throttle,
|
||||
lazyLoadComponent,
|
||||
lazyLoadImage,
|
||||
memoize,
|
||||
batchProcess,
|
||||
preloadResource,
|
||||
measurePerformance,
|
||||
useOptimizedRender,
|
||||
optimizeListRender,
|
||||
VirtualList,
|
||||
};
|
||||
Reference in New Issue
Block a user