Spaces:
Sleeping
Sleeping
import React, { useState, useRef, useEffect } from 'react'; | |
import { Category } from '../../types'; | |
import { useApp } from '../../contexts/AppContext'; | |
import CategoryBadge from './CategoryBadge'; | |
import ReactDOM from 'react-dom'; | |
interface CategorySelectorProps { | |
selectedCategory: string | Category; | |
onChange: (categoryId: string) => void; | |
className?: string; | |
} | |
const CategorySelector: React.FC<CategorySelectorProps> = ({ | |
selectedCategory, | |
onChange, | |
className = '' | |
}) => { | |
const { categories } = useApp(); | |
const [showDropdown, setShowDropdown] = useState(false); | |
const triggerRef = useRef<HTMLDivElement>(null); | |
// 查找选中的分类对象 | |
const selectedCategoryObj = categories.find(c => | |
typeof selectedCategory === 'string' | |
? c._id === selectedCategory | |
: c._id === selectedCategory._id | |
); | |
// 切换下拉菜单显示状态 | |
const toggleDropdown = () => { | |
setShowDropdown(!showDropdown); | |
}; | |
// 这是关键函数 - 处理分类选择 | |
const handleCategorySelect = (categoryId: string) => { | |
console.log('选择分类:', categoryId); | |
// 直接调用传入的onChange函数 | |
onChange(categoryId); | |
// 关闭下拉菜单 | |
setShowDropdown(false); | |
}; | |
// 点击外部关闭下拉菜单 | |
useEffect(() => { | |
const handleClickOutside = (e: MouseEvent) => { | |
// 只在下拉菜单打开时处理点击外部事件 | |
if (!showDropdown) return; | |
// 获取下拉菜单元素 | |
const menuElement = document.getElementById('category-dropdown-menu'); | |
// 检查点击是否在触发器或下拉菜单内 | |
const isClickInsideTrigger = triggerRef.current && triggerRef.current.contains(e.target as Node); | |
const isClickInsideMenu = menuElement && menuElement.contains(e.target as Node); | |
// 如果点击在两者之外,关闭菜单 | |
if (!isClickInsideTrigger && !isClickInsideMenu) { | |
setShowDropdown(false); | |
} | |
}; | |
// 添加全局点击事件监听 | |
document.addEventListener('mousedown', handleClickOutside); | |
// 组件卸载时移除监听 | |
return () => { | |
document.removeEventListener('mousedown', handleClickOutside); | |
}; | |
}, [showDropdown]); | |
// 创建下拉菜单Portal | |
const createMenu = () => { | |
if (!showDropdown || !triggerRef.current) return null; | |
// 获取触发元素位置 | |
const rect = triggerRef.current.getBoundingClientRect(); | |
// 使用Portal将菜单附加到body | |
return ReactDOM.createPortal( | |
<div | |
id="category-dropdown-menu" | |
style={{ | |
position: 'absolute', | |
zIndex: 10000, | |
width: '200px', | |
maxHeight: '300px', | |
overflowY: 'auto', | |
backgroundColor: 'white', | |
borderRadius: '8px', | |
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', | |
border: '1px solid #e2e8f0', | |
top: rect.bottom + window.scrollY + 5, | |
left: rect.left + window.scrollX | |
}} | |
> | |
{categories.map((category) => { | |
const isSelected = typeof selectedCategory === 'string' | |
? selectedCategory === category._id | |
: selectedCategory._id === category._id; | |
// 为每个选项创建一个按钮 | |
return ( | |
<button | |
key={category._id} | |
type="button" | |
style={{ | |
display: 'flex', | |
alignItems: 'center', | |
width: '100%', | |
textAlign: 'left', | |
padding: '8px 12px', | |
backgroundColor: isSelected ? '#ebf5ff' : 'transparent', | |
border: 'none', | |
cursor: 'pointer' | |
}} | |
// 重要!使用单独的内联函数 | |
onClick={() => { | |
console.log(`点击了分类: ${category._id}`); | |
handleCategorySelect(category._id); | |
}} | |
> | |
<div | |
style={{ | |
width: '12px', | |
height: '12px', | |
borderRadius: '50%', | |
backgroundColor: category.color, | |
marginRight: '8px' | |
}} | |
/> | |
<span style={{ flex: 1 }}>{category.name}</span> | |
{isSelected && ( | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
width="16" | |
height="16" | |
viewBox="0 0 24 24" | |
fill="none" | |
stroke="#007AFF" | |
strokeWidth="2" | |
strokeLinecap="round" | |
strokeLinejoin="round" | |
> | |
<polyline points="20 6 9 17 4 12"></polyline> | |
</svg> | |
)} | |
</button> | |
); | |
})} | |
</div>, | |
document.body | |
); | |
}; | |
return ( | |
<div className={className}> | |
<div | |
ref={triggerRef} | |
style={{ | |
display: 'flex', | |
alignItems: 'center', | |
cursor: 'pointer', | |
}} | |
onClick={toggleDropdown} | |
> | |
{selectedCategoryObj ? ( | |
<CategoryBadge category={selectedCategoryObj} /> | |
) : ( | |
<div className="ios-tag" style={{ backgroundColor: '#f0f0f0', color: '#666' }}> | |
选择分类 | |
</div> | |
)} | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
width="16" | |
height="16" | |
viewBox="0 0 24 24" | |
fill="none" | |
stroke="currentColor" | |
strokeWidth="2" | |
strokeLinecap="round" | |
strokeLinejoin="round" | |
style={{ marginLeft: '4px' }} | |
> | |
<polyline points="6 9 12 15 18 9"></polyline> | |
</svg> | |
</div> | |
{createMenu()} | |
</div> | |
); | |
}; | |
export default CategorySelector; |