|
@@ -0,0 +1,320 @@
|
|
|
+import { Component, OnInit, signal, computed, inject } from '@angular/core';
|
|
|
+import { CommonModule, Location } 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 { MatDialogModule, MatDialog } from '@angular/material/dialog';
|
|
|
+import { MatCardModule } from '@angular/material/card';
|
|
|
+import { MatIconModule } from '@angular/material/icon';
|
|
|
+import { MatChipsModule } from '@angular/material/chips';
|
|
|
+import { MatTooltipModule } from '@angular/material/tooltip';
|
|
|
+import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
|
+import { MatDividerModule } from '@angular/material/divider';
|
|
|
+import { MatMenuModule } from '@angular/material/menu';
|
|
|
+import { MatSnackBar } from '@angular/material/snack-bar';
|
|
|
+import { Router, ActivatedRoute } from '@angular/router';
|
|
|
+import { Employee } from '../../../models/hr.model';
|
|
|
+
|
|
|
+@Component({
|
|
|
+ selector: 'app-employee-detail',
|
|
|
+ standalone: true,
|
|
|
+ imports: [
|
|
|
+ CommonModule,
|
|
|
+ FormsModule,
|
|
|
+ ReactiveFormsModule,
|
|
|
+ MatButtonModule,
|
|
|
+ MatInputModule,
|
|
|
+ MatSelectModule,
|
|
|
+ MatDialogModule,
|
|
|
+ MatCardModule,
|
|
|
+ MatIconModule,
|
|
|
+ MatChipsModule,
|
|
|
+ MatTooltipModule,
|
|
|
+ MatButtonToggleModule,
|
|
|
+ MatDividerModule,
|
|
|
+ MatMenuModule
|
|
|
+ ],
|
|
|
+ templateUrl: './employee-detail.html',
|
|
|
+ styleUrls: ['./employee-detail.scss']
|
|
|
+})
|
|
|
+export class EmployeeDetailComponent implements OnInit {
|
|
|
+ private route = inject(ActivatedRoute);
|
|
|
+ private router = inject(Router);
|
|
|
+ private location = inject(Location);
|
|
|
+ private dialog = inject(MatDialog);
|
|
|
+ private snackBar = inject(MatSnackBar);
|
|
|
+ private fb = inject(FormBuilder);
|
|
|
+
|
|
|
+ employee = signal<Employee | null>(null);
|
|
|
+ screeningForm: FormGroup;
|
|
|
+
|
|
|
+ // 模拟员工数据
|
|
|
+ private mockEmployees: Employee[] = [
|
|
|
+ {
|
|
|
+ id: '1',
|
|
|
+ name: '张三',
|
|
|
+ department: '设计部',
|
|
|
+ position: '设计师',
|
|
|
+ employeeId: 'DS001',
|
|
|
+ phone: '13800138001',
|
|
|
+ email: 'zhangsan@example.com',
|
|
|
+ gender: '男',
|
|
|
+ birthDate: new Date('1990-05-15'),
|
|
|
+ hireDate: new Date('2022-01-15'),
|
|
|
+ status: '在职',
|
|
|
+ avatar: 'assets/images/avatar1.jpg',
|
|
|
+ education: '本科',
|
|
|
+ workYears: '3年',
|
|
|
+ idCard: '110101199005151234',
|
|
|
+ bankCard: '6222021234567890123',
|
|
|
+ address: '北京市朝阳区xxx街道xxx号',
|
|
|
+ supervisor: '李经理',
|
|
|
+ screeningStatus: 'approved',
|
|
|
+ screeningComment: '技能匹配度高,工作经验丰富',
|
|
|
+ screeningTime: new Date('2022-01-10')
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: '2',
|
|
|
+ name: '李四',
|
|
|
+ department: '客服部',
|
|
|
+ position: '客服专员',
|
|
|
+ employeeId: 'CS001',
|
|
|
+ phone: '13800138002',
|
|
|
+ email: 'lisi@example.com',
|
|
|
+ gender: '女',
|
|
|
+ birthDate: new Date('1992-08-20'),
|
|
|
+ hireDate: new Date('2022-03-10'),
|
|
|
+ status: '在职',
|
|
|
+ education: '大专',
|
|
|
+ workYears: '2年',
|
|
|
+ idCard: '110101199208201234',
|
|
|
+ bankCard: '6222021234567890124',
|
|
|
+ address: '北京市海淀区xxx街道xxx号',
|
|
|
+ supervisor: '王经理',
|
|
|
+ screeningStatus: 'pending',
|
|
|
+ screeningTime: undefined
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 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: '试用期',
|
|
|
+ education: '硕士',
|
|
|
+ workYears: '5年',
|
|
|
+ idCard: '110101198811151234',
|
|
|
+ bankCard: '6222021234567890125',
|
|
|
+ address: '北京市西城区xxx街道xxx号',
|
|
|
+ supervisor: '赵总监',
|
|
|
+ screeningStatus: 'rejected',
|
|
|
+ screeningComment: '技术能力需要进一步评估',
|
|
|
+ screeningTime: new Date('2023-01-02')
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ constructor() {
|
|
|
+ this.screeningForm = this.fb.group({
|
|
|
+ status: ['', Validators.required],
|
|
|
+ comment: ['']
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ ngOnInit() {
|
|
|
+ const employeeId = this.route.snapshot.paramMap.get('id');
|
|
|
+ if (employeeId) {
|
|
|
+ this.loadEmployee(employeeId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private loadEmployee(id: string) {
|
|
|
+ // 模拟从服务加载员工数据
|
|
|
+ const emp = this.mockEmployees.find(e => e.id === id);
|
|
|
+ if (emp) {
|
|
|
+ this.employee.set(emp);
|
|
|
+ } else {
|
|
|
+ this.showSnackBar('员工不存在');
|
|
|
+ this.goBack();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ goBack() {
|
|
|
+ this.location.back();
|
|
|
+ }
|
|
|
+
|
|
|
+ calculateAge(birthDate: Date): number {
|
|
|
+ if (!birthDate) return 0;
|
|
|
+ const today = new Date();
|
|
|
+ const birth = new Date(birthDate);
|
|
|
+ let age = today.getFullYear() - birth.getFullYear();
|
|
|
+ const monthDiff = today.getMonth() - birth.getMonth();
|
|
|
+ if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
|
|
+ age--;
|
|
|
+ }
|
|
|
+ return age;
|
|
|
+ }
|
|
|
+
|
|
|
+ calculateWorkYears(hireDate: Date): number {
|
|
|
+ if (!hireDate) return 0;
|
|
|
+ const today = new Date();
|
|
|
+ const hire = new Date(hireDate);
|
|
|
+ const diffTime = Math.abs(today.getTime() - hire.getTime());
|
|
|
+ const diffYears = Math.floor(diffTime / (1000 * 60 * 60 * 24 * 365));
|
|
|
+ return diffYears;
|
|
|
+ }
|
|
|
+
|
|
|
+ maskIdCard(idCard: string | undefined): string {
|
|
|
+ if (!idCard) return '未提供';
|
|
|
+ return idCard.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2');
|
|
|
+ }
|
|
|
+
|
|
|
+ maskBankCard(bankCard: string | undefined): string {
|
|
|
+ if (!bankCard) return '未提供';
|
|
|
+ return bankCard.replace(/(\d{4})\d{12}(\d{3})/, '$1 **** **** $2');
|
|
|
+ }
|
|
|
+
|
|
|
+ getStatusClass(status: string): string {
|
|
|
+ switch (status) {
|
|
|
+ case '在职': return 'status-active';
|
|
|
+ case '试用期': return 'status-probation';
|
|
|
+ case '离职': return 'status-resigned';
|
|
|
+ default: return '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ getScreeningStatusClass(status: string | undefined): string {
|
|
|
+ switch (status) {
|
|
|
+ case 'approved': return 'status-approved';
|
|
|
+ case 'rejected': return 'status-rejected';
|
|
|
+ case 'pending': return 'status-pending';
|
|
|
+ default: return 'status-unknown';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ getScreeningStatusIcon(status: string | undefined): string {
|
|
|
+ switch (status) {
|
|
|
+ case 'approved': return 'check_circle';
|
|
|
+ case 'rejected': return 'cancel';
|
|
|
+ case 'pending': return 'schedule';
|
|
|
+ default: return 'help';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ getScreeningStatusText(status: string | undefined): string {
|
|
|
+ switch (status) {
|
|
|
+ case 'approved': return '初筛通过';
|
|
|
+ case 'rejected': return '初筛不通过';
|
|
|
+ case 'pending': return '待审核';
|
|
|
+ default: return '未知状态';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ shareEmployee() {
|
|
|
+ if (navigator.share) {
|
|
|
+ navigator.share({
|
|
|
+ title: `员工档案 - ${this.employee()?.name}`,
|
|
|
+ text: `查看${this.employee()?.name}的员工档案信息`,
|
|
|
+ url: window.location.href
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 复制链接到剪贴板
|
|
|
+ navigator.clipboard.writeText(window.location.href);
|
|
|
+ this.showSnackBar('链接已复制到剪贴板');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ editEmployee() {
|
|
|
+ this.showSnackBar('编辑功能开发中...');
|
|
|
+ }
|
|
|
+
|
|
|
+ exportEmployee() {
|
|
|
+ this.showSnackBar('导出功能开发中...');
|
|
|
+ }
|
|
|
+
|
|
|
+ deleteEmployee() {
|
|
|
+ this.showSnackBar('删除功能开发中...');
|
|
|
+ }
|
|
|
+
|
|
|
+ editBasicInfo() {
|
|
|
+ this.showSnackBar('编辑基础信息功能开发中...');
|
|
|
+ }
|
|
|
+
|
|
|
+ editContactInfo() {
|
|
|
+ this.showSnackBar('编辑联系信息功能开发中...');
|
|
|
+ }
|
|
|
+
|
|
|
+ editWorkInfo() {
|
|
|
+ this.showSnackBar('编辑工作信息功能开发中...');
|
|
|
+ }
|
|
|
+
|
|
|
+ callEmployee(phone: string) {
|
|
|
+ if (phone) {
|
|
|
+ window.open(`tel:${phone}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ emailEmployee(email: string) {
|
|
|
+ if (email) {
|
|
|
+ window.open(`mailto:${email}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ approveScreening() {
|
|
|
+ this.screeningForm.patchValue({ status: 'approved' });
|
|
|
+ this.openScreeningDialog();
|
|
|
+ }
|
|
|
+
|
|
|
+ rejectScreening() {
|
|
|
+ this.screeningForm.patchValue({ status: 'rejected' });
|
|
|
+ this.openScreeningDialog();
|
|
|
+ }
|
|
|
+
|
|
|
+ private openScreeningDialog() {
|
|
|
+ // 这里应该打开对话框,简化处理直接提交
|
|
|
+ this.submitScreening();
|
|
|
+ }
|
|
|
+
|
|
|
+ submitScreening() {
|
|
|
+ if (this.screeningForm.valid) {
|
|
|
+ const formValue = this.screeningForm.value;
|
|
|
+ const currentEmployee = this.employee();
|
|
|
+
|
|
|
+ if (currentEmployee) {
|
|
|
+ const updatedEmployee = {
|
|
|
+ ...currentEmployee,
|
|
|
+ screeningStatus: formValue.status,
|
|
|
+ screeningComment: formValue.comment,
|
|
|
+ screeningTime: new Date()
|
|
|
+ };
|
|
|
+
|
|
|
+ this.employee.set(updatedEmployee);
|
|
|
+ this.showSnackBar('审核结果已保存');
|
|
|
+ this.screeningForm.reset();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ viewAttendance() {
|
|
|
+ this.router.navigate(['/hr/attendance'], {
|
|
|
+ queryParams: { employee: this.employee()?.id }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ viewSalary() {
|
|
|
+ this.showSnackBar('薪资记录功能开发中...');
|
|
|
+ }
|
|
|
+
|
|
|
+ private showSnackBar(message: string) {
|
|
|
+ this.snackBar.open(message, '关闭', {
|
|
|
+ duration: 3000,
|
|
|
+ horizontalPosition: 'center',
|
|
|
+ verticalPosition: 'top'
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|