You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
13 KiB
13 KiB
共享资源选择器优化
日期:2026-02-08 类型:功能增强 影响范围:前端 - ResourceSelectorPanel
变更概述
全面优化共享资源选择器的 UI/UX,新增智能级联选择、搜索、过滤、快捷操作等功能,大幅提升可用性。
背景
初版资源选择器存在以下问题:
- 交互逻辑不清晰:选择父级不会自动选择子级,不符合用户心理模型
- 视觉层级不够:缩进仅 16px,层级关系不明显
- 缺少关键功能:无搜索、无过滤、无快捷操作
- 可用性问题:资源多时难以找到目标,操作效率低
核心优化
1. 智能级联选择 ✨
选择文件夹 → 自动选择其下所有项目及资源
选择"西游记系列"文件夹
↓ 自动选择
《西游记》第一季 + 其下所有角色/场景/道具
《西游记》第二季 + 其下所有角色/场景/道具
选择项目 → 自动选择其下所有资源
选择《西游记》第一季
↓ 自动选择
孙悟空、猪八戒、唐僧、沙和尚(角色)
花果山、高老庄、流沙河(场景)
金箍棒、九齿钉耙(道具)
取消父级 → 自动取消所有子级
取消选择《西游记》第一季
↓ 自动取消
其下所有角色/场景/道具全部取消选中
子级全选 → 父级自动全选;部分选 → 父级半选
[ ] 《西游记》第一季 ← 未选
[◐] 《西游记》第一季 ← 半选(部分子级选中)
[✓] 《西游记》第一季 ← 全选(所有子级选中)
实现算法:
// 向下级联:选择父级 → 选择所有子孙
const descendantIds = getAllDescendantIds(node);
const allIds = [nodeId, ...descendantIds];
// 向上级联:根据子级状态计算父级状态
function getNodeCheckState(node, selectedIds) {
if (selectedIds.has(node.id)) return 'checked';
const childStates = node.children.map(child =>
getNodeCheckState(child, selectedIds)
);
const checkedCount = childStates.filter(s => s === 'checked').length;
if (checkedCount === node.children.length) return 'checked';
if (checkedCount > 0) return 'indeterminate'; // 半选
return 'unchecked';
}
2. 搜索功能 🔍
实时搜索 + 关键词高亮
┌─────────────────────────────────────┐
│ [🔍 搜索资源...] [×] │
├─────────────────────────────────────┤
│ 搜索"孙悟空",找到 2 项: │
│ [✓] 👤 孙悟空 (《西游记》第一季) │
│ └─ 关键词高亮显示 │
│ [✓] 👤 孙悟空 (《西游记》第二季) │
└─────────────────────────────────────┘
特性:
- ✅ 实时搜索(输入即搜)
- ✅ 关键词黄色高亮
- ✅ 显示父级路径(便于区分同名资源)
- ✅ 空状态友好提示
- ✅ 一键清空搜索(× 按钮)
3. 资源类型过滤 🎯
按钮组快速切换
┌─────────────────────────────────────┐
│ [全部] [角色] [场景] [道具] │
└─────────────────────────────────────┘
使用场景:
- 只想选择角色时,过滤其他类型
- 与搜索组合使用(如:搜索"孙悟空" + 过滤"角色")
- 文件夹和项目节点始终显示(保持层级结构)
4. 快捷操作 ⚡
工具栏提供批量操作
┌─────────────────────────────────────┐
│ [全选] | [取消全选] | [展开全部] | [收起] │
└─────────────────────────────────────┘
操作说明:
- 全选:选择所有资源(文件夹、项目、角色、场景、道具)
- 取消全选:清空所有选择
- 展开全部:展开所有层级(快速浏览全部内容)
- 收起:收起到顶层(仅保留"我的项目"展开)
5. 改进的视觉设计 🎨
5.1 更清晰的层级缩进
变更前:16px 缩进
[ ] ▶ 📁 我的项目
[ ] ▶ 📁 西游记系列
[ ] ▶ 📂 《西游记》第一季 (角色 15, 场景 8, 道具 12)
[ ] 👤 孙悟空
变更后:24px 缩进 + 统计信息独立行
[ ] ▶ 📁 我的项目
[◐] ▼ 📁 西游记系列
[◐] ▼ 📂 《西游记》第一季
│ └─ 角色 15 场景 8 道具 12
[✓] 👤 孙悟空
[ ] 👤 猪八戒
改进点:
- ✅ 缩进增加到 24px(层级更清晰)
- ✅ 统计信息独立一行(不挤在标题旁)
- ✅ 使用
└─连接线(视觉引导) - ✅ 半选状态用
◐图标表示
5.2 Checkbox 半选状态
新增 indeterminate 支持
// Checkbox 组件支持三态
<Checkbox
checked={checkState === 'checked'}
indeterminate={checkState === 'indeterminate'}
/>
// 渲染不同图标
checked: ✓ (Check)
indeterminate: - (Minus)
unchecked: 空
6. 增强的统计信息 📊
底部统计栏优化
变更前:
已选 5 项资源
变更后:
已选 5 项资源
角色 3 场景 1 道具 1
统计维度:
- 总数
- 按类型分类(角色/场景/道具)
- 实时更新
7. 宽度调整
面板宽度:320px → 380px
- 搜索框更宽,输入体验更好
- 统计信息显示更完整
- 按钮组不拥挤
弹窗自适应宽度:
- 风格面板:600px + 320px = 920px
- 资源面板:600px + 380px = 980px
技术实现
核心算法
1. 扁平化树结构
function flattenTree(nodes: ResourceTreeNode[]): Map<string, ResourceTreeNode> {
const map = new Map();
const traverse = (node) => {
map.set(node.id, node);
if (node.children) node.children.forEach(traverse);
};
nodes.forEach(traverse);
return map;
}
2. 获取所有子孙节点
function getAllDescendantIds(node: ResourceTreeNode): string[] {
if (!node.children) return [];
const ids = [];
for (const child of node.children) {
ids.push(child.id);
ids.push(...getAllDescendantIds(child));
}
return ids;
}
3. 搜索匹配
function searchNodes(
nodes: ResourceTreeNode[],
query: string,
typeFilter: ResourceTypeFilter
): ResourceTreeNode[] {
const results = [];
const lowerQuery = query.toLowerCase();
const traverse = (node) => {
const typeMatch = typeFilter === 'all' ||
node.type === typeFilter ||
node.type === 'folder' ||
node.type === 'project';
const nameMatch = node.name.toLowerCase().includes(lowerQuery);
if (typeMatch && nameMatch) results.push(node);
if (node.children) node.children.forEach(traverse);
};
nodes.forEach(traverse);
return results;
}
4. 关键词高亮
const highlightedName = useMemo(() => {
if (!searchQuery) return node.name;
const lowerName = node.name.toLowerCase();
const lowerQuery = searchQuery.toLowerCase();
const index = lowerName.indexOf(lowerQuery);
if (index === -1) return node.name;
const before = node.name.slice(0, index);
const match = node.name.slice(index, index + searchQuery.length);
const after = node.name.slice(index + searchQuery.length);
return (
<>
{before}
<mark className="bg-yellow-200">{match}</mark>
{after}
</>
);
}, [node.name, searchQuery]);
性能优化
- 使用 useMemo 缓存计算
const nodeMap = useMemo(() => flattenTree(mockResourceTree), []);
const selectedIds = useMemo(() => new Set(selectedResources.map(r => r.id)), [selectedResources]);
const filteredTree = useMemo(() => searchNodes(...), [searchQuery, typeFilter]);
- 使用 useCallback 稳定回调
const handleToggleSelect = useCallback((nodeId, checked) => { ... }, [nodeMap, selectedResources, onSelect]);
const handleExpandAll = useCallback(() => { ... }, [nodeMap]);
- 使用 memo 避免不必要的重渲染
const TreeNode = memo(function TreeNode({ ... }) { ... });
export const ResourceSelectorPanel = memo(function ResourceSelectorPanel({ ... }) { ... });
文件变更
修改:
client/src/components/features/project/ResourceSelectorPanel.tsx- 完全重构client/src/components/features/project/CreateProjectModal.tsx- 调整弹窗宽度client/src/components/ui/checkbox.tsx- 新增半选状态支持
新增依赖:
lucide-react图标:Minus- 半选图标Search- 搜索图标CheckSquare- 全选图标Square- 取消全选图标ChevronUp- 收起图标
交互演示
场景 1:选择整个文件夹
1. 点击"西游记系列"的 Checkbox
↓
2. 自动选中:
- 《西游记》第一季 + 15个角色 + 8个场景 + 12个道具
- 《西游记》第二季 + 18个角色 + 10个场景 + 15个道具
↓
3. 底部统计:已选 70 项资源
角色 33 场景 18 道具 27
场景 2:搜索 + 过滤
1. 输入搜索:"悟空"
↓ 显示
- 👤 孙悟空 (《西游记》第一季)
- 👤 孙悟空 (《西游记》第二季)
2. 点击过滤"角色"
↓ 仅显示角色类型,场景和道具被过滤
3. 勾选两个孙悟空
↓ 底部统计:已选 2 项资源
角色 2
场景 3:半选状态
初始状态:
[ ] 《西游记》第一季(未选)
[ ] 孙悟空
[ ] 猪八戒
[ ] 唐僧
选择孙悟空后:
[◐] 《西游记》第一季(半选 - 部分子级选中)
[✓] 孙悟空
[ ] 猪八戒
[ ] 唐僧
全部选中后:
[✓] 《西游记》第一季(全选 - 所有子级选中)
[✓] 孙悟空
[✓] 猪八戒
[✓] 唐僧
用户体验提升
操作效率提升
| 操作 | 变更前 | 变更后 | 提升 |
|---|---|---|---|
| 选择文件夹下所有资源 | 需逐个展开并勾选(~50次点击) | 1次点击 | 98% ↑ |
| 找到"孙悟空" | 需手动展开查找(~10次点击) | 搜索框输入(1次) | 90% ↑ |
| 只选角色资源 | 需逐个跳过场景和道具 | 过滤"角色"(1次点击) | 80% ↑ |
| 展开全部层级 | 需逐层点击展开(~8次) | "展开全部"(1次点击) | 87.5% ↑ |
认知负担降低
| 方面 | 变更前 | 变更后 |
|---|---|---|
| 层级关系 | 缩进小,难以区分 | 缩进明显,连接线清晰 |
| 选择反馈 | 不知道选了哪些子级 | 半选状态明确提示 |
| 统计信息 | 仅总数 | 总数 + 分类统计 |
| 空状态 | 无内容提示 | 友好提示"未找到匹配的资源" |
后续优化方向
Phase 1:性能优化
- 虚拟滚动(资源数 > 100 时)
- 搜索防抖(300ms)
- 记忆展开状态(LocalStorage)
Phase 2:高级功能
- 批量操作(如:全选某文件夹下的角色)
- 拖拽排序(调整资源优先级)
- 最近使用/推荐资源(智能推荐)
Phase 3:数据集成
- 替换 mock 数据为真实 API
- 加载状态优化(骨架屏)
- 错误处理(网络失败重试)
测试建议
功能测试
-
级联选择
- 选择文件夹自动选择所有子级
- 选择项目自动选择所有资源
- 取消父级自动取消所有子级
- 半选状态正确显示
-
搜索功能
- 实时搜索生效
- 关键词高亮正确
- 空状态提示显示
- 清空搜索恢复原始列表
-
资源类型过滤
- 过滤"角色"仅显示角色
- 过滤"场景"仅显示场景
- 过滤"道具"仅显示道具
- "全部"显示所有类型
-
快捷操作
- 全选选中所有资源
- 取消全选清空选择
- 展开全部展开所有层级
- 收起恢复默认状态
边界测试
- 大数据量(100+ 资源)性能
- 深层级嵌套(5+ 层)渲染
- 搜索无结果场景
- 空文件夹场景
视觉测试
- 缩进层级清晰
- 半选图标显示正确
- 关键词高亮可见
- 统计信息对齐
相关文档
变更记录
- 2026-02-08:完整优化版本 - 智能级联、搜索、过滤、快捷操作