feat: 添加项目详情/素材管理的MaterialSegment聚合视图功能
- 新增MaterialSegment聚合视图,支持按AI分类和模特聚合展示 - 实现后端MaterialSegmentViewService和相关API命令 - 创建前端React组件:MaterialSegmentView、MaterialSegmentGroup、MaterialSegmentCard等 - 添加MaterialSegment详细信息模态框和批量操作对话框 - 实现搜索、筛选、排序、分页功能 - 集成虚拟滚动和性能优化 - 在ProjectDetails页面添加片段管理选项卡 - 遵循promptx开发规范和UI/UX设计标准
This commit is contained in:
128
apps/desktop/src/components/VirtualizedSegmentList.tsx
Normal file
128
apps/desktop/src/components/VirtualizedSegmentList.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { VariableSizeList as List } from 'react-window';
|
||||
import {
|
||||
ClassificationGroup,
|
||||
ModelGroup,
|
||||
MaterialSegmentViewMode
|
||||
} from '../types/materialSegmentView';
|
||||
import { MaterialSegmentGroup } from './MaterialSegmentGroup';
|
||||
|
||||
interface VirtualizedSegmentListProps {
|
||||
groups: (ClassificationGroup | ModelGroup)[];
|
||||
viewMode: MaterialSegmentViewMode;
|
||||
selectedSegmentIds: Set<string>;
|
||||
expandedGroups: Set<string>;
|
||||
onToggleExpand: (groupId: string) => void;
|
||||
onSelectSegment: (segmentId: string) => void;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 虚拟化MaterialSegment列表组件
|
||||
* 遵循 Tauri 开发规范的性能优化组件设计模式
|
||||
*/
|
||||
export const VirtualizedSegmentList: React.FC<VirtualizedSegmentListProps> = ({
|
||||
groups,
|
||||
viewMode,
|
||||
selectedSegmentIds,
|
||||
expandedGroups,
|
||||
onToggleExpand,
|
||||
onSelectSegment,
|
||||
height,
|
||||
}) => {
|
||||
// 计算每个分组的高度
|
||||
const getItemHeight = useCallback((index: number) => {
|
||||
const group = groups[index];
|
||||
if (!group) return 120; // 默认折叠高度
|
||||
|
||||
const groupId = viewMode === MaterialSegmentViewMode.ByClassification
|
||||
? (group as ClassificationGroup).category
|
||||
: (group as ModelGroup).model_id;
|
||||
|
||||
const isExpanded = expandedGroups.has(groupId);
|
||||
|
||||
if (!isExpanded) {
|
||||
return 120; // 折叠状态的高度
|
||||
}
|
||||
|
||||
// 展开状态的高度计算
|
||||
const baseHeight = 120; // 头部高度
|
||||
const segmentCount = group.segments.length;
|
||||
const segmentsPerRow = 4; // 每行显示的片段数
|
||||
const rowCount = Math.ceil(segmentCount / segmentsPerRow);
|
||||
const segmentRowHeight = 280; // 每行片段的高度
|
||||
const footerHeight = 60; // 底部统计信息高度
|
||||
|
||||
return baseHeight + (rowCount * segmentRowHeight) + footerHeight;
|
||||
}, [groups, viewMode, expandedGroups]);
|
||||
|
||||
// 计算总高度
|
||||
const totalHeight = useMemo(() => {
|
||||
return groups.reduce((total, _, index) => total + getItemHeight(index), 0);
|
||||
}, [groups, getItemHeight]);
|
||||
|
||||
// 渲染单个分组项
|
||||
const renderItem = useCallback(({ index, style }: { index: number; style: React.CSSProperties }) => {
|
||||
const group = groups[index];
|
||||
if (!group) return null;
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<div className="px-6 pb-4">
|
||||
<MaterialSegmentGroup
|
||||
key={viewMode === MaterialSegmentViewMode.ByClassification
|
||||
? (group as ClassificationGroup).category
|
||||
: (group as ModelGroup).model_id}
|
||||
group={group}
|
||||
viewMode={viewMode}
|
||||
isExpanded={expandedGroups.has(
|
||||
viewMode === MaterialSegmentViewMode.ByClassification
|
||||
? (group as ClassificationGroup).category
|
||||
: (group as ModelGroup).model_id
|
||||
)}
|
||||
selectedSegmentIds={selectedSegmentIds}
|
||||
onToggleExpand={onToggleExpand}
|
||||
onSelectSegment={onSelectSegment}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [groups, viewMode, expandedGroups, selectedSegmentIds, onToggleExpand, onSelectSegment]);
|
||||
|
||||
// 如果分组数量较少,不使用虚拟化
|
||||
if (groups.length <= 10) {
|
||||
return (
|
||||
<div className="space-y-4 px-6">
|
||||
{groups.map((group, _index) => (
|
||||
<MaterialSegmentGroup
|
||||
key={viewMode === MaterialSegmentViewMode.ByClassification
|
||||
? (group as ClassificationGroup).category
|
||||
: (group as ModelGroup).model_id}
|
||||
group={group}
|
||||
viewMode={viewMode}
|
||||
isExpanded={expandedGroups.has(
|
||||
viewMode === MaterialSegmentViewMode.ByClassification
|
||||
? (group as ClassificationGroup).category
|
||||
: (group as ModelGroup).model_id
|
||||
)}
|
||||
selectedSegmentIds={selectedSegmentIds}
|
||||
onToggleExpand={onToggleExpand}
|
||||
onSelectSegment={onSelectSegment}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<List
|
||||
height={Math.min(height, totalHeight)}
|
||||
itemCount={groups.length}
|
||||
itemSize={(index) => getItemHeight(index)}
|
||||
width="100%"
|
||||
className="scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100"
|
||||
>
|
||||
{renderItem}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user