Spaces:
Sleeping
Sleeping
Update src/components/Category/CategorySelector.tsx
Browse files
src/components/Category/CategorySelector.tsx
CHANGED
|
@@ -17,111 +17,150 @@ const CategorySelector: React.FC<CategorySelectorProps> = ({
|
|
| 17 |
}) => {
|
| 18 |
const { categories } = useApp();
|
| 19 |
const [showDropdown, setShowDropdown] = useState(false);
|
| 20 |
-
const
|
| 21 |
|
|
|
|
| 22 |
const selectedCategoryObj = categories.find(c =>
|
| 23 |
typeof selectedCategory === 'string'
|
| 24 |
? c._id === selectedCategory
|
| 25 |
: c._id === selectedCategory._id
|
| 26 |
);
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
// 点击外部关闭下拉菜单
|
| 29 |
useEffect(() => {
|
| 30 |
-
const handleClickOutside = (
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
setShowDropdown(false);
|
| 33 |
}
|
| 34 |
};
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
}
|
| 39 |
|
|
|
|
| 40 |
return () => {
|
| 41 |
document.removeEventListener('mousedown', handleClickOutside);
|
| 42 |
};
|
| 43 |
}, [showDropdown]);
|
| 44 |
|
| 45 |
-
//
|
| 46 |
-
const
|
| 47 |
-
|
| 48 |
-
};
|
| 49 |
-
|
| 50 |
-
const handleCategorySelect = (categoryId: string) => {
|
| 51 |
-
// 重要:确保这个函数被正确调用
|
| 52 |
-
onChange(categoryId);
|
| 53 |
-
setShowDropdown(false);
|
| 54 |
-
};
|
| 55 |
-
|
| 56 |
-
// 使用Portal将下拉菜单渲染到body
|
| 57 |
-
const renderDropdown = () => {
|
| 58 |
-
if (!showDropdown || !selectorRef.current) return null;
|
| 59 |
|
| 60 |
-
|
|
|
|
| 61 |
|
|
|
|
| 62 |
return ReactDOM.createPortal(
|
| 63 |
-
<div
|
|
|
|
| 64 |
style={{
|
| 65 |
position: 'absolute',
|
| 66 |
-
zIndex:
|
| 67 |
width: '200px',
|
| 68 |
maxHeight: '300px',
|
| 69 |
overflowY: 'auto',
|
| 70 |
-
top: `${rect.bottom + window.scrollY}px`,
|
| 71 |
-
left: `${rect.left + window.scrollX}px`,
|
| 72 |
backgroundColor: 'white',
|
| 73 |
-
borderRadius: '
|
| 74 |
-
boxShadow: '0
|
| 75 |
-
border: '1px solid
|
|
|
|
|
|
|
| 76 |
}}
|
| 77 |
>
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
key={category._id}
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
handleCategorySelect(category._id);
|
| 88 |
}}
|
| 89 |
>
|
| 90 |
<div
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
| 98 |
<svg
|
| 99 |
xmlns="http://www.w3.org/2000/svg"
|
| 100 |
width="16"
|
| 101 |
height="16"
|
| 102 |
viewBox="0 0 24 24"
|
| 103 |
fill="none"
|
| 104 |
-
stroke="
|
| 105 |
strokeWidth="2"
|
| 106 |
strokeLinecap="round"
|
| 107 |
strokeLinejoin="round"
|
| 108 |
-
className="ml-auto"
|
| 109 |
>
|
| 110 |
<polyline points="20 6 9 17 4 12"></polyline>
|
| 111 |
</svg>
|
| 112 |
)}
|
| 113 |
-
</
|
| 114 |
-
)
|
| 115 |
-
|
| 116 |
</div>,
|
| 117 |
document.body
|
| 118 |
);
|
| 119 |
};
|
| 120 |
|
| 121 |
return (
|
| 122 |
-
<div
|
| 123 |
<div
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
onClick={toggleDropdown}
|
| 126 |
>
|
| 127 |
{selectedCategoryObj ? (
|
|
@@ -141,13 +180,13 @@ const CategorySelector: React.FC<CategorySelectorProps> = ({
|
|
| 141 |
strokeWidth="2"
|
| 142 |
strokeLinecap="round"
|
| 143 |
strokeLinejoin="round"
|
| 144 |
-
|
| 145 |
>
|
| 146 |
<polyline points="6 9 12 15 18 9"></polyline>
|
| 147 |
</svg>
|
| 148 |
</div>
|
| 149 |
|
| 150 |
-
{
|
| 151 |
</div>
|
| 152 |
);
|
| 153 |
};
|
|
|
|
| 17 |
}) => {
|
| 18 |
const { categories } = useApp();
|
| 19 |
const [showDropdown, setShowDropdown] = useState(false);
|
| 20 |
+
const triggerRef = useRef<HTMLDivElement>(null);
|
| 21 |
|
| 22 |
+
// 查找选中的分类对象
|
| 23 |
const selectedCategoryObj = categories.find(c =>
|
| 24 |
typeof selectedCategory === 'string'
|
| 25 |
? c._id === selectedCategory
|
| 26 |
: c._id === selectedCategory._id
|
| 27 |
);
|
| 28 |
|
| 29 |
+
// 切换下拉菜单显示状态
|
| 30 |
+
const toggleDropdown = () => {
|
| 31 |
+
setShowDropdown(!showDropdown);
|
| 32 |
+
};
|
| 33 |
+
|
| 34 |
+
// 这是关键函数 - 处理分类选择
|
| 35 |
+
const handleCategorySelect = (categoryId: string) => {
|
| 36 |
+
console.log('选择分类:', categoryId);
|
| 37 |
+
// 直接调用传入的onChange函数
|
| 38 |
+
onChange(categoryId);
|
| 39 |
+
// 关闭下拉菜单
|
| 40 |
+
setShowDropdown(false);
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
// 点击外部关闭下拉菜单
|
| 44 |
useEffect(() => {
|
| 45 |
+
const handleClickOutside = (e: MouseEvent) => {
|
| 46 |
+
// 只在下拉菜单打开时处理点击外部事件
|
| 47 |
+
if (!showDropdown) return;
|
| 48 |
+
|
| 49 |
+
// 获取下拉菜单元素
|
| 50 |
+
const menuElement = document.getElementById('category-dropdown-menu');
|
| 51 |
+
|
| 52 |
+
// 检查点击是否在触发器或下拉菜单内
|
| 53 |
+
const isClickInsideTrigger = triggerRef.current && triggerRef.current.contains(e.target as Node);
|
| 54 |
+
const isClickInsideMenu = menuElement && menuElement.contains(e.target as Node);
|
| 55 |
+
|
| 56 |
+
// 如果点击在两者之外,关闭菜单
|
| 57 |
+
if (!isClickInsideTrigger && !isClickInsideMenu) {
|
| 58 |
setShowDropdown(false);
|
| 59 |
}
|
| 60 |
};
|
| 61 |
|
| 62 |
+
// 添加全局点击事件监听
|
| 63 |
+
document.addEventListener('mousedown', handleClickOutside);
|
|
|
|
| 64 |
|
| 65 |
+
// 组件卸载时移除监听
|
| 66 |
return () => {
|
| 67 |
document.removeEventListener('mousedown', handleClickOutside);
|
| 68 |
};
|
| 69 |
}, [showDropdown]);
|
| 70 |
|
| 71 |
+
// 创建下拉菜单Portal
|
| 72 |
+
const createMenu = () => {
|
| 73 |
+
if (!showDropdown || !triggerRef.current) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
+
// 获取触发元素位置
|
| 76 |
+
const rect = triggerRef.current.getBoundingClientRect();
|
| 77 |
|
| 78 |
+
// 使用Portal将菜单附加到body
|
| 79 |
return ReactDOM.createPortal(
|
| 80 |
+
<div
|
| 81 |
+
id="category-dropdown-menu"
|
| 82 |
style={{
|
| 83 |
position: 'absolute',
|
| 84 |
+
zIndex: 10000,
|
| 85 |
width: '200px',
|
| 86 |
maxHeight: '300px',
|
| 87 |
overflowY: 'auto',
|
|
|
|
|
|
|
| 88 |
backgroundColor: 'white',
|
| 89 |
+
borderRadius: '8px',
|
| 90 |
+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
| 91 |
+
border: '1px solid #e2e8f0',
|
| 92 |
+
top: rect.bottom + window.scrollY + 5,
|
| 93 |
+
left: rect.left + window.scrollX
|
| 94 |
}}
|
| 95 |
>
|
| 96 |
+
{categories.map((category) => {
|
| 97 |
+
const isSelected = typeof selectedCategory === 'string'
|
| 98 |
+
? selectedCategory === category._id
|
| 99 |
+
: selectedCategory._id === category._id;
|
| 100 |
+
|
| 101 |
+
// 为每个选项创建一个按钮
|
| 102 |
+
return (
|
| 103 |
+
<button
|
| 104 |
key={category._id}
|
| 105 |
+
type="button"
|
| 106 |
+
style={{
|
| 107 |
+
display: 'flex',
|
| 108 |
+
alignItems: 'center',
|
| 109 |
+
width: '100%',
|
| 110 |
+
textAlign: 'left',
|
| 111 |
+
padding: '8px 12px',
|
| 112 |
+
backgroundColor: isSelected ? '#ebf5ff' : 'transparent',
|
| 113 |
+
border: 'none',
|
| 114 |
+
cursor: 'pointer'
|
| 115 |
+
}}
|
| 116 |
+
// 重要!使用单独的内联函数
|
| 117 |
+
onClick={() => {
|
| 118 |
+
console.log(`点击了分类: ${category._id}`);
|
| 119 |
handleCategorySelect(category._id);
|
| 120 |
}}
|
| 121 |
>
|
| 122 |
<div
|
| 123 |
+
style={{
|
| 124 |
+
width: '12px',
|
| 125 |
+
height: '12px',
|
| 126 |
+
borderRadius: '50%',
|
| 127 |
+
backgroundColor: category.color,
|
| 128 |
+
marginRight: '8px'
|
| 129 |
+
}}
|
| 130 |
+
/>
|
| 131 |
+
<span style={{ flex: 1 }}>{category.name}</span>
|
| 132 |
+
{isSelected && (
|
| 133 |
<svg
|
| 134 |
xmlns="http://www.w3.org/2000/svg"
|
| 135 |
width="16"
|
| 136 |
height="16"
|
| 137 |
viewBox="0 0 24 24"
|
| 138 |
fill="none"
|
| 139 |
+
stroke="#007AFF"
|
| 140 |
strokeWidth="2"
|
| 141 |
strokeLinecap="round"
|
| 142 |
strokeLinejoin="round"
|
|
|
|
| 143 |
>
|
| 144 |
<polyline points="20 6 9 17 4 12"></polyline>
|
| 145 |
</svg>
|
| 146 |
)}
|
| 147 |
+
</button>
|
| 148 |
+
);
|
| 149 |
+
})}
|
| 150 |
</div>,
|
| 151 |
document.body
|
| 152 |
);
|
| 153 |
};
|
| 154 |
|
| 155 |
return (
|
| 156 |
+
<div className={className}>
|
| 157 |
<div
|
| 158 |
+
ref={triggerRef}
|
| 159 |
+
style={{
|
| 160 |
+
display: 'flex',
|
| 161 |
+
alignItems: 'center',
|
| 162 |
+
cursor: 'pointer',
|
| 163 |
+
}}
|
| 164 |
onClick={toggleDropdown}
|
| 165 |
>
|
| 166 |
{selectedCategoryObj ? (
|
|
|
|
| 180 |
strokeWidth="2"
|
| 181 |
strokeLinecap="round"
|
| 182 |
strokeLinejoin="round"
|
| 183 |
+
style={{ marginLeft: '4px' }}
|
| 184 |
>
|
| 185 |
<polyline points="6 9 12 15 18 9"></polyline>
|
| 186 |
</svg>
|
| 187 |
</div>
|
| 188 |
|
| 189 |
+
{createMenu()}
|
| 190 |
</div>
|
| 191 |
);
|
| 192 |
};
|