# 共享资源选择器优化 > **日期**:2026-02-08 > **类型**:功能增强 > **影响范围**:前端 - ResourceSelectorPanel --- ## 变更概述 全面优化共享资源选择器的 UI/UX,新增智能级联选择、搜索、过滤、快捷操作等功能,大幅提升可用性。 ## 背景 初版资源选择器存在以下问题: 1. **交互逻辑不清晰**:选择父级不会自动选择子级,不符合用户心理模型 2. **视觉层级不够**:缩进仅 16px,层级关系不明显 3. **缺少关键功能**:无搜索、无过滤、无快捷操作 4. **可用性问题**:资源多时难以找到目标,操作效率低 ## 核心优化 ### 1. 智能级联选择 ✨ **选择文件夹 → 自动选择其下所有项目及资源** ``` 选择"西游记系列"文件夹 ↓ 自动选择 《西游记》第一季 + 其下所有角色/场景/道具 《西游记》第二季 + 其下所有角色/场景/道具 ``` **选择项目 → 自动选择其下所有资源** ``` 选择《西游记》第一季 ↓ 自动选择 孙悟空、猪八戒、唐僧、沙和尚(角色) 花果山、高老庄、流沙河(场景) 金箍棒、九齿钉耙(道具) ``` **取消父级 → 自动取消所有子级** ``` 取消选择《西游记》第一季 ↓ 自动取消 其下所有角色/场景/道具全部取消选中 ``` **子级全选 → 父级自动全选;部分选 → 父级半选** ``` [ ] 《西游记》第一季 ← 未选 [◐] 《西游记》第一季 ← 半选(部分子级选中) [✓] 《西游记》第一季 ← 全选(所有子级选中) ``` **实现算法**: ```typescript // 向下级联:选择父级 → 选择所有子孙 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` 支持** ```typescript // Checkbox 组件支持三态 // 渲染不同图标 checked: ✓ (Check) indeterminate: - (Minus) unchecked: 空 ``` ### 6. 增强的统计信息 📊 **底部统计栏优化** **变更前**: ``` 已选 5 项资源 ``` **变更后**: ``` 已选 5 项资源 角色 3 场景 1 道具 1 ``` **统计维度**: - 总数 - 按类型分类(角色/场景/道具) - 实时更新 ### 7. 宽度调整 **面板宽度**:320px → 380px - 搜索框更宽,输入体验更好 - 统计信息显示更完整 - 按钮组不拥挤 **弹窗自适应宽度**: - 风格面板:600px + 320px = 920px - 资源面板:600px + 380px = 980px ## 技术实现 ### 核心算法 #### 1. 扁平化树结构 ```typescript function flattenTree(nodes: ResourceTreeNode[]): Map { 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. 获取所有子孙节点 ```typescript 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. 搜索匹配 ```typescript 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. 关键词高亮 ```typescript 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} {match} {after} ); }, [node.name, searchQuery]); ``` ### 性能优化 1. **使用 useMemo 缓存计算** ```typescript const nodeMap = useMemo(() => flattenTree(mockResourceTree), []); const selectedIds = useMemo(() => new Set(selectedResources.map(r => r.id)), [selectedResources]); const filteredTree = useMemo(() => searchNodes(...), [searchQuery, typeFilter]); ``` 2. **使用 useCallback 稳定回调** ```typescript const handleToggleSelect = useCallback((nodeId, checked) => { ... }, [nodeMap, selectedResources, onSelect]); const handleExpandAll = useCallback(() => { ... }, [nodeMap]); ``` 3. **使用 memo 避免不必要的重渲染** ```typescript 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+ 层)渲染 - [ ] 搜索无结果场景 - [ ] 空文件夹场景 ### 视觉测试 - [ ] 缩进层级清晰 - [ ] 半选图标显示正确 - [ ] 关键词高亮可见 - [ ] 统计信息对齐 ## 相关文档 - [ADR 02: 跨项目资源共享](../../server/adrs/02-cross-project-resource-sharing.md) - [新建项目流程优化 Changelog](./2026-02-08-create-project-optimization.md) ## 变更记录 - 2026-02-08:完整优化版本 - 智能级联、搜索、过滤、快捷操作