Просмотр исходного кода

feat: 企业选择器添加搜索功能

- 将企业选择器从基础 Select 组件改为 Popover + Command 组合
- 支持通过公司名称搜索企业
- 搜索时调用 searchCompanies API 接口
- 无搜索词时显示前50条企业数据
- 优化用户体验,解决企业数量过多难以选择的问题

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 3 недель назад
Родитель
Сommit
eadec63981

+ 137 - 63
allin-packages/company-management-ui/src/components/CompanySelector.tsx

@@ -1,12 +1,21 @@
-import React from 'react';
+import React, { useState } from 'react';
 import { useQuery } from '@tanstack/react-query';
 import { useQuery } from '@tanstack/react-query';
 import {
 import {
-  Select,
-  SelectContent,
-  SelectItem,
-  SelectTrigger,
-  SelectValue,
-} from '@d8d/shared-ui-components/components/ui/select';
+  Popover,
+  PopoverContent,
+  PopoverTrigger,
+} from '@d8d/shared-ui-components/components/ui/popover';
+import {
+  Command,
+  CommandEmpty,
+  CommandGroup,
+  CommandInput,
+  CommandItem,
+  CommandList,
+} from '@d8d/shared-ui-components/components/ui/command';
+import { Button } from '@d8d/shared-ui-components/components/ui/button';
+import { Check, ChevronsUpDown, Building2 } from 'lucide-react';
+import { cn } from '@d8d/shared-ui-components/utils/cn';
 import { companyClientManager } from '../api/companyClient';
 import { companyClientManager } from '../api/companyClient';
 
 
 interface CompanySelectorProps {
 interface CompanySelectorProps {
@@ -28,79 +37,144 @@ export const CompanySelector: React.FC<CompanySelectorProps> = ({
   'data-testid': testId,
   'data-testid': testId,
   platformId,
   platformId,
 }) => {
 }) => {
+  const [open, setOpen] = useState(false);
+  const [searchKeyword, setSearchKeyword] = useState('');
+
+  // 搜索公司
   const {
   const {
-    data: companies,
-    isLoading,
-    isError,
+    data: searchResults,
+    isLoading: isSearching,
   } = useQuery({
   } = useQuery({
-    queryKey: ['companies', platformId],
+    queryKey: ['searchCompanies', searchKeyword, platformId],
     queryFn: async () => {
     queryFn: async () => {
       const client = companyClientManager.get();
       const client = companyClientManager.get();
 
 
-      let res;
-      if (platformId) {
-        // 按平台过滤公司
-        res = await client.getCompaniesByPlatform[':platformId'].$get({
-          param: { platformId }
-        });
-      } else {
-        // 获取所有公司(分页,取前100条)
-        res = await client.getAllCompanies.$get({
+      if (searchKeyword.trim()) {
+        // 有搜索词时使用搜索接口
+        const res = await client.searchCompanies.$get({
           query: {
           query: {
+            name: searchKeyword.trim(),
             skip: 0,
             skip: 0,
-            take: 100
+            take: 50
           }
           }
         });
         });
+
+        if (res.status !== 200) throw new Error('搜索公司失败');
+        return await res.json();
+      } else {
+        // 无搜索词时获取所有公司(前50条)
+        if (platformId) {
+          const res = await client.getCompaniesByPlatform[':platformId'].$get({
+            param: { platformId }
+          });
+          if (res.status !== 200) throw new Error('获取公司列表失败');
+          const companies = await res.json();
+          // 转换为统一格式
+          return {
+            data: companies,
+            total: companies.length
+          };
+        } else {
+          const res = await client.getAllCompanies.$get({
+            query: {
+              skip: 0,
+              take: 50
+            }
+          });
+          if (res.status !== 200) throw new Error('获取公司列表失败');
+          return await res.json();
+        }
       }
       }
+    },
+    enabled: open, // 只在下拉打开时查询
+  });
 
 
-      if (res.status !== 200) throw new Error('获取公司列表失败');
+  // 获取当前选中的公司名称
+  const {
+    data: selectedCompany,
+  } = useQuery({
+    queryKey: ['company', value],
+    queryFn: async () => {
+      if (!value) return null;
+      const client = companyClientManager.get();
+      const res = await client.getCompany[':id'].$get({
+        param: { id: value }
+      });
+      if (res.status !== 200) throw new Error('获取公司详情失败');
       return await res.json();
       return await res.json();
     },
     },
+    enabled: !!value,
   });
   });
 
 
-  if (isError) {
-    return (
-      <div className="text-sm text-destructive">
-        加载公司列表失败
-      </div>
-    );
-  }
-
-  // 处理响应数据格式
-  let companyList: Array<{ id: number; companyName: string; platformId?: number }> = [];
-
-  if (companies) {
-    if (platformId) {
-      // getCompaniesByPlatform 返回的是数组
-      companyList = companies as Array<{ id: number; companyName: string; platformId?: number }>;
-    } else {
-      // getAllCompanies 返回的是 { data: [], total: number }
-      companyList = (companies as { data: Array<{ id: number; companyName: string; platformId?: number }> }).data || [];
-    }
-  }
+  const companyList = searchResults?.data || [];
 
 
   return (
   return (
-    <Select
-      value={value?.toString() || ''}
-      onValueChange={(val) => {
-        if (val) {
-          onChange?.(parseInt(val));
-        }
-      }}
-      disabled={disabled || isLoading || companyList.length === 0}
-    >
-      <SelectTrigger className={className} data-testid={testId}>
-        <SelectValue placeholder={isLoading ? '加载中...' : placeholder} />
-      </SelectTrigger>
-      <SelectContent>
-        {companyList.map((company) => (
-          <SelectItem key={company.id} value={company.id.toString()}>
-            {company.companyName}
-          </SelectItem>
-        ))}
-      </SelectContent>
-    </Select>
+    <Popover open={open} onOpenChange={setOpen}>
+      <PopoverTrigger asChild>
+        <Button
+          variant="outline"
+          role="combobox"
+          aria-expanded={open}
+          disabled={disabled}
+          data-testid={testId}
+          className={cn(
+            "w-full justify-between font-normal",
+            !value && "text-muted-foreground",
+            className
+          )}
+        >
+          <span className="flex items-center gap-2 truncate">
+            <Building2 className="h-4 w-4 shrink-0 opacity-50" />
+            {selectedCompany?.companyName || placeholder}
+          </span>
+          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+        </Button>
+      </PopoverTrigger>
+      <PopoverContent className="w-[--radix-popover-trigger-width] p-0" align="start">
+        <Command shouldFilter={false}>
+          <CommandInput
+            placeholder="搜索公司名称..."
+            value={searchKeyword}
+            onValueChange={setSearchKeyword}
+          />
+          <CommandList>
+            {isSearching ? (
+              <div className="py-6 text-center text-sm text-muted-foreground">
+                搜索中...
+              </div>
+            ) : companyList.length === 0 ? (
+              <CommandEmpty>
+                {searchKeyword ? "未找到匹配的公司" : "暂无公司数据"}
+              </CommandEmpty>
+            ) : (
+              <CommandGroup>
+                {companyList.map((company) => (
+                  <CommandItem
+                    key={company.id}
+                    value={company.id.toString()}
+                    onSelect={(currentValue) => {
+                      const selectedId = parseInt(currentValue);
+                      onChange?.(selectedId);
+                      setOpen(false);
+                      setSearchKeyword('');
+                    }}
+                  >
+                    <Check
+                      className={cn(
+                        "mr-2 h-4 w-4",
+                        value === company.id ? "opacity-100" : "opacity-0"
+                      )}
+                    />
+                    {company.companyName}
+                  </CommandItem>
+                ))}
+              </CommandGroup>
+            )}
+          </CommandList>
+        </Command>
+      </PopoverContent>
+    </Popover>
   );
   );
 };
 };
 
 
