|
@@ -23,8 +23,6 @@ import { Checkbox } from '@d8d/shared-ui-components/components/ui/checkbox';
|
|
|
import { Alert, AlertDescription } from '@d8d/shared-ui-components/components/ui/alert';
|
|
import { Alert, AlertDescription } from '@d8d/shared-ui-components/components/ui/alert';
|
|
|
import { AlertCircle } from 'lucide-react';
|
|
import { AlertCircle } from 'lucide-react';
|
|
|
import { AreaSelect } from '@d8d/area-management-ui';
|
|
import { AreaSelect } from '@d8d/area-management-ui';
|
|
|
-import { Form } from '@d8d/shared-ui-components/components/ui/form';
|
|
|
|
|
-import { useForm } from 'react-hook-form';
|
|
|
|
|
import { disabilityClientManager } from '../api/disabilityClient';
|
|
import { disabilityClientManager } from '../api/disabilityClient';
|
|
|
import type {
|
|
import type {
|
|
|
DisabledPersonData,
|
|
DisabledPersonData,
|
|
@@ -43,6 +41,13 @@ const DEFAULT_PAGINATION = {
|
|
|
pageSize: 10,
|
|
pageSize: 10,
|
|
|
} as const;
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
+// 输入验证最大长度
|
|
|
|
|
+const MAX_LENGTH = {
|
|
|
|
|
+ NAME: 50,
|
|
|
|
|
+ DISABILITY_ID: 50,
|
|
|
|
|
+ PHONE: 11,
|
|
|
|
|
+} as const;
|
|
|
|
|
+
|
|
|
// 默认搜索参数
|
|
// 默认搜索参数
|
|
|
const createDefaultSearchParams = () => ({
|
|
const createDefaultSearchParams = () => ({
|
|
|
name: '',
|
|
name: '',
|
|
@@ -135,9 +140,6 @@ const DisabledPersonSelector: React.FC<DisabledPersonSelectorProps> = ({
|
|
|
const [showBlacklistConfirm, setShowBlacklistConfirm] = useState(false);
|
|
const [showBlacklistConfirm, setShowBlacklistConfirm] = useState(false);
|
|
|
const [pendingSelection, setPendingSelection] = useState<DisabledPersonData | DisabledPersonData[] | null>(null);
|
|
const [pendingSelection, setPendingSelection] = useState<DisabledPersonData | DisabledPersonData[] | null>(null);
|
|
|
|
|
|
|
|
- // 为AreaSelect创建表单上下文
|
|
|
|
|
- const form = useForm();
|
|
|
|
|
-
|
|
|
|
|
// 搜索残疾人列表(添加错误处理)
|
|
// 搜索残疾人列表(添加错误处理)
|
|
|
const { data, isLoading, error } = useQuery({
|
|
const { data, isLoading, error } = useQuery({
|
|
|
queryKey: ['disabled-persons-search', searchParams, searchAreaNames],
|
|
queryKey: ['disabled-persons-search', searchParams, searchAreaNames],
|
|
@@ -197,7 +199,7 @@ const DisabledPersonSelector: React.FC<DisabledPersonSelectorProps> = ({
|
|
|
setSearchAreaNames({});
|
|
setSearchAreaNames({});
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
- // 处理选择人员(使用 useCallback 优化)
|
|
|
|
|
|
|
+ // 处理选择人员(使用 useCallback 优化,使用函数式 setState 避免依赖 selectedPersons)
|
|
|
const handleSelectPerson = useCallback((person: DisabledPersonData) => {
|
|
const handleSelectPerson = useCallback((person: DisabledPersonData) => {
|
|
|
if (disabledIds.includes(person.id)) {
|
|
if (disabledIds.includes(person.id)) {
|
|
|
return; // 跳过禁用的人员
|
|
return; // 跳过禁用的人员
|
|
@@ -214,40 +216,43 @@ const DisabledPersonSelector: React.FC<DisabledPersonSelectorProps> = ({
|
|
|
onSelect(person);
|
|
onSelect(person);
|
|
|
onOpenChange(false);
|
|
onOpenChange(false);
|
|
|
} else {
|
|
} else {
|
|
|
- const isSelected = selectedPersons.some(p => p.id === person.id);
|
|
|
|
|
- let newSelectedPersons: DisabledPersonData[];
|
|
|
|
|
-
|
|
|
|
|
- if (isSelected) {
|
|
|
|
|
- newSelectedPersons = selectedPersons.filter(p => p.id !== person.id);
|
|
|
|
|
- } else {
|
|
|
|
|
- newSelectedPersons = [...selectedPersons, person];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- setSelectedPersons(newSelectedPersons);
|
|
|
|
|
|
|
+ // 使用函数式 setState,避免依赖 selectedPersons
|
|
|
|
|
+ setSelectedPersons(prev => {
|
|
|
|
|
+ const isSelected = prev.some(p => p.id === person.id);
|
|
|
|
|
+ if (isSelected) {
|
|
|
|
|
+ return prev.filter(p => p.id !== person.id);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return [...prev, person];
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
- }, [disabledIds, mode, onSelect, onOpenChange, selectedPersons]);
|
|
|
|
|
|
|
+ }, [disabledIds, mode, onSelect, onOpenChange]);
|
|
|
|
|
|
|
|
- // 处理批量选择(使用 useCallback 优化)
|
|
|
|
|
|
|
+ // 处理批量选择(使用 useCallback 优化,使用函数式 setState 避免依赖 selectedPersons)
|
|
|
const handleBatchSelect = useCallback(() => {
|
|
const handleBatchSelect = useCallback(() => {
|
|
|
- console.debug('[DisabledPersonSelector] handleBatchSelect 被调用, selectedPersons:', selectedPersons.length);
|
|
|
|
|
- if (selectedPersons.length === 0) {
|
|
|
|
|
- console.debug('[DisabledPersonSelector] 没有选中人员,返回');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 使用函数式 setState 获取当前值,避免在依赖数组中包含 selectedPersons
|
|
|
|
|
+ setSelectedPersons(currentPersons => {
|
|
|
|
|
+ console.debug('[DisabledPersonSelector] handleBatchSelect 被调用, selectedPersons:', currentPersons.length);
|
|
|
|
|
+ if (currentPersons.length === 0) {
|
|
|
|
|
+ console.debug('[DisabledPersonSelector] 没有选中人员,返回');
|
|
|
|
|
+ return currentPersons;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 检查是否有黑名单人员
|
|
|
|
|
- const blacklistPersons = selectedPersons.filter(p => p.isInBlackList === BLACKLIST_STATUS.IN_BLACKLIST);
|
|
|
|
|
- if (blacklistPersons.length > 0) {
|
|
|
|
|
- console.debug('[DisabledPersonSelector] 检测到黑名单人员');
|
|
|
|
|
- setPendingSelection(selectedPersons);
|
|
|
|
|
- setShowBlacklistConfirm(true);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 检查是否有黑名单人员
|
|
|
|
|
+ const blacklistPersons = currentPersons.filter(p => p.isInBlackList === BLACKLIST_STATUS.IN_BLACKLIST);
|
|
|
|
|
+ if (blacklistPersons.length > 0) {
|
|
|
|
|
+ console.debug('[DisabledPersonSelector] 检测到黑名单人员');
|
|
|
|
|
+ setPendingSelection(currentPersons);
|
|
|
|
|
+ setShowBlacklistConfirm(true);
|
|
|
|
|
+ return currentPersons;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- console.debug('[DisabledPersonSelector] 调用 onSelect, 人员数量:', selectedPersons.length);
|
|
|
|
|
- onSelect(selectedPersons);
|
|
|
|
|
- onOpenChange(false);
|
|
|
|
|
- }, [onSelect, onOpenChange, selectedPersons]);
|
|
|
|
|
|
|
+ console.debug('[DisabledPersonSelector] 调用 onSelect, 人员数量:', currentPersons.length);
|
|
|
|
|
+ onSelect(currentPersons);
|
|
|
|
|
+ onOpenChange(false);
|
|
|
|
|
+ return currentPersons;
|
|
|
|
|
+ });
|
|
|
|
|
+ }, [onSelect, onOpenChange]);
|
|
|
|
|
|
|
|
// 确认选择黑名单人员(使用 useCallback 优化)
|
|
// 确认选择黑名单人员(使用 useCallback 优化)
|
|
|
const handleConfirmBlacklistSelection = useCallback(() => {
|
|
const handleConfirmBlacklistSelection = useCallback(() => {
|
|
@@ -304,6 +309,7 @@ const DisabledPersonSelector: React.FC<DisabledPersonSelectorProps> = ({
|
|
|
<Input
|
|
<Input
|
|
|
id="name"
|
|
id="name"
|
|
|
placeholder="输入姓名"
|
|
placeholder="输入姓名"
|
|
|
|
|
+ maxLength={MAX_LENGTH.NAME}
|
|
|
value={searchParams.name || ''}
|
|
value={searchParams.name || ''}
|
|
|
onChange={(e) => setSearchParams(prev => ({ ...prev, name: e.target.value }))}
|
|
onChange={(e) => setSearchParams(prev => ({ ...prev, name: e.target.value }))}
|
|
|
data-testid="search-name-input"
|
|
data-testid="search-name-input"
|
|
@@ -333,6 +339,7 @@ const DisabledPersonSelector: React.FC<DisabledPersonSelectorProps> = ({
|
|
|
<Input
|
|
<Input
|
|
|
id="disabilityId"
|
|
id="disabilityId"
|
|
|
placeholder="输入残疾证号"
|
|
placeholder="输入残疾证号"
|
|
|
|
|
+ maxLength={MAX_LENGTH.DISABILITY_ID}
|
|
|
value={searchParams.disabilityId || ''}
|
|
value={searchParams.disabilityId || ''}
|
|
|
onChange={(e) => setSearchParams(prev => ({ ...prev, disabilityId: e.target.value }))}
|
|
onChange={(e) => setSearchParams(prev => ({ ...prev, disabilityId: e.target.value }))}
|
|
|
data-testid="search-disability-id-input"
|
|
data-testid="search-disability-id-input"
|
|
@@ -344,6 +351,7 @@ const DisabledPersonSelector: React.FC<DisabledPersonSelectorProps> = ({
|
|
|
<Input
|
|
<Input
|
|
|
id="phone"
|
|
id="phone"
|
|
|
placeholder="输入联系电话"
|
|
placeholder="输入联系电话"
|
|
|
|
|
+ maxLength={MAX_LENGTH.PHONE}
|
|
|
value={searchParams.phone || ''}
|
|
value={searchParams.phone || ''}
|
|
|
onChange={(e) => setSearchParams(prev => ({ ...prev, phone: e.target.value }))}
|
|
onChange={(e) => setSearchParams(prev => ({ ...prev, phone: e.target.value }))}
|
|
|
data-testid="search-phone-input"
|
|
data-testid="search-phone-input"
|
|
@@ -354,27 +362,24 @@ const DisabledPersonSelector: React.FC<DisabledPersonSelectorProps> = ({
|
|
|
{/* 第二行:区域选择器单独一行 */}
|
|
{/* 第二行:区域选择器单独一行 */}
|
|
|
<div className="space-y-2">
|
|
<div className="space-y-2">
|
|
|
<Label>省份/城市/区县</Label>
|
|
<Label>省份/城市/区县</Label>
|
|
|
- {/* AreaSelect需要Form上下文,所以包装在Form中 */}
|
|
|
|
|
- <Form {...form}>
|
|
|
|
|
- <AreaSelect
|
|
|
|
|
- value={areaSelection}
|
|
|
|
|
- onChange={(value) => {
|
|
|
|
|
- // 转换类型以确保类型兼容
|
|
|
|
|
- setAreaSelection({
|
|
|
|
|
- provinceId: typeof value.provinceId === 'number' ? value.provinceId : undefined,
|
|
|
|
|
- cityId: typeof value.cityId === 'number' ? value.cityId : undefined,
|
|
|
|
|
- districtId: typeof value.districtId === 'number' ? value.districtId : undefined,
|
|
|
|
|
- });
|
|
|
|
|
- // 同时更新用于搜索的地区名称
|
|
|
|
|
- setSearchAreaNames({
|
|
|
|
|
- province: value.provinceId?.toString(),
|
|
|
|
|
- city: value.cityId?.toString(),
|
|
|
|
|
- district: value.districtId?.toString(),
|
|
|
|
|
- });
|
|
|
|
|
- }}
|
|
|
|
|
- data-testid="area-select"
|
|
|
|
|
- />
|
|
|
|
|
- </Form>
|
|
|
|
|
|
|
+ <AreaSelect
|
|
|
|
|
+ value={areaSelection}
|
|
|
|
|
+ onChange={(value) => {
|
|
|
|
|
+ // 转换类型以确保类型兼容
|
|
|
|
|
+ setAreaSelection({
|
|
|
|
|
+ provinceId: typeof value.provinceId === 'number' ? value.provinceId : undefined,
|
|
|
|
|
+ cityId: typeof value.cityId === 'number' ? value.cityId : undefined,
|
|
|
|
|
+ districtId: typeof value.districtId === 'number' ? value.districtId : undefined,
|
|
|
|
|
+ });
|
|
|
|
|
+ // 同时更新用于搜索的地区名称
|
|
|
|
|
+ setSearchAreaNames({
|
|
|
|
|
+ province: value.provinceId?.toString(),
|
|
|
|
|
+ city: value.cityId?.toString(),
|
|
|
|
|
+ district: value.districtId?.toString(),
|
|
|
|
|
+ });
|
|
|
|
|
+ }}
|
|
|
|
|
+ data-testid="area-select"
|
|
|
|
|
+ />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* 第三行:其他筛选条件和操作按钮 - 响应式网格 */}
|
|
{/* 第三行:其他筛选条件和操作按钮 - 响应式网格 */}
|