import { Component, OnInit, signal, computed, Inject } from '@angular/core';
import { CommonModule, NgIf, NgFor } from '@angular/common';
import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { MatDialogModule, MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatTabsModule } from '@angular/material/tabs';
import { MatChipsModule } from '@angular/material/chips';
import { MatMenuModule } from '@angular/material/menu';
import { MatCardModule } from '@angular/material/card';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { Router } from '@angular/router';
import { Employee, Department, Position, Contract, Certificate, EmployeeStatus } from '../../../models/hr.model';
// 新增员工对话框组件
@Component({
selector: 'app-add-employee-dialog',
standalone: true,
imports: [
CommonModule,
NgFor,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatInputModule,
MatSelectModule,
MatDatepickerModule,
MatNativeDateModule,
MatDialogModule,
MatIconModule
],
template: `
{{data.isEdit ? '编辑员工信息' : '新增员工'}}
`,
styles: [`
.employee-form {
display: flex;
flex-direction: column;
gap: 16px;
padding: 8px;
}
.form-row {
display: flex;
gap: 16px;
mat-form-field {
flex: 1;
}
}
`]
})
export class AddEmployeeDialog implements OnInit {
employeeForm: FormGroup;
departments: Department[] = [
{ id: '1', name: '设计部', employeeCount: 0 },
{ id: '2', name: '客服部', employeeCount: 0 },
{ id: '3', name: '技术部', employeeCount: 0 },
{ id: '4', name: '行政部', employeeCount: 0 }
];
positions: Position[] = [
{ id: '1', name: '设计师', departmentId: '1', departmentName: '设计部', level: '中级' },
{ id: '2', name: '客服专员', departmentId: '2', departmentName: '客服部', level: '初级' },
{ id: '3', name: '技术组长', departmentId: '3', departmentName: '技术部', level: '高级' },
{ id: '4', name: '前端开发', departmentId: '3', departmentName: '技术部', level: '中级' },
{ id: '5', name: '行政专员', departmentId: '4', departmentName: '行政部', level: '初级' }
];
constructor(
private fb: FormBuilder,
public dialogRef: MatDialogRef,
@Inject(MAT_DIALOG_DATA) public data: {employee?: Employee, isEdit: boolean}
) {
this.employeeForm = this.fb.group({
name: ['', Validators.required],
employeeId: ['', Validators.required],
department: ['', Validators.required],
position: ['', Validators.required],
phone: ['', [Validators.required, Validators.pattern(/^1[3-9]\d{9}$/)]],
email: ['', [Validators.required, Validators.email]],
gender: ['', Validators.required],
birthDate: [null],
hireDate: [new Date(), Validators.required],
status: ['试用期', Validators.required]
});
if (data.isEdit && data.employee) {
this.employeeForm.patchValue(data.employee);
}
}
ngOnInit() {}
saveEmployee() {
if (this.employeeForm.valid) {
const employeeData = this.employeeForm.value;
this.dialogRef.close(employeeData);
}
}
}
// 入职流程对话框组件
@Component({
selector: 'app-onboarding-dialog',
standalone: true,
imports: [
CommonModule,
NgFor,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatInputModule,
MatSelectModule,
MatDatepickerModule,
MatNativeDateModule,
MatDialogModule,
MatIconModule,
MatCheckboxModule
],
template: `
入职流程
员工信息
姓名: {{data.employee.name}}
部门: {{data.employee.department}}
职位: {{data.employee.position}}
入职日期: {{data.employee.hireDate | date:'yyyy-MM-dd'}}
入职任务清单
{{task.name}}
负责人: {{task.assignee}}
`,
styles: [`
.employee-info {
margin-bottom: 24px;
padding: 16px;
background-color: #f5f5f5;
border-radius: 4px;
h3 {
margin-top: 0;
margin-bottom: 16px;
color: #1a3a6e;
}
p {
margin: 8px 0;
}
}
.onboarding-tasks {
h3 {
margin-bottom: 16px;
color: #1a3a6e;
}
}
.task-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #e0e0e0;
&:last-child {
border-bottom: none;
}
.task-assignee {
color: #757575;
font-size: 12px;
}
}
`]
})
export class OnboardingDialog {
onboardingTasks = [
{ name: '签署劳动合同', completed: false, assignee: '人事' },
{ name: '录入系统权限', completed: false, assignee: '技术支持' },
{ name: '领取办公用品', completed: false, assignee: '行政' },
{ name: '参加新员工培训', completed: false, assignee: '培训部' },
{ name: '技术组长审核岗位匹配度', completed: false, assignee: '技术组长' }
];
constructor(
public dialogRef: MatDialogRef,
@Inject(MAT_DIALOG_DATA) public data: {employee: Employee}
) {}
saveOnboarding() {
this.dialogRef.close(this.onboardingTasks);
}
}
// 离职流程对话框组件
@Component({
selector: 'app-offboarding-dialog',
standalone: true,
imports: [
CommonModule,
NgFor,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatInputModule,
MatSelectModule,
MatDatepickerModule,
MatNativeDateModule,
MatDialogModule,
MatIconModule,
MatCheckboxModule
],
template: `
离职流程
`,
styles: [`
.offboarding-form {
display: flex;
flex-direction: column;
gap: 24px;
}
.employee-info {
padding: 16px;
background-color: #f5f5f5;
border-radius: 4px;
h3 {
margin-top: 0;
margin-bottom: 16px;
color: #1a3a6e;
}
p {
margin: 8px 0;
}
}
.offboarding-details,
.offboarding-tasks {
h3 {
margin-bottom: 16px;
color: #1a3a6e;
}
}
.full-width {
width: 100%;
}
.task-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #e0e0e0;
&:last-child {
border-bottom: none;
}
.task-assignee {
color: #757575;
font-size: 12px;
}
}
`]
})
export class OffboardingDialog implements OnInit {
offboardingForm: FormGroup;
offboardingTasks = [
{ name: '项目交接', completed: false, assignee: '技术组长' },
{ name: '客户信息同步', completed: false, assignee: '客服主管' },
{ name: '薪资结算', completed: false, assignee: '财务' },
{ name: '归还办公设备', completed: false, assignee: '行政' },
{ name: '系统权限冻结', completed: false, assignee: '技术支持' }
];
constructor(
private fb: FormBuilder,
public dialogRef: MatDialogRef,
@Inject(MAT_DIALOG_DATA) public data: {employee: Employee}
) {
this.offboardingForm = this.fb.group({
reason: ['', Validators.required],
resignDate: [new Date(), Validators.required],
handoverRequirements: ['']
});
}
ngOnInit() {}
saveOffboarding() {
if (this.offboardingForm.valid) {
const offboardingData = {
...this.offboardingForm.value,
tasks: this.offboardingTasks
};
this.dialogRef.close(offboardingData);
}
}
}
@Component({
selector: 'app-employee-records',
standalone: true,
imports: [
CommonModule,
NgIf,
NgFor,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatInputModule,
MatSelectModule,
MatDatepickerModule,
MatNativeDateModule,
MatDialogModule,
MatTableModule,
MatPaginatorModule,
MatCheckboxModule,
MatSnackBarModule,
MatIconModule,
MatTooltipModule,
MatTabsModule,
MatChipsModule,
MatMenuModule,
MatCardModule,
MatProgressSpinnerModule
],
templateUrl: './employee-records.html',
styleUrls: ['./employee-records.scss']
})
export class EmployeeRecords implements OnInit {
// 员工数据
employees = signal([
{
id: '1',
name: '张三',
department: '设计部',
position: '设计师',
employeeId: 'DS001',
phone: '13800138001',
email: 'zhangsan@example.com',
gender: '男',
birthDate: new Date('1990-01-01'),
hireDate: new Date('2022-01-15'),
status: '在职',
idCard: '110105199001011234',
bankCard: '6222021234567890'
},
{
id: '2',
name: '李四',
department: '客服部',
position: '客服专员',
employeeId: 'CS001',
phone: '13800138002',
email: 'lisi@example.com',
gender: '女',
birthDate: new Date('1992-05-20'),
hireDate: new Date('2022-03-10'),
status: '在职',
idCard: '110105199205201256',
bankCard: '6216619876543210'
},
{
id: '3',
name: '王五',
department: '技术部',
position: '前端开发',
employeeId: 'FE001',
phone: '13800138003',
email: 'wangwu@example.com',
gender: '男',
birthDate: new Date('1988-11-15'),
hireDate: new Date('2023-01-05'),
status: '试用期',
idCard: '110105198811151278',
bankCard: '6225880011223344'
},
{
id: '4',
name: '赵六',
department: '设计部',
position: '高级设计师',
employeeId: 'DS002',
phone: '13800138004',
email: 'zhaoliu@example.com',
gender: '男',
birthDate: new Date('1985-07-22'),
hireDate: new Date('2021-06-18'),
status: '在职',
idCard: '110105198507221299',
bankCard: '6217009988776655'
},
{
id: '5',
name: '钱七',
department: '技术部',
position: '技术组长',
employeeId: 'TL001',
phone: '13800138005',
email: 'qianqi@example.com',
gender: '男',
birthDate: new Date('1983-03-30'),
hireDate: new Date('2020-09-01'),
status: '在职',
idCard: '110105198303301233',
bankCard: '6222035566778899'
}
]);
// 筛选条件
filterName = signal('');
filterDepartment = signal('');
filterPosition = signal('');
filterStatus = signal('');
// 表格列定义
displayedColumns = ['select', 'name', 'employeeId', 'department', 'position', 'phone', 'idCard', 'bankCard', 'hireDate', 'status', 'actions'];
// 选中的员工
selectedEmployees = signal([]);
// 展示敏感信息的行(按员工id)
sensitiveExpandedIds = signal([]);
// 是否有任意行展开敏感信息
isAnySensitiveExpanded = computed(() => this.sensitiveExpandedIds().length > 0);
// 部门和职位数据
departments = [
{ id: '1', name: '设计部', employeeCount: 25 },
{ id: '2', name: '客服部', employeeCount: 18 },
{ id: '3', name: '技术部', employeeCount: 30 },
{ id: '4', name: '行政部', employeeCount: 10 }
];
positions = [
{ id: '1', name: '设计师', departmentId: '1', departmentName: '设计部', level: '中级' },
{ id: '2', name: '高级设计师', departmentId: '1', departmentName: '设计部', level: '高级' },
{ id: '3', name: '客服专员', departmentId: '2', departmentName: '客服部', level: '初级' },
{ id: '4', name: '客服主管', departmentId: '2', departmentName: '客服部', level: '高级' },
{ id: '5', name: '前端开发', departmentId: '3', departmentName: '技术部', level: '中级' },
{ id: '6', name: '技术组长', departmentId: '3', departmentName: '技术部', level: '高级' },
{ id: '7', name: '行政专员', departmentId: '4', departmentName: '行政部', level: '初级' }
];
// 计算过滤后的员工列表
filteredEmployees = computed(() => {
return this.employees().filter(employee => {
const nameMatch = this.filterName() === '' || employee.name.includes(this.filterName());
const departmentMatch = this.filterDepartment() === '' || employee.department === this.filterDepartment();
const positionMatch = this.filterPosition() === '' || employee.position === this.filterPosition();
const statusMatch = this.filterStatus() === '' || employee.status === this.filterStatus();
return nameMatch && departmentMatch && positionMatch && statusMatch;
});
});
constructor(
private dialog: MatDialog,
private snackBar: MatSnackBar,
private router: Router
) {}
ngOnInit() {}
// 掩码与格式化工具
maskIdCard(id: string): string {
if (!id) return '';
if (id.length >= 18) return `${id.slice(0, 6)}********${id.slice(-4)}`;
if (id.length > 6) return `${id.slice(0, 3)}****${id.slice(-2)}`;
return id;
}
maskBankCard(card: string): string {
if (!card) return '';
const compact = card.replace(/\s+/g, '');
if (compact.length <= 8) return compact;
const first4 = compact.slice(0, 4);
const last4 = compact.slice(-4);
return `${first4} **** **** ${last4}`;
}
formatBankCard(card: string): string {
if (!card) return '';
return card.replace(/\s+/g, '').replace(/(\d{4})(?=\d)/g, '$1 ').trim();
}
isSensitiveExpanded(id: string): boolean {
return this.sensitiveExpandedIds().includes(id);
}
toggleSensitive(id: string) {
const list = this.sensitiveExpandedIds();
if (list.includes(id)) {
this.sensitiveExpandedIds.set(list.filter(x => x !== id));
} else {
this.sensitiveExpandedIds.set([...list, id]);
}
}
// 打开新增员工对话框
openAddEmployeeDialog() {
const dialogRef = this.dialog.open(AddEmployeeDialog, {
width: '700px',
panelClass: 'hr-dialog',
data: { isEdit: false }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
const newEmployee: Employee = {
id: (this.employees().length + 1).toString(),
...result
};
this.employees.update(employees => [...employees, newEmployee]);
this.showSnackBar('员工添加成功');
}
});
}
// 打开编辑员工对话框
openEditEmployeeDialog(employee: Employee) {
const dialogRef = this.dialog.open(AddEmployeeDialog, {
width: '700px',
panelClass: 'hr-dialog',
data: { employee, isEdit: true }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.employees.update(employees =>
employees.map(emp => emp.id === employee.id ? { ...emp, ...result } : emp)
);
this.showSnackBar('员工信息更新成功');
}
});
}
// 打开入职流程对话框
openOnboardingDialog(employee: Employee) {
const dialogRef = this.dialog.open(OnboardingDialog, {
width: '600px',
panelClass: 'hr-dialog',
data: { employee }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.showSnackBar('入职流程更新成功');
}
});
}
// 打开离职流程对话框
openOffboardingDialog(employee: Employee) {
const dialogRef = this.dialog.open(OffboardingDialog, {
width: '600px',
panelClass: 'hr-dialog',
data: { employee }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// 更新员工状态为离职
this.employees.update(employees =>
employees.map(emp => emp.id === employee.id ? { ...emp, status: '离职' } : emp)
);
this.showSnackBar('离职流程已启动');
}
});
}
// 删除员工
deleteEmployee(employee: Employee) {
if (confirm(`确定要删除员工 ${employee.name} 吗?`)) {
this.employees.update(employees =>
employees.filter(emp => emp.id !== employee.id)
);
this.showSnackBar('员工已删除');
}
}
// 批量删除员工
batchDelete() {
if (this.selectedEmployees().length === 0) {
this.showSnackBar('请先选择要删除的员工');
return;
}
if (confirm(`确定要删除选中的 ${this.selectedEmployees().length} 名员工吗?`)) {
this.employees.update(employees =>
employees.filter(emp => !this.selectedEmployees().includes(emp.id))
);
this.selectedEmployees.set([]);
this.showSnackBar('批量删除成功');
}
}
// 导出员工数据
exportEmployees(type: string) {
// 实际项目中这里会调用导出服务
this.showSnackBar(`已导出${type}`);
}
// 快捷卡片查看详情
openQuickAction(type: 'onboarding' | 'offboarding' | 'probation') {
switch (type) {
case 'onboarding':
// 这里可以导航或弹窗,先给出提示
this.showSnackBar('前往入职流程列表');
break;
case 'offboarding':
this.showSnackBar('前往离职流程列表');
break;
case 'probation':
this.showSnackBar('查看试用期即将到期员工');
break;
}
}
// 进入员工详情(根据角色跳转至相应详情页)
goToDetails(employee: Employee) {
// 目前仅实现设计师角色详情,后续可按职位扩展其它角色
this.showSnackBar('该角色详情页正在建设中');
}
// 选择/取消选择单个员工
toggleSelection(employeeId: string) {
if (this.isSelected(employeeId)) {
this.selectedEmployees.update(selected => selected.filter(id => id !== employeeId));
} else {
this.selectedEmployees.update(selected => [...selected, employeeId]);
}
}
// 选择/取消选择所有员工
toggleSelectAll(checked: boolean) {
if (checked) {
const allIds = this.filteredEmployees().map(emp => emp.id);
this.selectedEmployees.set(allIds);
} else {
this.selectedEmployees.set([]);
}
}
// 检查员工是否被选中
isSelected(employeeId: string): boolean {
return this.selectedEmployees().includes(employeeId);
}
// 检查是否所有员工都被选中
isAllSelected(): boolean {
return this.filteredEmployees().length > 0 &&
this.filteredEmployees().every(emp => this.selectedEmployees().includes(emp.id));
}
// 显示提示消息
showSnackBar(message: string) {
this.snackBar.open(message, '关闭', {
duration: 3000,
horizontalPosition: 'center',
verticalPosition: 'bottom',
panelClass: ['success-snackbar']
});
}
// 重置筛选条件
resetFilters() {
this.filterName.set('');
this.filterDepartment.set('');
this.filterPosition.set('');
this.filterStatus.set('');
}
}