|
@@ -1,11 +1,805 @@
|
|
|
-import { Component } from '@angular/core';
|
|
|
+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 { 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: `
|
|
|
+ <h2 mat-dialog-title>{{data.isEdit ? '编辑员工信息' : '新增员工'}}</h2>
|
|
|
+ <mat-dialog-content>
|
|
|
+ <form [formGroup]="employeeForm" class="employee-form">
|
|
|
+ <div class="form-row">
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>姓名</mat-label>
|
|
|
+ <input matInput formControlName="name" required>
|
|
|
+ <mat-error *ngIf="employeeForm.get('name')?.hasError('required')">姓名不能为空</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>工号</mat-label>
|
|
|
+ <input matInput formControlName="employeeId" required>
|
|
|
+ <mat-error *ngIf="employeeForm.get('employeeId')?.hasError('required')">工号不能为空</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-row">
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>部门</mat-label>
|
|
|
+ <mat-select formControlName="department" required>
|
|
|
+ <mat-option *ngFor="let dept of departments" [value]="dept.name">{{dept.name}}</mat-option>
|
|
|
+ </mat-select>
|
|
|
+ <mat-error *ngIf="employeeForm.get('department')?.hasError('required')">请选择部门</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>职位</mat-label>
|
|
|
+ <mat-select formControlName="position" required>
|
|
|
+ <mat-option *ngFor="let pos of positions" [value]="pos.name">{{pos.name}}</mat-option>
|
|
|
+ </mat-select>
|
|
|
+ <mat-error *ngIf="employeeForm.get('position')?.hasError('required')">请选择职位</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-row">
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>手机号码</mat-label>
|
|
|
+ <input matInput formControlName="phone" required>
|
|
|
+ <mat-error *ngIf="employeeForm.get('phone')?.hasError('required')">手机号码不能为空</mat-error>
|
|
|
+ <mat-error *ngIf="employeeForm.get('phone')?.hasError('pattern')">请输入有效的手机号码</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>邮箱</mat-label>
|
|
|
+ <input matInput formControlName="email" required>
|
|
|
+ <mat-error *ngIf="employeeForm.get('email')?.hasError('required')">邮箱不能为空</mat-error>
|
|
|
+ <mat-error *ngIf="employeeForm.get('email')?.hasError('email')">请输入有效的邮箱地址</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-row">
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>性别</mat-label>
|
|
|
+ <mat-select formControlName="gender" required>
|
|
|
+ <mat-option value="男">男</mat-option>
|
|
|
+ <mat-option value="女">女</mat-option>
|
|
|
+ </mat-select>
|
|
|
+ <mat-error *ngIf="employeeForm.get('gender')?.hasError('required')">请选择性别</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>出生日期</mat-label>
|
|
|
+ <input matInput [matDatepicker]="birthDatePicker" formControlName="birthDate">
|
|
|
+ <mat-datepicker-toggle matSuffix [for]="birthDatePicker"></mat-datepicker-toggle>
|
|
|
+ <mat-datepicker #birthDatePicker></mat-datepicker>
|
|
|
+ </mat-form-field>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-row">
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>入职日期</mat-label>
|
|
|
+ <input matInput [matDatepicker]="hireDatePicker" formControlName="hireDate" required>
|
|
|
+ <mat-datepicker-toggle matSuffix [for]="hireDatePicker"></mat-datepicker-toggle>
|
|
|
+ <mat-datepicker #hireDatePicker></mat-datepicker>
|
|
|
+ <mat-error *ngIf="employeeForm.get('hireDate')?.hasError('required')">入职日期不能为空</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+
|
|
|
+ <mat-form-field appearance="outline">
|
|
|
+ <mat-label>员工状态</mat-label>
|
|
|
+ <mat-select formControlName="status" required>
|
|
|
+ <mat-option value="在职">在职</mat-option>
|
|
|
+ <mat-option value="离职">离职</mat-option>
|
|
|
+ <mat-option value="试用期">试用期</mat-option>
|
|
|
+ </mat-select>
|
|
|
+ <mat-error *ngIf="employeeForm.get('status')?.hasError('required')">请选择员工状态</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </mat-dialog-content>
|
|
|
+ <mat-dialog-actions align="end">
|
|
|
+ <button mat-button mat-dialog-close>取消</button>
|
|
|
+ <button mat-raised-button color="primary" [disabled]="employeeForm.invalid" (click)="saveEmployee()">保存</button>
|
|
|
+ </mat-dialog-actions>
|
|
|
+ `,
|
|
|
+ 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<AddEmployeeDialog>,
|
|
|
+ @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: `
|
|
|
+ <h2 mat-dialog-title>入职流程</h2>
|
|
|
+ <mat-dialog-content>
|
|
|
+ <div class="employee-info">
|
|
|
+ <h3>员工信息</h3>
|
|
|
+ <p><strong>姓名:</strong> {{data.employee.name}}</p>
|
|
|
+ <p><strong>部门:</strong> {{data.employee.department}}</p>
|
|
|
+ <p><strong>职位:</strong> {{data.employee.position}}</p>
|
|
|
+ <p><strong>入职日期:</strong> {{data.employee.hireDate | date:'yyyy-MM-dd'}}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="onboarding-tasks">
|
|
|
+ <h3>入职任务清单</h3>
|
|
|
+ <div class="task-list">
|
|
|
+ <div *ngFor="let task of onboardingTasks" class="task-item">
|
|
|
+ <mat-checkbox [(ngModel)]="task.completed">{{task.name}}</mat-checkbox>
|
|
|
+ <span class="task-assignee">负责人: {{task.assignee}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </mat-dialog-content>
|
|
|
+ <mat-dialog-actions align="end">
|
|
|
+ <button mat-button mat-dialog-close>关闭</button>
|
|
|
+ <button mat-raised-button color="primary" (click)="saveOnboarding()">保存进度</button>
|
|
|
+ </mat-dialog-actions>
|
|
|
+ `,
|
|
|
+ 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<OnboardingDialog>,
|
|
|
+ @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: `
|
|
|
+ <h2 mat-dialog-title>离职流程</h2>
|
|
|
+ <mat-dialog-content>
|
|
|
+ <form [formGroup]="offboardingForm" class="offboarding-form">
|
|
|
+ <div class="employee-info">
|
|
|
+ <h3>员工信息</h3>
|
|
|
+ <p><strong>姓名:</strong> {{data.employee.name}}</p>
|
|
|
+ <p><strong>部门:</strong> {{data.employee.department}}</p>
|
|
|
+ <p><strong>职位:</strong> {{data.employee.position}}</p>
|
|
|
+ <p><strong>入职日期:</strong> {{data.employee.hireDate | date:'yyyy-MM-dd'}}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="offboarding-details">
|
|
|
+ <h3>离职信息</h3>
|
|
|
+
|
|
|
+ <mat-form-field appearance="outline" class="full-width">
|
|
|
+ <mat-label>离职原因</mat-label>
|
|
|
+ <mat-select formControlName="reason" required>
|
|
|
+ <mat-option value="个人发展">个人发展</mat-option>
|
|
|
+ <mat-option value="薪资问题">薪资问题</mat-option>
|
|
|
+ <mat-option value="工作环境">工作环境</mat-option>
|
|
|
+ <mat-option value="家庭原因">家庭原因</mat-option>
|
|
|
+ <mat-option value="健康原因">健康原因</mat-option>
|
|
|
+ <mat-option value="其他">其他</mat-option>
|
|
|
+ </mat-select>
|
|
|
+ <mat-error *ngIf="offboardingForm.get('reason')?.hasError('required')">请选择离职原因</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+
|
|
|
+ <mat-form-field appearance="outline" class="full-width">
|
|
|
+ <mat-label>离职日期</mat-label>
|
|
|
+ <input matInput [matDatepicker]="resignDatePicker" formControlName="resignDate" required>
|
|
|
+ <mat-datepicker-toggle matSuffix [for]="resignDatePicker"></mat-datepicker-toggle>
|
|
|
+ <mat-datepicker #resignDatePicker></mat-datepicker>
|
|
|
+ <mat-error *ngIf="offboardingForm.get('resignDate')?.hasError('required')">离职日期不能为空</mat-error>
|
|
|
+ </mat-form-field>
|
|
|
+
|
|
|
+ <mat-form-field appearance="outline" class="full-width">
|
|
|
+ <mat-label>工作交接要求</mat-label>
|
|
|
+ <textarea matInput formControlName="handoverRequirements" rows="3"></textarea>
|
|
|
+ </mat-form-field>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="offboarding-tasks">
|
|
|
+ <h3>离职任务清单</h3>
|
|
|
+ <div class="task-list">
|
|
|
+ <div *ngFor="let task of offboardingTasks" class="task-item">
|
|
|
+ <mat-checkbox [(ngModel)]="task.completed" [ngModelOptions]="{standalone: true}">
|
|
|
+ {{task.name}}
|
|
|
+ </mat-checkbox>
|
|
|
+ <span class="task-assignee">负责人: {{task.assignee}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </mat-dialog-content>
|
|
|
+ <mat-dialog-actions align="end">
|
|
|
+ <button mat-button mat-dialog-close>取消</button>
|
|
|
+ <button mat-raised-button color="primary" [disabled]="offboardingForm.invalid" (click)="saveOffboarding()">提交</button>
|
|
|
+ </mat-dialog-actions>
|
|
|
+ `,
|
|
|
+ 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<OffboardingDialog>,
|
|
|
+ @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',
|
|
|
- imports: [],
|
|
|
+ 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',
|
|
|
- styleUrl: './employee-records.scss'
|
|
|
+ styleUrls: ['./employee-records.scss']
|
|
|
})
|
|
|
-export class EmployeeRecords {
|
|
|
+export class EmployeeRecords implements OnInit {
|
|
|
+ // 员工数据
|
|
|
+ employees = signal<Employee[]>([
|
|
|
+ {
|
|
|
+ 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: '在职'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 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: '在职'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 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: '试用期'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 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: '在职'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 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: '在职'
|
|
|
+ }
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 筛选条件
|
|
|
+ filterName = signal('');
|
|
|
+ filterDepartment = signal('');
|
|
|
+ filterPosition = signal('');
|
|
|
+ filterStatus = signal('');
|
|
|
+
|
|
|
+ // 表格列定义
|
|
|
+ displayedColumns = ['select', 'name', 'employeeId', 'department', 'position', 'phone', 'hireDate', 'status', 'actions'];
|
|
|
+
|
|
|
+ // 选中的员工
|
|
|
+ selectedEmployees = signal<string[]>([]);
|
|
|
+
|
|
|
+ // 部门和职位数据
|
|
|
+ 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
|
|
|
+ ) {}
|
|
|
+
|
|
|
+ ngOnInit() {}
|
|
|
+
|
|
|
+ // 打开新增员工对话框
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择/取消选择单个员工
|
|
|
+ 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('');
|
|
|
+ }
|
|
|
}
|