Skip to content

Commit 8d0a5a9

Browse files
committed
feat: 重构文件列表组件并优化筛选逻辑
- 使用虚拟滚动优化大文件列表性能 - 移除不必要的版本和语言参数传递 - 调整筛选器布局和样式 - 优化文件列表的查询条件和显示逻辑
1 parent b41b9f2 commit 8d0a5a9

6 files changed

Lines changed: 201 additions & 102 deletions

File tree

src/components/FileList.tsx

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
*/
55

66
import React, { useState, useMemo, useCallback } from 'react';
7-
import { FileCard } from './FileCard';
7+
import { Button, Space } from 'tdesign-react';
88
import { SearchFilter } from './SearchFilter';
9-
import type { WinFileInfo, EditionAndLanguage } from '../types/api';
9+
import { formatFileSize } from '../utils/format';
10+
import type { WinFileInfo } from '../types/api';
1011

1112
interface FileListProps {
1213
files: WinFileInfo[];
13-
editionAndLanguage?: EditionAndLanguage;
14-
selectedLanguage?: string;
1514
emptyContent?: React.ReactNode;
1615
enableSearch?: boolean;
1716
onDownload: (url: string) => void;
@@ -23,18 +22,22 @@ interface FileListProps {
2322
*/
2423
export const FileList: React.FC<FileListProps> = ({
2524
files,
26-
editionAndLanguage,
27-
selectedLanguage,
2825
emptyContent,
2926
enableSearch = true,
3027
onDownload,
3128
onCopy,
3229
}) => {
3330
const [filteredFiles, setFilteredFiles] = useState<WinFileInfo[]>(files);
31+
const [scrollTop, setScrollTop] = useState(0);
32+
33+
const ROW_HEIGHT = 86;
34+
const CONTAINER_HEIGHT = 520;
35+
const OVERSCAN = 6;
3436

3537
// 当外部 files 变化时更新过滤列表
3638
React.useEffect(() => {
3739
setFilteredFiles(files);
40+
setScrollTop(0);
3841
}, [files]);
3942

4043
/**
@@ -44,32 +47,72 @@ export const FileList: React.FC<FileListProps> = ({
4447
setFilteredFiles(newFilteredFiles);
4548
}, []);
4649

47-
/**
48-
* 渲染文件卡片
49-
*/
50-
const renderFileCards = useMemo(() => {
51-
return filteredFiles.map((info, index) => {
52-
// 查找版本和语言的显示标签
53-
const editionLabel = editionAndLanguage?.Edition.find(
54-
(item) => item.value === info.Edition
55-
)?.label_cn;
56-
57-
const languageLabel = selectedLanguage
58-
? editionAndLanguage?.Language.find((item) => item.value === selectedLanguage)?.label_cn
59-
: info.Language;
50+
const visibleRange = useMemo(() => {
51+
const start = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - OVERSCAN);
52+
const visibleCount = Math.ceil(CONTAINER_HEIGHT / ROW_HEIGHT) + OVERSCAN * 2;
53+
const end = Math.min(filteredFiles.length, start + visibleCount);
54+
return { start, end };
55+
}, [filteredFiles.length, scrollTop]);
6056

57+
const visibleRows = useMemo(() => {
58+
return filteredFiles.slice(visibleRange.start, visibleRange.end).map((info, offset) => {
59+
const index = visibleRange.start + offset;
60+
const top = index * ROW_HEIGHT;
6161
return (
62-
<FileCard
63-
key={`${info.FileName}-${index}`}
64-
info={info}
65-
editionLabel={editionLabel}
66-
languageLabel={languageLabel}
67-
onDownload={onDownload}
68-
onCopy={onCopy}
69-
/>
62+
<div className="file-list-row file-list-row-virtual" style={{ top }} key={`${info.FileName}-${index}`}>
63+
<div className="file-list-main">
64+
<div className="file-list-name">{info.FileName}</div>
65+
<div className="file-list-meta">
66+
<span>{info.VerCode} ({info.BuildVer})</span>
67+
<span>{info.Language}</span>
68+
<span>{info.Edition}</span>
69+
<span>{info.Architecture}</span>
70+
<span>{formatFileSize(info.Size)}</span>
71+
</div>
72+
</div>
73+
<Space className="file-list-actions" size="small">
74+
<Button theme="primary" size="small" onClick={() => onDownload(info.FilePath)}>
75+
下载
76+
</Button>
77+
<Button variant="outline" size="small" onClick={() => onCopy(info.FilePath)}>
78+
复制直链
79+
</Button>
80+
</Space>
81+
</div>
7082
);
7183
});
72-
}, [filteredFiles, editionAndLanguage, selectedLanguage, onDownload, onCopy]);
84+
}, [filteredFiles, onCopy, onDownload, visibleRange]);
85+
86+
const totalHeight = filteredFiles.length * ROW_HEIGHT;
87+
88+
const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
89+
setScrollTop(event.currentTarget.scrollTop);
90+
}, []);
91+
92+
const renderRows = useMemo(() => {
93+
return filteredFiles.map((info, index) => (
94+
<div className="file-list-row" key={`${info.FileName}-${index}`}>
95+
<div className="file-list-main">
96+
<div className="file-list-name">{info.FileName}</div>
97+
<div className="file-list-meta">
98+
<span>{info.VerCode} ({info.BuildVer})</span>
99+
<span>{info.Language}</span>
100+
<span>{info.Edition}</span>
101+
<span>{info.Architecture}</span>
102+
<span>{formatFileSize(info.Size)}</span>
103+
</div>
104+
</div>
105+
<Space className="file-list-actions" size="small">
106+
<Button theme="primary" size="small" onClick={() => onDownload(info.FilePath)}>
107+
下载
108+
</Button>
109+
<Button variant="outline" size="small" onClick={() => onCopy(info.FilePath)}>
110+
复制直链
111+
</Button>
112+
</Space>
113+
</div>
114+
));
115+
}, [filteredFiles, onCopy, onDownload]);
73116

74117
if (files.length === 0) {
75118
return <>{emptyContent}</>;
@@ -89,7 +132,15 @@ export const FileList: React.FC<FileListProps> = ({
89132
没有找到匹配的文件
90133
</div>
91134
) : (
92-
renderFileCards
135+
filteredFiles.length <= 30 ? (
136+
<div className="file-list-wrap">{renderRows}</div>
137+
) : (
138+
<div className="file-list-virtual" style={{ height: CONTAINER_HEIGHT }} onScroll={handleScroll}>
139+
<div className="file-list-virtual-inner" style={{ height: totalHeight }}>
140+
{visibleRows}
141+
</div>
142+
</div>
143+
)
93144
)}
94145
</div>
95146
);

src/components/SystemSelector.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const SystemSelector: React.FC<SystemSelectorProps> = ({
5151
// 计算禁用状态
5252
const isVersionDisabled = !systemCode;
5353
const isLanguageDisabled = !editionAndLanguage?.Language.length || !version;
54-
const isEditionDisabled = !editionAndLanguage?.Edition.length || !version;
54+
const isEditionDisabled = !language || !editionAndLanguage?.Edition.length || !version;
5555

5656
// 处理系统代码变化
5757
const handleSystemCodeChange = useCallback((value: unknown) => {
@@ -119,7 +119,7 @@ export const SystemSelector: React.FC<SystemSelectorProps> = ({
119119
prefixIcon={<>版本:</>}
120120
disabled={isEditionDisabled}
121121
options={editionAndLanguage?.Edition}
122-
placeholder={isEditionDisabled ? '请先选择版本号' : '选择版本'}
122+
placeholder={isEditionDisabled ? '请先选择语言' : '选择版本'}
123123
clearable
124124
onChange={handleEditionChange}
125125
/>

src/global/main.css

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -130,37 +130,26 @@ p {
130130
text-align: left;
131131
}
132132

133-
.all-system-layout {
134-
display: grid;
135-
grid-template-columns: repeat(2, minmax(0, 1fr));
136-
gap: 18px;
137-
align-items: center;
138-
}
139-
140-
.all-system-left,
141-
.all-system-right {
133+
.panel-lite {
142134
border: 1px solid rgba(219, 228, 242, 0.85);
143135
border-radius: 14px;
144136
background: rgba(255, 255, 255, 0.72);
145137
padding: 14px;
146-
text-align: center;
138+
}
139+
140+
.all-system-filter {
141+
margin-bottom: 14px;
147142
}
148143

149144
.all-system-subtitle {
150145
margin: 0 0 10px;
151146
font-size: 1rem;
152147
color: var(--app-text-muted);
148+
text-align: left;
153149
}
154150

155-
.all-system-left .selector-grid {
156-
grid-template-columns: 1fr;
157-
width: min(420px, 100%);
158-
margin: 0 auto;
159-
}
160-
161-
.all-system-right .files-area {
162-
width: min(740px, 100%);
163-
margin: 0 auto;
151+
.all-system-filter .selector-grid {
152+
grid-template-columns: repeat(5, minmax(0, 1fr));
164153
}
165154

166155
.latest-grid {
@@ -180,7 +169,71 @@ p {
180169
}
181170

182171
.files-area {
183-
margin-top: 22px;
172+
margin-top: 14px;
173+
}
174+
175+
.file-list-wrap {
176+
display: flex;
177+
flex-direction: column;
178+
gap: 10px;
179+
}
180+
181+
.file-list-row {
182+
display: flex;
183+
justify-content: space-between;
184+
align-items: center;
185+
gap: 12px;
186+
border: 1px solid var(--app-border);
187+
border-radius: 12px;
188+
padding: 10px 12px;
189+
background: #fff;
190+
}
191+
192+
.file-list-row-virtual {
193+
position: absolute;
194+
left: 0;
195+
right: 0;
196+
min-height: 78px;
197+
}
198+
199+
.file-list-main {
200+
min-width: 0;
201+
text-align: left;
202+
}
203+
204+
.file-list-name {
205+
font-size: 13px;
206+
line-height: 1.4;
207+
overflow: hidden;
208+
text-overflow: ellipsis;
209+
white-space: nowrap;
210+
}
211+
212+
.file-list-meta {
213+
margin-top: 6px;
214+
color: var(--app-text-muted);
215+
display: flex;
216+
flex-wrap: nowrap;
217+
gap: 10px;
218+
font-size: 12px;
219+
white-space: nowrap;
220+
overflow: hidden;
221+
text-overflow: ellipsis;
222+
}
223+
224+
.file-list-actions {
225+
flex-shrink: 0;
226+
}
227+
228+
.file-list-virtual {
229+
overflow: auto;
230+
border: 1px solid var(--app-border);
231+
border-radius: 12px;
232+
background: #fff;
233+
}
234+
235+
.file-list-virtual-inner {
236+
position: relative;
184237
}
185238

186239
.file-card + .file-card {
@@ -330,8 +383,8 @@ p {
330383
}
331384

332385
@media (max-width: 992px) {
333-
.selector-grid {
334-
grid-template-columns: repeat(2, minmax(0, 1fr));
386+
.all-system-filter .selector-grid {
387+
grid-template-columns: repeat(3, minmax(0, 1fr));
335388
}
336389
}
337390

@@ -352,8 +405,13 @@ p {
352405
padding: 18px 14px;
353406
}
354407

355-
.all-system-layout {
356-
grid-template-columns: 1fr;
408+
.file-list-row {
409+
flex-direction: column;
410+
align-items: flex-start;
411+
}
412+
413+
.file-list-actions {
414+
width: 100%;
357415
}
358416

359417
.about-page,
@@ -364,7 +422,7 @@ p {
364422
}
365423

366424
@media (max-width: 560px) {
367-
.selector-grid {
425+
.all-system-filter .selector-grid {
368426
grid-template-columns: 1fr;
369427
}
370428
}

src/hooks/useWinNewData.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ interface UseWinNewDataReturn {
4141
systemCode: string,
4242
version: string,
4343
language: string,
44-
edition: string,
4544
architecture: 'all' | 'x64' | 'x86' | 'arm64'
4645
) => Promise<void>;
4746
handleDownload: (url: string) => void;
@@ -120,7 +119,6 @@ export function useWinNewData(): UseWinNewDataReturn {
120119
systemCode: string,
121120
version: string,
122121
language: string,
123-
edition: string,
124122
architecture: 'all' | 'x64' | 'x86' | 'arm64'
125123
) => {
126124
if (!systemCode || !version) {
@@ -133,7 +131,6 @@ export function useWinNewData(): UseWinNewDataReturn {
133131
systemCode,
134132
version,
135133
language,
136-
edition,
137134
architecture
138135
);
139136
setEditionAndLanguage(data);
@@ -158,8 +155,8 @@ export function useWinNewData(): UseWinNewDataReturn {
158155
}) => {
159156
const { systemCode, version, language, edition, architecture } = params;
160157

161-
// 如果参数不完整,清空结果
162-
if (!systemCode || !version || !language || !edition || !architecture) {
158+
// 查询必须包含系统 + 版本 + 语言
159+
if (!systemCode || !version || !language) {
163160
setFilteredFiles([]);
164161
return;
165162
}

0 commit comments

Comments
 (0)