samlax12 commited on
Commit
f85c209
·
verified ·
1 Parent(s): 71c410d

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 selectorRef = useRef<HTMLDivElement>(null);
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 = (event: MouseEvent) => {
31
- if (selectorRef.current && !selectorRef.current.contains(event.target as Node)) {
 
 
 
 
 
 
 
 
 
 
 
32
  setShowDropdown(false);
33
  }
34
  };
35
 
36
- if (showDropdown) {
37
- document.addEventListener('mousedown', handleClickOutside);
38
- }
39
 
 
40
  return () => {
41
  document.removeEventListener('mousedown', handleClickOutside);
42
  };
43
  }, [showDropdown]);
44
 
45
- // 切换下拉菜单显示状态
46
- const toggleDropdown = () => {
47
- setShowDropdown(!showDropdown);
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
- const rect = selectorRef.current.getBoundingClientRect();
 
61
 
 
62
  return ReactDOM.createPortal(
63
- <div
 
64
  style={{
65
  position: 'absolute',
66
- zIndex: 9999,
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: '0.375rem',
74
- boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
75
- border: '1px solid rgba(0, 0, 0, 0.05)'
 
 
76
  }}
77
  >
78
- <div className="py-1" role="menu" aria-orientation="vertical">
79
- {categories.map((category) => (
80
- <div
 
 
 
 
 
81
  key={category._id}
82
- className="px-4 py-2 text-sm hover:bg-gray-100 cursor-pointer flex items-center"
83
- // 重要: 使用独立的事件处理函数,不依赖冒泡
84
- onClick={(e) => {
85
- e.preventDefault();
86
- e.stopPropagation();
 
 
 
 
 
 
 
 
 
87
  handleCategorySelect(category._id);
88
  }}
89
  >
90
  <div
91
- className="w-3 h-3 rounded-full mr-2"
92
- style={{ backgroundColor: category.color }}
93
- ></div>
94
- {category.name}
95
- {(typeof selectedCategory === 'string' ?
96
- selectedCategory === category._id :
97
- selectedCategory._id === category._id) && (
 
 
 
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="currentColor"
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
- </div>
114
- ))}
115
- </div>
116
  </div>,
117
  document.body
118
  );
119
  };
120
 
121
  return (
122
- <div ref={selectorRef} className={`relative ${className}`}>
123
  <div
124
- className="flex items-center cursor-pointer"
 
 
 
 
 
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
- className="ml-1"
145
  >
146
  <polyline points="6 9 12 15 18 9"></polyline>
147
  </svg>
148
  </div>
149
 
150
- {renderDropdown()}
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
  };