生产管理系统前端 开发中心配置系统 所有页面
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Employee, UserStatus } from '../types';
|
||||
|
||||
interface EmployeeDetailDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
selectedEmployee: Employee | null;
|
||||
}
|
||||
|
||||
export function EmployeeDetailDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
selectedEmployee
|
||||
}: EmployeeDetailDialogProps) {
|
||||
const getStatusBadge = (status: UserStatus) => {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return <Badge className="bg-green-100 text-green-700">正常</Badge>;
|
||||
case 'frozen':
|
||||
return <Badge className="bg-gray-100 text-gray-700">已冻结</Badge>;
|
||||
case 'inactive':
|
||||
return <Badge className="bg-red-100 text-red-700">停用</Badge>;
|
||||
default:
|
||||
return <Badge>{status}</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
if (!selectedEmployee) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>员工详情</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
查看员工的详细信息
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label>姓名</Label>
|
||||
<div className="mt-1">{selectedEmployee.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>用户名</Label>
|
||||
<div className="mt-1">{selectedEmployee.username}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>电话</Label>
|
||||
<div className="mt-1">{selectedEmployee.phone}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>邮箱</Label>
|
||||
<div className="mt-1">{selectedEmployee.email || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>部门</Label>
|
||||
<div className="mt-1">{selectedEmployee.department || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>职位</Label>
|
||||
<div className="mt-1">{selectedEmployee.position || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>状态</Label>
|
||||
<div className="mt-1">{getStatusBadge(selectedEmployee.status)}</div>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<Label>角色</Label>
|
||||
<div className="mt-1 flex flex-wrap gap-2">
|
||||
{selectedEmployee.roles && selectedEmployee.roles.length > 0 ? (
|
||||
selectedEmployee.roles.map((role, index) => (
|
||||
<Badge key={index} className="bg-purple-100 text-purple-700">
|
||||
{role}
|
||||
</Badge>
|
||||
))
|
||||
) : (
|
||||
<span className="text-muted-foreground">未分配角色</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{selectedEmployee.lastLoginTime && (
|
||||
<div>
|
||||
<Label>最后登录</Label>
|
||||
<div className="mt-1">
|
||||
{new Date(selectedEmployee.lastLoginTime).toLocaleString('zh-CN')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Label>创建时间</Label>
|
||||
<div className="mt-1">
|
||||
{new Date(selectedEmployee.createdAt).toLocaleString('zh-CN')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>更新时间</Label>
|
||||
<div className="mt-1">
|
||||
{new Date(selectedEmployee.updatedAt).toLocaleString('zh-CN')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Employee, Role, EmployeeFormData } from '../types';
|
||||
|
||||
interface EmployeeFormDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
editingEmployee: Employee | null;
|
||||
formData: EmployeeFormData;
|
||||
onFormDataChange: (data: EmployeeFormData) => void;
|
||||
onSave: () => void;
|
||||
roles: Role[];
|
||||
}
|
||||
|
||||
export function EmployeeFormDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
editingEmployee,
|
||||
formData,
|
||||
onFormDataChange,
|
||||
onSave,
|
||||
roles
|
||||
}: EmployeeFormDialogProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingEmployee ? '编辑员工' : '添加员工'}</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
{editingEmployee ? '编辑员工信息' : '添加新员工'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="username">用户名 *</Label>
|
||||
<Input
|
||||
id="username"
|
||||
value={formData.username || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, username: e.target.value })}
|
||||
placeholder="登录用户名"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="name">姓名 *</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={formData.name || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, name: e.target.value })}
|
||||
placeholder="真实姓名"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="phone">电话 *</Label>
|
||||
<Input
|
||||
id="phone"
|
||||
value={formData.phone || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, phone: e.target.value })}
|
||||
placeholder="手机号码"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="email">邮箱</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={formData.email || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, email: e.target.value })}
|
||||
placeholder="电子邮箱"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="department">部门</Label>
|
||||
<Input
|
||||
id="department"
|
||||
value={formData.department || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, department: e.target.value })}
|
||||
placeholder="所属部门"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="position">职位</Label>
|
||||
<Input
|
||||
id="position"
|
||||
value={formData.position || ''}
|
||||
onChange={(e) => onFormDataChange({ ...formData, position: e.target.value })}
|
||||
placeholder="职位名称"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 角色选择 */}
|
||||
<div>
|
||||
<Label>角色配置 *</Label>
|
||||
<Card className="mt-2 p-4 bg-gray-50">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{roles
|
||||
.filter(role => role.status === 'active')
|
||||
.map((role) => (
|
||||
<div key={role.id} className="flex items-start gap-2">
|
||||
<Checkbox
|
||||
id={`role-${role.id}`}
|
||||
checked={formData.roleIds?.includes(role.id) || false}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
roleIds: [...(formData.roleIds || []), role.id],
|
||||
});
|
||||
} else {
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
roleIds: (formData.roleIds || []).filter(id => id !== role.id),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<Label htmlFor={`role-${role.id}`} className="cursor-pointer text-foreground">
|
||||
{role.name}
|
||||
</Label>
|
||||
{role.description && (
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{role.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{roles.filter(r => r.status === 'active').length === 0 && (
|
||||
<p className="text-sm text-muted-foreground text-center py-2">
|
||||
暂无可用角色,请先在角色管理中创建角色
|
||||
</p>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={onSave}>保存</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Eye, Edit, Lock, Trash2, UserX, UserCheck } from 'lucide-react';
|
||||
import { Employee, UserStatus } from '../types';
|
||||
|
||||
interface EmployeeListProps {
|
||||
employees: Employee[];
|
||||
onViewDetail: (employee: Employee) => void;
|
||||
onEdit: (employee: Employee) => void;
|
||||
onResetPassword: (employee: Employee) => void;
|
||||
onToggleStatus: (employee: Employee) => void;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
export function EmployeeList({
|
||||
employees,
|
||||
onViewDetail,
|
||||
onEdit,
|
||||
onResetPassword,
|
||||
onToggleStatus,
|
||||
onDelete
|
||||
}: EmployeeListProps) {
|
||||
const getStatusBadge = (status: UserStatus) => {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return <Badge className="bg-green-100 text-green-700">正常</Badge>;
|
||||
case 'frozen':
|
||||
return <Badge className="bg-gray-100 text-gray-700">已冻结</Badge>;
|
||||
case 'inactive':
|
||||
return <Badge className="bg-red-100 text-red-700">停用</Badge>;
|
||||
default:
|
||||
return <Badge>{status}</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>姓名</TableHead>
|
||||
<TableHead>用户名</TableHead>
|
||||
<TableHead>电话</TableHead>
|
||||
<TableHead>部门</TableHead>
|
||||
<TableHead>职位</TableHead>
|
||||
<TableHead>角色</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{employees.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="text-center text-muted-foreground py-8">
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
employees.map((employee) => (
|
||||
<TableRow key={employee.id}>
|
||||
<TableCell>{employee.name}</TableCell>
|
||||
<TableCell className="text-muted-foreground">{employee.username}</TableCell>
|
||||
<TableCell>{employee.phone}</TableCell>
|
||||
<TableCell className="text-muted-foreground">{employee.department || '-'}</TableCell>
|
||||
<TableCell className="text-muted-foreground">{employee.position || '-'}</TableCell>
|
||||
<TableCell>
|
||||
{employee.roles && employee.roles.length > 0
|
||||
? employee.roles.join(', ')
|
||||
: '-'}
|
||||
</TableCell>
|
||||
<TableCell>{getStatusBadge(employee.status)}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onViewDetail(employee)}
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onEdit(employee)}
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onResetPassword(employee)}
|
||||
>
|
||||
<Lock className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onToggleStatus(employee)}
|
||||
>
|
||||
{employee.status === 'active' ? (
|
||||
<UserX className="w-4 h-4 text-orange-600" />
|
||||
) : (
|
||||
<UserCheck className="w-4 h-4 text-green-600" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onDelete(employee.id)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Search } from 'lucide-react';
|
||||
import { EmployeeFilters } from '../types';
|
||||
|
||||
interface EmployeeManagementFiltersProps {
|
||||
filters: EmployeeFilters;
|
||||
onFiltersChange: (filters: EmployeeFilters) => void;
|
||||
}
|
||||
|
||||
export function EmployeeManagementFilters({
|
||||
filters,
|
||||
onFiltersChange
|
||||
}: EmployeeManagementFiltersProps) {
|
||||
const updateFilter = (key: keyof EmployeeFilters, value: string) => {
|
||||
onFiltersChange({
|
||||
...filters,
|
||||
[key]: value
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-4">
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索员工姓名、用户名、电话、部门..."
|
||||
value={filters.searchKeyword}
|
||||
onChange={(e) => updateFilter('searchKeyword', e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Select value={filters.statusFilter} onValueChange={(value) => updateFilter('statusFilter', value)}>
|
||||
<SelectTrigger className="w-40">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="active">正常</SelectItem>
|
||||
<SelectItem value="frozen">已冻结</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
interface EmployeeManagementHeaderProps {
|
||||
onAddEmployee: () => void;
|
||||
}
|
||||
|
||||
export function EmployeeManagementHeader({ onAddEmployee }: EmployeeManagementHeaderProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-green-800">员工管理</h2>
|
||||
<p className="text-muted-foreground">管理本企业员工账户体系</p>
|
||||
</div>
|
||||
<Button onClick={onAddEmployee}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
添加员工
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { EmployeeManagementStats, Employee } from '../types';
|
||||
|
||||
interface EmployeeManagementStatsCardsProps {
|
||||
employees: Employee[];
|
||||
}
|
||||
|
||||
export function EmployeeManagementStatsCards({ employees }: EmployeeManagementStatsCardsProps) {
|
||||
const stats: EmployeeManagementStats[] = [
|
||||
{
|
||||
label: '总员工数',
|
||||
value: employees.length,
|
||||
color: 'text-blue-600',
|
||||
bg: 'bg-blue-100',
|
||||
},
|
||||
{
|
||||
label: '正常员工',
|
||||
value: employees.filter(e => e.status === 'active').length,
|
||||
color: 'text-green-600',
|
||||
bg: 'bg-green-100',
|
||||
},
|
||||
{
|
||||
label: '冻结员工',
|
||||
value: employees.filter(e => e.status === 'frozen').length,
|
||||
color: 'text-gray-600',
|
||||
bg: 'bg-gray-100',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{stats.map((stat, index) => (
|
||||
<Card key={index} className="p-4">
|
||||
<div className="text-sm text-muted-foreground">{stat.label}</div>
|
||||
<div className={`mt-2 ${stat.color} text-2xl font-semibold`}>{stat.value}</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
259
crop-x/src/app/(app)/central-config/user/employee/page.tsx
Normal file
259
crop-x/src/app/(app)/central-config/user/employee/page.tsx
Normal file
@@ -0,0 +1,259 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { EmployeeManagementHeader } from './components/EmployeeManagementHeader';
|
||||
import { EmployeeManagementStatsCards } from './components/EmployeeManagementStatsCards';
|
||||
import { EmployeeManagementFilters } from './components/EmployeeManagementFilters';
|
||||
import { EmployeeList } from './components/EmployeeList';
|
||||
import { EmployeeFormDialog } from './components/EmployeeFormDialog';
|
||||
import { EmployeeDetailDialog } from './components/EmployeeDetailDialog';
|
||||
import { Employee, Role, EmployeeFilters, EmployeeFormData } from './types';
|
||||
|
||||
export default function EmployeeManagementPage() {
|
||||
const [employees, setEmployees] = useState<Employee[]>([]);
|
||||
const [roles, setRoles] = useState<Role[]>([]);
|
||||
const [filters, setFilters] = useState<EmployeeFilters>({
|
||||
searchKeyword: '',
|
||||
statusFilter: 'all'
|
||||
});
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [showDetailDialog, setShowDetailDialog] = useState(false);
|
||||
const [editingEmployee, setEditingEmployee] = useState<Employee | null>(null);
|
||||
const [selectedEmployee, setSelectedEmployee] = useState<Employee | null>(null);
|
||||
const [formData, setFormData] = useState<EmployeeFormData>({
|
||||
enterpriseId: 'ent-2',
|
||||
enterpriseName: '丰收现代农业集团',
|
||||
status: 'active',
|
||||
roleIds: [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadEmployees();
|
||||
loadRoles();
|
||||
}, []);
|
||||
|
||||
const loadRoles = () => {
|
||||
const data = localStorage.getItem('smart_agriculture_roles');
|
||||
if (data) {
|
||||
setRoles(JSON.parse(data));
|
||||
}
|
||||
};
|
||||
|
||||
const loadEmployees = () => {
|
||||
const data = localStorage.getItem('smart_agriculture_employees');
|
||||
if (data) {
|
||||
setEmployees(JSON.parse(data));
|
||||
} else {
|
||||
// 初始化示例数据
|
||||
const mockEmployees: Employee[] = [
|
||||
{
|
||||
id: 'emp-1',
|
||||
enterpriseId: 'ent-2',
|
||||
enterpriseName: '丰收现代农业集团',
|
||||
username: 'zhangsan',
|
||||
name: '张三',
|
||||
phone: '13800138001',
|
||||
email: 'zhangsan@example.com',
|
||||
department: '技术部',
|
||||
position: '农机操作员',
|
||||
roleIds: ['role-3'],
|
||||
roles: ['操作员'],
|
||||
status: 'active',
|
||||
createdAt: '2024-10-01T08:00:00',
|
||||
updatedAt: '2024-10-01T08:00:00',
|
||||
lastLoginTime: '2024-10-14T09:30:00',
|
||||
},
|
||||
{
|
||||
id: 'emp-2',
|
||||
enterpriseId: 'ent-2',
|
||||
enterpriseName: '丰收现代农业集团',
|
||||
username: 'lisi',
|
||||
name: '李四',
|
||||
phone: '13900139002',
|
||||
email: 'lisi@example.com',
|
||||
department: '管理部',
|
||||
position: '部门主管',
|
||||
roleIds: ['role-2'],
|
||||
roles: ['企业管理员'],
|
||||
status: 'active',
|
||||
createdAt: '2024-10-02T10:00:00',
|
||||
updatedAt: '2024-10-02T10:00:00',
|
||||
lastLoginTime: '2024-10-14T08:15:00',
|
||||
},
|
||||
{
|
||||
id: 'emp-3',
|
||||
enterpriseId: 'ent-2',
|
||||
enterpriseName: '丰收现代农业集团',
|
||||
username: 'wangwu',
|
||||
name: '王五',
|
||||
phone: '13700137003',
|
||||
department: '维修部',
|
||||
position: '维修技师',
|
||||
roleIds: ['role-3'],
|
||||
roles: ['操作员'],
|
||||
status: 'frozen',
|
||||
createdAt: '2024-09-28T14:00:00',
|
||||
updatedAt: '2024-10-10T16:00:00',
|
||||
},
|
||||
];
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(mockEmployees));
|
||||
setEmployees(mockEmployees);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredEmployees = employees.filter(emp => {
|
||||
const matchKeyword = !filters.searchKeyword ||
|
||||
emp.name.includes(filters.searchKeyword) ||
|
||||
emp.username.includes(filters.searchKeyword) ||
|
||||
emp.phone.includes(filters.searchKeyword) ||
|
||||
(emp.department && emp.department.includes(filters.searchKeyword));
|
||||
|
||||
const matchStatus = filters.statusFilter === 'all' || emp.status === filters.statusFilter;
|
||||
|
||||
return matchKeyword && matchStatus;
|
||||
});
|
||||
|
||||
const handleAddEmployee = () => {
|
||||
setEditingEmployee(null);
|
||||
setFormData({
|
||||
enterpriseId: 'ent-2',
|
||||
enterpriseName: '丰收现代农业集团',
|
||||
status: 'active',
|
||||
roleIds: [],
|
||||
});
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
const handleEdit = (employee: Employee) => {
|
||||
setEditingEmployee(employee);
|
||||
setFormData(employee);
|
||||
setShowForm(true);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (!formData.username || !formData.name || !formData.phone) {
|
||||
toast.error('请填写必填项');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证角色选择
|
||||
if (!formData.roleIds || formData.roleIds.length === 0) {
|
||||
toast.error('请至少选择一个角色');
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据角色ID设置角色名称
|
||||
const selectedRoles = roles.filter(r => formData.roleIds?.includes(r.id));
|
||||
const roleNames = selectedRoles.map(r => r.name);
|
||||
|
||||
if (editingEmployee) {
|
||||
// 更新
|
||||
const updated = employees.map(emp =>
|
||||
emp.id === editingEmployee.id
|
||||
? {
|
||||
...emp,
|
||||
...formData,
|
||||
roles: roleNames,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
: emp
|
||||
);
|
||||
setEmployees(updated);
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
|
||||
toast.success('员工信息更新成功');
|
||||
} else {
|
||||
// 新增
|
||||
const newEmployee: Employee = {
|
||||
id: `emp-${Date.now()}`,
|
||||
...formData as Employee,
|
||||
roles: roleNames,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
const updated = [...employees, newEmployee];
|
||||
setEmployees(updated);
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
|
||||
toast.success('员工添加成功');
|
||||
}
|
||||
|
||||
setShowForm(false);
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
if (!confirm('确定要删除该员工吗?')) return;
|
||||
|
||||
const updated = employees.filter(emp => emp.id !== id);
|
||||
setEmployees(updated);
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
|
||||
toast.success('员工删除成功');
|
||||
};
|
||||
|
||||
const handleToggleStatus = (employee: Employee) => {
|
||||
const newStatus = employee.status === 'active' ? 'frozen' : 'active';
|
||||
const updated = employees.map(emp =>
|
||||
emp.id === employee.id
|
||||
? { ...emp, status: newStatus, updatedAt: new Date().toISOString() }
|
||||
: emp
|
||||
);
|
||||
setEmployees(updated);
|
||||
localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated));
|
||||
toast.success(newStatus === 'active' ? '账户已激活' : '账户已冻结');
|
||||
};
|
||||
|
||||
const handleResetPassword = (employee: Employee) => {
|
||||
if (!confirm(`确定要重置 ${employee.name} 的密码吗?`)) return;
|
||||
toast.success('密码已重置为:123456');
|
||||
};
|
||||
|
||||
const handleViewDetail = (employee: Employee) => {
|
||||
setSelectedEmployee(employee);
|
||||
setShowDetailDialog(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<EmployeeManagementHeader
|
||||
onAddEmployee={handleAddEmployee}
|
||||
/>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<EmployeeManagementStatsCards employees={employees} />
|
||||
|
||||
{/* 搜索和筛选 */}
|
||||
<EmployeeManagementFilters
|
||||
filters={filters}
|
||||
onFiltersChange={setFilters}
|
||||
/>
|
||||
|
||||
{/* 员工列表 */}
|
||||
<EmployeeList
|
||||
employees={filteredEmployees}
|
||||
onViewDetail={handleViewDetail}
|
||||
onEdit={handleEdit}
|
||||
onResetPassword={handleResetPassword}
|
||||
onToggleStatus={handleToggleStatus}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
|
||||
{/* 添加/编辑表单 */}
|
||||
<EmployeeFormDialog
|
||||
open={showForm}
|
||||
onOpenChange={setShowForm}
|
||||
editingEmployee={editingEmployee}
|
||||
formData={formData}
|
||||
onFormDataChange={setFormData}
|
||||
onSave={handleSave}
|
||||
roles={roles}
|
||||
/>
|
||||
|
||||
{/* 详情对话框 */}
|
||||
<EmployeeDetailDialog
|
||||
open={showDetailDialog}
|
||||
onOpenChange={setShowDetailDialog}
|
||||
selectedEmployee={selectedEmployee}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
crop-x/src/app/(app)/central-config/user/employee/types.ts
Normal file
65
crop-x/src/app/(app)/central-config/user/employee/types.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
// 员工管理相关类型定义
|
||||
|
||||
export interface Employee {
|
||||
id: string;
|
||||
enterpriseId: string;
|
||||
enterpriseName: string;
|
||||
username: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
email?: string;
|
||||
department?: string;
|
||||
position?: string;
|
||||
roleIds: string[];
|
||||
roles?: string[];
|
||||
status: UserStatus;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastLoginTime?: string;
|
||||
}
|
||||
|
||||
export type UserStatus = 'active' | 'inactive' | 'frozen';
|
||||
|
||||
export interface Role {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
description?: string;
|
||||
type: RoleType;
|
||||
menuIds: string[];
|
||||
permissionIds: string[];
|
||||
defaultHomePage?: string;
|
||||
status: 'active' | 'inactive';
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export type RoleType = 'system' | 'custom';
|
||||
|
||||
// 统计数据
|
||||
export interface EmployeeManagementStats {
|
||||
label: string;
|
||||
value: number;
|
||||
color: string;
|
||||
bg: string;
|
||||
}
|
||||
|
||||
// 筛选条件
|
||||
export interface EmployeeFilters {
|
||||
searchKeyword: string;
|
||||
statusFilter: string;
|
||||
}
|
||||
|
||||
// 表单数据
|
||||
export interface EmployeeFormData {
|
||||
username?: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
department?: string;
|
||||
position?: string;
|
||||
enterpriseId?: string;
|
||||
enterpriseName?: string;
|
||||
status?: UserStatus;
|
||||
roleIds?: string[];
|
||||
}
|
||||
Reference in New Issue
Block a user