生产管理系统 - 提交用户管理页面代码

This commit is contained in:
2025-11-11 21:10:14 +08:00
parent 9778a0f26a
commit cfe87a78cb
6 changed files with 996 additions and 2 deletions

View File

@@ -0,0 +1,359 @@
/**
* filekorolheader: 修改用户弹窗组件 - 编辑用户功能界面
* 功能:用户信息编辑表单、表单验证、数据更新、状态管理
* 路径:/central-config/tenant/user-management/components/EditUserModal
* 规范遵循crop-x-new/docs/开发项目规范.md使用shadcn语义化样式支持暗色主题
*/
'use client';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { toast } from 'sonner';
import { User } from '../types';
import { fetchEnterprisesForDropdown, transformEnterprisesToOptions, type EnterpriseOption } from './enterpriseApi';
import { PasswordInput } from './PasswordInput';
import { USER_TYPE_OPTIONS, USER_TYPES } from '../constants/userTypes';
interface EditUserModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
user: User | null;
onSuccess?: () => void;
}
interface EditUserFormData {
username: string;
password: string;
fullName: string;
phone: string;
email: string;
userType: typeof USER_TYPES[keyof typeof USER_TYPES];
enterpriseId?: string;
}
export function EditUserModal({ open, onOpenChange, user, onSuccess }: EditUserModalProps) {
const [formData, setFormData] = useState<EditUserFormData>({
username: '',
password: '',
fullName: '',
phone: '',
email: '',
userType: 'tenant',
enterpriseId: '',
});
const [errors, setErrors] = useState<Record<string, string>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [enterprises, setEnterprises] = useState<EnterpriseOption[]>([]);
const [enterpriseOptions, setEnterpriseOptions] = useState<Array<{ value: string; label: string }>>([]);
const [isLoadingEnterprises, setIsLoadingEnterprises] = useState(false);
// 加载企业列表数据
const loadEnterprises = async () => {
try {
setIsLoadingEnterprises(true);
console.log('🏢 EditUserModal - 开始加载企业列表');
const enterpriseData = await fetchEnterprisesForDropdown();
setEnterprises(enterpriseData);
const options = transformEnterprisesToOptions(enterpriseData);
setEnterpriseOptions(options);
console.log('🏢 EditUserModal - 企业列表加载完成:', {
total: enterpriseData.length,
options: options.length,
});
} catch (error) {
console.error('🏢 EditUserModal - 加载企业列表失败:', error);
toast.error('加载企业列表失败,请刷新页面重试');
} finally {
setIsLoadingEnterprises(false);
}
};
// 当弹窗打开时加载企业列表
useEffect(() => {
if (open) {
loadEnterprises();
}
}, [open]);
// 编辑弹窗不预填充用户数据,保持空表单
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!formData.username.trim()) {
newErrors.username = '用户名不能为空';
}
if (!formData.password.trim()) {
newErrors.password = '密码不能为空';
} else if (formData.password.length < 6) {
newErrors.password = '密码长度至少6位';
}
if (!formData.fullName.trim()) {
newErrors.fullName = '姓名不能为空';
}
if (!formData.phone.trim()) {
newErrors.phone = '电话不能为空';
} else if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
newErrors.phone = '请输入正确的手机号码';
}
if (formData.email.trim() && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = '邮箱格式不正确';
}
if (formData.userType === 'tenant' && !formData.enterpriseId?.trim()) {
newErrors.enterpriseId = '企业管理员必须选择所属企业';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async () => {
if (!validateForm() || !user) {
return;
}
setIsSubmitting(true);
try {
// TODO: 调用API更新用户
// 暂时模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000));
const submitData = {
id: user.id,
username: formData.username,
password: formData.password,
full_name: formData.fullName,
phone: formData.phone,
email: formData.email || undefined,
user_type: formData.userType,
...(formData.userType === 'tenant' && formData.enterpriseId ? { enterprise_id: formData.enterpriseId } : {}),
};
console.log('更新用户数据:', submitData);
toast.success('用户信息更新成功');
onOpenChange(false);
onSuccess?.();
} catch (error) {
console.error('更新用户失败:', error);
toast.error('更新用户失败,请重试');
} finally {
setIsSubmitting(false);
}
};
const handleInputChange = (field: keyof EditUserFormData, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
// 清除对应字段的错误
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: '' }));
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[70vw] max-w-4xl">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription className="sr-only">
</DialogDescription>
</DialogHeader>
{!user ? (
<div className="py-4 text-center text-muted-foreground">
</div>
) : (
<>
<div className="space-y-6 py-4">
{/* 第一行:用户名、密码 */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-username"> *</Label>
<Input
id="edit-username"
value={formData.username}
onChange={(e) => handleInputChange('username', e.target.value)}
placeholder="请输入用户名"
className={errors.username ? 'border-red-500' : ''}
/>
{errors.username && (
<p className="text-sm text-red-500 dark:text-red-400">{errors.username}</p>
)}
</div>
<PasswordInput
id="edit-password"
label="密码"
value={formData.password}
onChange={(value) => handleInputChange('password', value)}
placeholder="请输入密码"
required={true}
error={errors.password}
/>
</div>
{/* 第二行:姓名、电话 */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-full_name"> *</Label>
<Input
id="edit-full_name"
value={formData.fullName}
onChange={(e) => handleInputChange('fullName', e.target.value)}
placeholder="请输入姓名"
className={errors.fullName ? 'border-red-500' : ''}
/>
{errors.fullName && (
<p className="text-sm text-red-500 dark:text-red-400">{errors.fullName}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="edit-phone"> *</Label>
<Input
id="edit-phone"
value={formData.phone}
onChange={(e) => handleInputChange('phone', e.target.value)}
placeholder="请输入手机号码"
className={errors.phone ? 'border-red-500' : ''}
/>
{errors.phone && (
<p className="text-sm text-red-500 dark:text-red-400">{errors.phone}</p>
)}
</div>
</div>
{/* 第三行:邮箱 */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-email"></Label>
<Input
id="edit-email"
type="email"
value={formData.email}
onChange={(e) => handleInputChange('email', e.target.value)}
placeholder="请输入邮箱(可选)"
className={errors.email ? 'border-red-500' : ''}
/>
{errors.email && (
<p className="text-sm text-red-500 dark:text-red-400">{errors.email}</p>
)}
</div>
</div>
{/* 第四行:用户类型、所属企业 */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label> *</Label>
<Select
value={formData.userType}
onValueChange={(value: typeof USER_TYPES[keyof typeof USER_TYPES]) => handleInputChange('userType', value)}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{USER_TYPE_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{formData.userType === 'tenant' ? (
<div className="space-y-2">
<Label htmlFor="edit-enterpriseId"> *</Label>
<Select
value={formData.enterpriseId}
onValueChange={(value) => handleInputChange('enterpriseId', value)}
disabled={isLoadingEnterprises}
>
<SelectTrigger>
<SelectValue placeholder={
isLoadingEnterprises
? "正在加载..."
: "请选择所属企业"
} />
</SelectTrigger>
<SelectContent>
{isLoadingEnterprises ? (
<SelectItem value="loading" disabled>
...
</SelectItem>
) : (
enterpriseOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))
)}
</SelectContent>
</Select>
{errors.enterpriseId && (
<p className="text-sm text-red-500 dark:text-red-400">{errors.enterpriseId}</p>
)}
</div>
) : (
<div className="space-y-2">
<Label></Label>
<Input
value="系统管理员无需选择企业"
disabled
className="bg-muted"
/>
</div>
)}
</div>
{/* 用户ID显示 */}
<div className="text-xs text-muted-foreground">
ID: {user.id}
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isSubmitting}
>
</Button>
<Button
onClick={handleSubmit}
disabled={isSubmitting}
>
{isSubmitting ? '更新中...' : '更新'}
</Button>
</DialogFooter>
</>
)}
</DialogContent>
</Dialog>
);
}