promptmanager / src /components /Category /CategorySelector.tsx
samlax12's picture
Update src/components/Category/CategorySelector.tsx
f85c209 verified
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;