check-filter-检查并修正筛选表单.md 11 KB


description: "检查并修正筛选表单指令"

筛选表单检查与修复指南

问题描述

在使用 React Hook Form 和状态管理进行筛选表单开发时,常见的同步问题包括:

  1. 表单状态与查询参数不同步:重置操作只清除查询参数,但表单字段值保持不变
  2. 表单验证问题:字段验证规则不完整或错误
  3. UI状态不一致:筛选条件显示与实际查询条件不符

检查步骤

1. 检查表单重置功能

# 检查 handleReset 函数是否同时重置表单和查询参数
grep -n "handleReset" src/**/*.tsx

2. 检查表单字段同步

# 检查表单字段是否与查询参数正确映射
grep -n "setSearchParams\|form.reset" src/**/*.tsx

3. 验证表单字段定义

# 检查所有表单字段是否正确定义
grep -n "FormField\|control" src/**/*.tsx | head -10

常见问题及修复方案

问题1: 重置功能不完整

症状:点击重置按钮后,查询参数被清除但表单字段值保持不变

修复方案

// 错误示例 - 只重置查询参数
const handleReset = () => {
  setSearchParams({
    status: undefined,
    keyword: undefined,
    // ...其他参数
  });
};

// 正确示例 - 同时重置表单和查询参数
const handleReset = () => {
  // 重置表单字段值
  form.reset({
    status: undefined,
    keyword: undefined,
    dateRange: undefined,
    // ...所有表单字段
  });
  
  // 重置搜索参数
  setSearchParams({
    status: undefined,
    keyword: undefined,
    startDate: undefined,
    endDate: undefined,
    // ...对应查询参数
  });
};

问题2: Select组件缺少"全部"选项

症状:筛选表单中的Select组件缺少"全部"选项,用户无法取消筛选

修复方案

// 错误示例 - 缺少全部选项
<SelectContent>
  {Object.values(StatusEnum).map(status => (
    <SelectItem key={status} value={status}>
      {StatusNameMap[status]}
    </SelectItem>
  ))}
</SelectContent>

// 正确示例 - 添加全部选项(使用特殊值"all"并转换为undefined)
<Select onValueChange={(value) => field.onChange(value === "all" ? undefined : value)} value={field.value || "all"}>
  <FormControl>
    <SelectTrigger>
      <SelectValue placeholder="全部状态" />
    </SelectTrigger>
  </FormControl>
  <SelectContent>
    <SelectItem value="all">全部状态</SelectItem>
    {Object.values(StatusEnum).map(status => (
      <SelectItem key={status} value={status}>
        {StatusNameMap[status]}
      </SelectItem>
    ))}
  </SelectContent>
</Select>

注意事项

  • Radix UI Select组件不允许使用空字符串作为SelectItem的value
  • 使用特殊值"all"来表示全部选项,在onChange时转换为undefined
  • 在value prop中使用 field.value || "all" 来确保选中状态正确显示

问题2: 表单字段映射错误

症状:表单字段名称与查询参数名称不一致

修复方案

// 确保表单字段名与查询参数名正确映射
const handleSearch = (values: any) => {
  setSearchParams({
    status: values.status,           // 表单字段名 -> 查询参数名
    keyword: values.keyword,         // 保持一致
    startDate: values.dateRange?.[0]?.toISOString(),  // 复杂映射
    endDate: values.dateRange?.[1]?.toISOString(),
  });
};

问题3: 初始状态不一致

症状:组件挂载时表单初始值与查询参数不匹配

修复方案

// 使用 useEffect 同步初始状态
useEffect(() => {
  const initialValues = {
    status: searchParams.status || undefined,
    keyword: searchParams.keyword || undefined,
    dateRange: searchParams.startDate && searchParams.endDate 
      ? [new Date(searchParams.startDate), new Date(searchParams.endDate)]
      : undefined,
  };
  form.reset(initialValues);
}, []);

通用CRUD filters参数使用指南

问题4: 未使用通用CRUD的filters参数

症状:页面使用独立的查询参数而不是通用CRUD的标准filters参数

修复方案

// 错误示例 - 使用独立参数
const res = await client.$get({
  query: {
    page: 1,
    pageSize: 10,
    status: searchParams.status,
    problemType: searchParams.problemType,
    keyword: searchParams.keyword,
    startDate: searchParams.startDate,
    endDate: searchParams.endDate,
  }
});

// 正确示例 - 使用通用CRUD filters参数
const filters: any = {};
if (searchParams.status) filters.status = searchParams.status;
if (searchParams.problemType) filters.problemType = searchParams.problemType;
if (searchParams.startDate && searchParams.endDate) {
  filters.createdAt = {
    between: [searchParams.startDate, searchParams.endDate]
  };
}

const res = await client.$get({
  query: {
    page: 1,
    pageSize: 10,
    keyword: searchParams.keyword,  // 独立的关键词参数
    filters: Object.keys(filters).length > 0 ? JSON.stringify(filters) : undefined,
  }
});

问题5: 关联字段搜索与筛选不支持

症状:无法通过关联实体的字段进行搜索和筛选(如设备名称、用户名称等)

