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 |
};
|