Ver código fonte

♻️ refactor(combobox): 优化组合框组件结构与性能

- 将 Command 逻辑封装为独立的 ComboboxCommand 组件,确保 options 更新时重新渲染
- 添加调试日志以便追踪选项变化

🐛 fix(ActivitySelect): 修复搜索关键词变化时选项不更新的问题

- 将 searchKeyword 添加到 useMemo 依赖数组中,确保搜索时重新计算选项
- 添加调试日志追踪活动数据和搜索关键词变化
yourname 4 meses atrás
pai
commit
f0f51dd1cd

+ 2 - 1
src/client/admin/components/ActivitySelect.tsx

@@ -58,13 +58,14 @@ export function ActivitySelect({
 
   // 将活动数据转换为 Combobox 选项
   const activityOptions = React.useMemo(() => {
+    console.log('activitiesData updated:', activitiesData?.data?.length, 'searchKeyword:', searchKeyword)
     if (!activitiesData?.data) return []
 
     return activitiesData.data.map((activity: ActivityResponse) => ({
       value: activity.id.toString(),
       label: `${activity.name} (${getActivityTypeLabel(activity.type)})`,
     }))
-  }, [activitiesData])
+  }, [activitiesData, searchKeyword])
 
 
   const handleValueChange = (newValue: string) => {

+ 68 - 29
src/client/components/ui/combobox.tsx

@@ -36,6 +36,61 @@ interface ComboboxProps {
   disabled?: boolean
 }
 
+// 将 Command 逻辑封装为独立组件,确保 options 更新时重新渲染
+interface ComboboxCommandProps {
+  options: ComboboxOption[]
+  value?: string
+  onValueChange?: (value: string) => void
+  onSearchChange?: (search: string) => void
+  searchPlaceholder?: string
+  emptyMessage?: string
+  onClose: () => void
+}
+
+function ComboboxCommand({
+  options,
+  value,
+  onValueChange,
+  onSearchChange,
+  searchPlaceholder = "搜索...",
+  emptyMessage = "未找到匹配项",
+  onClose,
+}: ComboboxCommandProps) {
+  console.debug('ComboboxCommand options', options)
+
+  return (
+    <Command shouldFilter={false}>
+      <CommandInput
+        placeholder={searchPlaceholder}
+        onValueChange={onSearchChange}
+      />
+      <CommandList>
+        <CommandEmpty>{emptyMessage}</CommandEmpty>
+        <CommandGroup>
+          {options.map((option) => (
+            <CommandItem
+              key={option.value}
+              value={option.value}
+              onSelect={(currentValue) => {
+                onValueChange?.(currentValue === value ? "" : currentValue)
+                onClose()
+              }}
+            >
+              <CheckIcon
+                className={cn(
+                  "mr-2 h-4 w-4",
+                  value === option.value ? "opacity-100" : "opacity-0"
+                )}
+              />
+              {option.label}
+            </CommandItem>
+          ))}
+        </CommandGroup>
+      </CommandList>
+    </Command>
+  )
+}
+
 export function Combobox({
   options,
   value,
@@ -49,6 +104,10 @@ export function Combobox({
 }: ComboboxProps) {
   const [open, setOpen] = React.useState(false)
 
+  const handleClose = () => {
+    setOpen(false)
+  }
+
   return (
     <Popover open={open} onOpenChange={setOpen}>
       <PopoverTrigger asChild>
@@ -66,35 +125,15 @@ export function Combobox({
         </Button>
       </PopoverTrigger>
       <PopoverContent className="w-full p-0" align="start">
-        <Command>
-          <CommandInput
-            placeholder={searchPlaceholder}
-            onValueChange={onSearchChange}
-          />
-          <CommandList>
-            <CommandEmpty>{emptyMessage}</CommandEmpty>
-            <CommandGroup>
-              {options.map((option) => (
-                <CommandItem
-                  key={option.value}
-                  value={option.value}
-                  onSelect={(currentValue) => {
-                    onValueChange?.(currentValue === value ? "" : currentValue)
-                    setOpen(false)
-                  }}
-                >
-                  <CheckIcon
-                    className={cn(
-                      "mr-2 h-4 w-4",
-                      value === option.value ? "opacity-100" : "opacity-0"
-                    )}
-                  />
-                  {option.label}
-                </CommandItem>
-              ))}
-            </CommandGroup>
-          </CommandList>
-        </Command>
+        <ComboboxCommand
+          options={options}
+          value={value}
+          onValueChange={onValueChange}
+          onSearchChange={onSearchChange}
+          searchPlaceholder={searchPlaceholder}
+          emptyMessage={emptyMessage}
+          onClose={handleClose}
+        />
       </PopoverContent>
     </Popover>
   )