修复方案

  1. 配置searchFields和relations:

    const routes = createCrudRoutes({
    entity: YourEntity,
    searchFields: ['title', 'description', 'device.zichanInfo.assetName'], // 支持关联字段搜索(格式:relation.field 或 relation.nestedRelation.field)
    relations: ['device.zichanInfo'], // 确保关联数据被加载
    // ...其他配置
    });
    
  2. 使用关联字段筛选:

    // 在filters参数中使用关联字段进行精确筛选
    const filters = {
    'device.zichanInfo.assetName': '服务器001',           // 精确匹配
    'device.zichanInfo.status': { gte: 1 },              // 范围查询
    'device.zichanInfo.department.name': '%IT%'          // 模糊匹配
    };
    
    const res = await client.$get({
    query: {
    page: 1,
    pageSize: 10,
    filters: JSON.stringify(filters)
    }
    });
    

别名生成规则

  • 单级关联:device.zichanInfo → 别名:device_zichanInfo
  • 嵌套关联:device.zichanInfo.department → 别名:device_zichanInfo_department
  • 字段引用:device_zichanInfo.assetName, device_zichanInfo_department.name

支持的筛选操作

  • 精确匹配:{ "relation.field": "value" }
  • 模糊匹配:{ "relation.field": "%value%" }
  • 范围查询:{ "relation.field": { "gte": min, "lte": max } }
  • IN查询:{ "relation.field": [value1, value2, ...] }

完整代码示例

修复后的筛选表单组件(使用通用CRUD filters)

import React from 'react';
import { useForm } from 'react-hook-form';
import { Form, FormField, FormItem, FormLabel, FormControl } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from '@/components/ui/button';
import { workOrderClient } from '@/client/api';

export function FilterForm() {
  const [searchParams, setSearchParams] = useState({});
  const form = useForm();

  // 搜索处理 - 使用通用CRUD filters参数
  const handleSearch = (values: any) => {
    // 构建filters对象
    const filters: any = {};
    if (values.status) filters.status = values.status;
    if (values.problemType) filters.problemType = values.problemType;
    if (values.dateRange?.[0] && values.dateRange?.[1]) {
      filters.createdAt = {
        between: [values.dateRange[0], values.dateRange[1]]
      };
    }

    setSearchParams({
      keyword: values.keyword,
      filters: Object.keys(filters).length > 0 ? JSON.stringify(filters) : undefined,
    });
  };

  // 重置处理 - 修复后的版本
  const handleReset = () => {
    // 重置表单字段值
    form.reset({
      status: undefined,
      keyword: undefined,
      problemType: undefined,
      dateRange: undefined,
    });
    
    // 重置搜索参数
    setSearchParams({
      keyword: undefined,
      filters: undefined,
    });
  };

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(handleSearch)}>
        <FormField
          control={form.control}
          name="status"
          render={({ field }) => (
            <FormItem>
              <FormLabel>状态</FormLabel>
              <Select onValueChange={(value) => field.onChange(value === "all" ? undefined : value)} value={field.value || "all"}>
                <FormControl>
                  <SelectTrigger>
                    <SelectValue placeholder="选择状态" />
                  </SelectTrigger>
                </FormControl>
                <SelectContent>
                  <SelectItem value="all">全部状态</SelectItem>
                  <SelectItem value="active">活跃</SelectItem>
                  <SelectItem value="inactive">非活跃</SelectItem>
                </SelectContent>
              </Select>
            </FormItem>
          )}
        />
        
        <FormField
          control={form.control}
          name="keyword"
          render={({ field }) => (
            <FormItem>
              <FormLabel>关键字</FormLabel>
              <FormControl>
                <Input placeholder="输入关键字" {...field} />
              </FormControl>
            </FormItem>
          )}
        />
        
        <div className="flex gap-2">
          <Button type="submit">查询</Button>
          <Button type="button" variant="outline" onClick={handleReset}>
            重置
          </Button>
        </div>
      </form>
    </Form>
  );
}

最佳实践

  1. 保持同步:始终确保表单状态与查询参数同步
  2. 完整重置:重置操作应同时清除表单字段和查询参数
  3. 类型安全:为表单值和查询参数定义明确的 TypeScript 类型
  4. 测试验证:编写测试用例验证重置功能正常工作
  5. 用户体验:重置后应自动触发数据重新加载

自动化检查脚本

#!/bin/bash
# 检查筛选表单常见问题

echo "=== 筛选表单检查 ==="

# 检查 handleReset 函数
echo "1. 检查 handleReset 函数:"
grep -A 10 -B 2 "handleReset" src/**/*.tsx | grep -E "setSearchParams|form.reset"

# 检查表单字段映射
echo "2. 检查表单字段映射:"
grep -A 5 -B 5 "handleSearch" src/**/*.tsx

# 检查初始状态同步
echo "3. 检查初始状态同步:"
grep -A 10 -B 2 "useEffect.*searchParams" src/**/*.tsx

# 检查通用CRUD filters参数使用
echo "4. 检查通用CRUD filters参数使用:"
grep -A 5 -B 5 "filters.*JSON.stringify" src/**/*.tsx

# 检查关联字段搜索配置
echo "5. 检查关联字段搜索配置:"
grep -A 3 -B 3 "searchFields.*\." src/server/api/**/index.ts

echo "=== 检查完成 ==="

最佳实践更新

  1. 标准接口:优先使用通用CRUD的filters参数而不是独立参数
  2. 关联搜索:合理配置searchFields和relations支持关联字段搜索
  3. 性能优化:避免在filters中使用过多关联查询,必要时使用自定义服务方法

使用此指令可以快速识别和修复筛选表单中的常见同步问题,包括通用CRUD filters参数的使用和关联字段搜索功能。

使用此指令可以快速识别和修复筛选表单中的常见同步问题。