|
|
@@ -0,0 +1,208 @@
|
|
|
+import React, { useState, useEffect } from 'react';
|
|
|
+import { Button } from '@d8d/shared-ui-components/components/ui/button';
|
|
|
+import { Card, CardContent } from '@d8d/shared-ui-components/components/ui/card';
|
|
|
+import { Label } from '@d8d/shared-ui-components/components/ui/label';
|
|
|
+import { Input } from '@d8d/shared-ui-components/components/ui/input';
|
|
|
+import { Switch } from '@d8d/shared-ui-components/components/ui/switch';
|
|
|
+import {
|
|
|
+ Select,
|
|
|
+ SelectContent,
|
|
|
+ SelectItem,
|
|
|
+ SelectTrigger,
|
|
|
+ SelectValue,
|
|
|
+} from '@d8d/shared-ui-components/components/ui/select';
|
|
|
+import { Plus, Trash2, Phone } from 'lucide-react';
|
|
|
+import { toast } from 'sonner';
|
|
|
+
|
|
|
+// 与残疾人的关系枚举
|
|
|
+export const GUARDIAN_RELATIONSHIPS = [
|
|
|
+ '父母',
|
|
|
+ '配偶',
|
|
|
+ '子女',
|
|
|
+ '兄弟姐妹',
|
|
|
+ '祖父母/外祖父母',
|
|
|
+ '其他亲属',
|
|
|
+ '监护人',
|
|
|
+ '其他'
|
|
|
+] as const;
|
|
|
+
|
|
|
+export interface GuardianPhoneItem {
|
|
|
+ phoneNumber: string;
|
|
|
+ relationship: string;
|
|
|
+ isPrimary: number;
|
|
|
+ tempId?: string; // 临时ID用于React key
|
|
|
+}
|
|
|
+
|
|
|
+export interface GuardianPhoneManagementProps {
|
|
|
+ value?: GuardianPhoneItem[];
|
|
|
+ onChange?: (guardianPhones: GuardianPhoneItem[]) => void;
|
|
|
+ maxPhones?: number;
|
|
|
+}
|
|
|
+
|
|
|
+export const GuardianPhoneManagement: React.FC<GuardianPhoneManagementProps> = ({
|
|
|
+ value = [],
|
|
|
+ onChange,
|
|
|
+ maxPhones = 5,
|
|
|
+}) => {
|
|
|
+ const [guardianPhones, setGuardianPhones] = useState<GuardianPhoneItem[]>(value);
|
|
|
+
|
|
|
+ // 同步外部value变化
|
|
|
+ useEffect(() => {
|
|
|
+ setGuardianPhones(value);
|
|
|
+ }, [value]);
|
|
|
+
|
|
|
+ const handleAddGuardianPhone = () => {
|
|
|
+ if (guardianPhones.length >= maxPhones) {
|
|
|
+ toast.warning(`最多只能添加 ${maxPhones} 个监护人电话`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const newGuardianPhone: GuardianPhoneItem = {
|
|
|
+ phoneNumber: '',
|
|
|
+ relationship: '',
|
|
|
+ isPrimary: 0,
|
|
|
+ tempId: `temp-${Date.now()}-${Math.random()}`,
|
|
|
+ };
|
|
|
+
|
|
|
+ const newGuardianPhones = [...guardianPhones, newGuardianPhone];
|
|
|
+ setGuardianPhones(newGuardianPhones);
|
|
|
+ onChange?.(newGuardianPhones);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleRemoveGuardianPhone = (index: number) => {
|
|
|
+ const newGuardianPhones = guardianPhones.filter((_, i) => i !== index);
|
|
|
+ setGuardianPhones(newGuardianPhones);
|
|
|
+ onChange?.(newGuardianPhones);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleFieldChange = (index: number, field: keyof GuardianPhoneItem, value: string | number) => {
|
|
|
+ const newGuardianPhones = [...guardianPhones];
|
|
|
+ newGuardianPhones[index] = { ...newGuardianPhones[index], [field]: value };
|
|
|
+
|
|
|
+ // 如果设置为主要联系人,其他联系人取消主要状态
|
|
|
+ if (field === 'isPrimary' && value === 1) {
|
|
|
+ newGuardianPhones.forEach((phone, i) => {
|
|
|
+ if (i !== index) {
|
|
|
+ phone.isPrimary = 0;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ setGuardianPhones(newGuardianPhones);
|
|
|
+ onChange?.(newGuardianPhones);
|
|
|
+ };
|
|
|
+
|
|
|
+ const validatePhoneNumber = (phoneNumber: string) => {
|
|
|
+ // 中国手机号验证:11位数字,1开头
|
|
|
+ const phoneRegex = /^1[3-9]\d{9}$/;
|
|
|
+ return phoneRegex.test(phoneNumber);
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="space-y-4">
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
+ <Label>监护人电话管理</Label>
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ variant="outline"
|
|
|
+ size="sm"
|
|
|
+ onClick={handleAddGuardianPhone}
|
|
|
+ disabled={guardianPhones.length >= maxPhones}
|
|
|
+ data-testid="add-guardian-phone-button"
|
|
|
+ >
|
|
|
+ <Plus className="h-4 w-4 mr-2" />
|
|
|
+ 添加监护人电话
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {guardianPhones.length === 0 ? (
|
|
|
+ <Card>
|
|
|
+ <CardContent className="pt-6">
|
|
|
+ <div className="flex flex-col items-center justify-center py-8 text-center">
|
|
|
+ <Phone className="h-12 w-12 text-muted-foreground mb-4" />
|
|
|
+ <p className="text-sm text-muted-foreground">暂无监护人电话信息</p>
|
|
|
+ <p className="text-xs text-muted-foreground mt-1">点击"添加监护人电话"按钮添加监护人电话</p>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ ) : (
|
|
|
+ <div className="space-y-4">
|
|
|
+ {guardianPhones.map((phone, index) => (
|
|
|
+ <Card key={phone.tempId || index} className="relative">
|
|
|
+ <CardContent className="pt-6">
|
|
|
+ <div className="absolute top-4 right-4">
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ onClick={() => handleRemoveGuardianPhone(index)}
|
|
|
+ data-testid={`remove-guardian-phone-${index}`}
|
|
|
+ >
|
|
|
+ <Trash2 className="h-4 w-4 text-destructive" />
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Label htmlFor={`phoneNumber-${index}`}>电话号码 *</Label>
|
|
|
+ <Input
|
|
|
+ id={`phoneNumber-${index}`}
|
|
|
+ value={phone.phoneNumber}
|
|
|
+ onChange={(e) => handleFieldChange(index, 'phoneNumber', e.target.value)}
|
|
|
+ placeholder="请输入11位手机号"
|
|
|
+ data-testid={`phone-number-input-${index}`}
|
|
|
+ />
|
|
|
+ {phone.phoneNumber && !validatePhoneNumber(phone.phoneNumber) && (
|
|
|
+ <p className="text-xs text-destructive">请输入有效的11位手机号</p>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Label htmlFor={`relationship-${index}`}>与残疾人的关系 *</Label>
|
|
|
+ <Select
|
|
|
+ value={phone.relationship || ''}
|
|
|
+ onValueChange={(value) => handleFieldChange(index, 'relationship', value)}
|
|
|
+ >
|
|
|
+ <SelectTrigger id={`relationship-${index}`} data-testid={`relationship-select-${index}`}>
|
|
|
+ <SelectValue placeholder="请选择关系" />
|
|
|
+ </SelectTrigger>
|
|
|
+ <SelectContent>
|
|
|
+ {GUARDIAN_RELATIONSHIPS.map((rel) => (
|
|
|
+ <SelectItem key={rel} value={rel}>
|
|
|
+ {rel}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="space-y-2 flex items-center">
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ <Switch
|
|
|
+ checked={phone.isPrimary === 1}
|
|
|
+ onCheckedChange={(checked) => handleFieldChange(index, 'isPrimary', checked ? 1 : 0)}
|
|
|
+ data-testid={`primary-phone-switch-${index}`}
|
|
|
+ />
|
|
|
+ <Label htmlFor={`isPrimary-${index}`}>设为主要联系人</Label>
|
|
|
+ </div>
|
|
|
+ {phone.isPrimary === 1 && (
|
|
|
+ <p className="text-xs text-muted-foreground ml-2">主要联系人</p>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <div className="text-xs text-muted-foreground">
|
|
|
+ <p>• 最多可添加 {maxPhones} 个监护人电话</p>
|
|
|
+ <p>• 只能设置一个主要联系人</p>
|
|
|
+ <p>• 主要联系人将优先用于联系</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default GuardianPhoneManagement;
|