-export default CompanySelector;
+export default CompanySelector;

+ 1 - 13
mini/src/pages/yongren/order/detail/index.tsx

@@ -513,21 +513,9 @@ const OrderDetail: React.FC = () => {
               <Text className="text-gray-800">{order.actualPeople}人</Text>
               <Text className="text-gray-800">{order.actualPeople}人</Text>
             </View>
             </View>
             <View className="flex flex-col">
             <View className="flex flex-col">
-              <Text className="text-gray-500">预计开始</Text>
+              <Text className="text-gray-500">开始时间</Text>
               <Text className="text-gray-800">{order.expectedStartDate}</Text>
               <Text className="text-gray-800">{order.expectedStartDate}</Text>
             </View>
             </View>
-            <View className="flex flex-col">
-              <Text className="text-gray-500">实际开始</Text>
-              <Text className="text-gray-800">{order.actualStartDate}</Text>
-            </View>
-            <View className="flex flex-col">
-              <Text className="text-gray-500">实际结束</Text>
-              <Text className="text-gray-800">{order.actualEndDate || '未结束'}</Text>
-            </View>
-            <View className="flex flex-col">
-              <Text className="text-gray-500">渠道</Text>
-              <Text className="text-gray-800">{order.channel}</Text>
-            </View>
           </View>
           </View>
         </View>
         </View>
 
 

+ 1 - 1
mini/src/pages/yongren/order/list/index.tsx

@@ -81,7 +81,7 @@ const OrderCard: React.FC<OrderCardProps> = ({ order, onViewDetail }) => {
           <Text className="font-semibold text-gray-800">{order.name}</Text>
           <Text className="font-semibold text-gray-800">{order.name}</Text>
           <Text className="text-xs text-gray-500">{order.createdAt} 创建</Text>
           <Text className="text-xs text-gray-500">{order.createdAt} 创建</Text>
         </View>
         </View>
-        <Text className={`text-xs px-2 py-1 rounded-full ${order.statusClass}`}>
+        <Text className={`text-xs px-2 py-1 rounded-full whitespace-nowrap ${order.statusClass}`}>
           {order.statusLabel}
           {order.statusLabel}
         </Text>
         </Text>
       </View>
       </View>