import { Component, OnInit, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { EmployeeService } from '../services/employee.service'; import { DepartmentService } from '../services/department.service'; import { EmployeeInfoPanelComponent, EmployeeFullInfo } from '../../../shared/components/employee-info-panel'; interface Employee { id: string; name: string; // 昵称(内部沟通用) realname?: string; // 真实姓名 mobile: string; userid: string; roleName: string; department: string; departmentId?: string; isDisabled?: boolean; createdAt?: Date; // 新增展示字段 avatar?: string; email?: string; position?: string; gender?: string; level?: string; skills?: string[]; joinDate?: Date | string; workload?: { currentProjects?: number; completedProjects?: number; averageQuality?: number }; } interface Department { id: string; name: string; } @Component({ selector: 'app-employees', standalone: true, imports: [CommonModule, FormsModule, EmployeeInfoPanelComponent], templateUrl: './employees.html', styleUrls: ['./employees.scss'] }) export class Employees implements OnInit { // 数据 employees = signal([]); departments = signal([]); loading = signal(false); // 筛选 keyword = signal(''); roleFilter = signal('all'); // 侧边面板(原有的,保留用于向后兼容) showPanel = false; panelMode: 'detail' | 'edit' = 'detail'; currentEmployee: Employee | null = null; formModel: Partial = {}; // 新的员工信息面板 showEmployeeInfoPanel = false; selectedEmployeeForPanel: EmployeeFullInfo | null = null; // 统计 - 按身份统计 stats = { total: signal(0), service: signal(0), // 客服 designer: signal(0), // 组员(设计师) leader: signal(0), // 组长 hr: signal(0), // 人事 finance: signal(0) // 财务 }; // 角色列表 roles = ['客服', '组员', '组长', '人事', '财务','管理员']; constructor( private employeeService: EmployeeService, private departmentService: DepartmentService ) {} ngOnInit(): void { this.loadEmployees(); this.loadDepartments(); } async loadEmployees(): Promise { this.loading.set(true); try { const emps = await this.employeeService.findEmployees(); console.log(`🔍 [Employees] 开始加载 ${emps.length} 个员工的详细信息...`); const empList: Employee[] = await Promise.all(emps.map(async (e) => { const json = this.employeeService.toJSON(e); const data = (e as any).get ? ((e as any).get('data') || {}) : {}; const workload = data.workload || {}; const wxwork = data.wxworkInfo || {}; // 优先级说明: // 1. name(昵称):优先使用企微昵称 wxwork.name,其次 json.name,最后 data.name // 2. realname(真实姓名):优先使用用户填写的 data.realname // 3. mobile(手机号):优先使用企微手机号 wxwork.mobile,其次 data.mobile,最后 json.mobile const wxworkName = wxwork.name || ''; // 企微昵称 const wxworkMobile = wxwork.mobile || ''; // 企微手机号 const dataMobile = data.mobile || ''; // data字段中的手机号 const jsonMobile = json.mobile || ''; // Parse表字段的手机号 // 手机号优先级:企微手机号 > data.mobile > json.mobile let finalMobile = wxworkMobile || dataMobile || jsonMobile || ''; // 如果手机号为空或格式不对,尝试从其他字段获取 if (!finalMobile || !/^1[3-9]\d{9}$/.test(finalMobile)) { // 尝试从 data 中的其他可能字段获取 finalMobile = data.phone || data.telephone || wxwork.telephone || jsonMobile || ''; } // 🔥 获取员工的实际项目负载数据 let actualWorkload = { currentProjects: 0, completedProjects: 0, averageQuality: 0 }; // 只为设计师(组员)和组长查询项目负载 if (json.roleName === '组员' || json.roleName === '组长') { try { console.log(`🔍 [Employees] 开始查询员工项目负载:`, { 员工姓名: wxworkName || json.name, 员工ID: json.objectId, 角色: json.roleName }); const projectData = await this.employeeService.getEmployeeWorkload(json.objectId); actualWorkload = { currentProjects: projectData.currentProjects, completedProjects: projectData.completedProjects, averageQuality: workload.averageQuality || 0 }; if (projectData.currentProjects > 0 || projectData.completedProjects > 0) { console.log(`✅ [Employees] 员工 ${wxworkName || json.name} 项目负载:`, { 当前项目数: projectData.currentProjects, 已完成项目数: projectData.completedProjects, 进行中项目: projectData.ongoingProjects.map(p => `${p.name} (${p.id})`), 已完成项目: projectData.completedProjectsList.map(p => `${p.name} (${p.id})`) }); } else { console.warn(`⚠️ [Employees] 员工 ${wxworkName || json.name} (${json.objectId}) 没有查询到项目数据`); } } catch (error) { console.error(`❌ [Employees] 获取员工 ${json.objectId} 项目负载失败:`, error); } } return { id: json.objectId, name: wxworkName || json.name || data.name || '未知', // 优先企微昵称 realname: data.realname || '', // 用户填写的真实姓名 mobile: finalMobile, // 优先企微手机号 userid: json.userid || wxwork.userid || '', roleName: json.roleName || '未分配', department: e.get("department")?.get("name") || '未分配', departmentId: e.get("department")?.id, isDisabled: json.isDisabled || false, createdAt: json.createdAt, avatar: data.avatar || wxwork.avatar || '', email: data.email || wxwork.email || '', position: wxwork.position || '', gender: data.gender || wxwork.gender || '', level: data.level || '', skills: Array.isArray(data.skills) ? data.skills : [], joinDate: data.joinDate || '', workload: actualWorkload // 使用实际查询的负载数据 }; })); console.log(`✅ [Employees] 所有员工数据加载完成,共 ${empList.length} 人`); this.employees.set(empList); // 更新统计 this.stats.total.set(empList.length); this.stats.service.set(empList.filter(e => e.roleName === '客服').length); this.stats.designer.set(empList.filter(e => e.roleName === '组员').length); this.stats.leader.set(empList.filter(e => e.roleName === '组长').length); this.stats.hr.set(empList.filter(e => e.roleName === '人事').length); this.stats.finance.set(empList.filter(e => e.roleName === '财务').length); } catch (error) { console.error('加载员工列表失败:', error); } finally { this.loading.set(false); } } async loadDepartments(): Promise { try { const depts = await this.departmentService.findDepartments(); this.departments.set( depts.map(d => { const json = this.departmentService.toJSON(d); return { id: json.objectId, name: json.name }; }) ); } catch (error) { console.error('加载部门列表失败:', error); } } get filtered(): Employee[] { const kw = this.keyword().trim().toLowerCase(); const role = this.roleFilter(); let list = this.employees(); if (role !== 'all') { list = list.filter(e => e.roleName === role); } if (kw) { list = list.filter( e => (e.name || '').toLowerCase().includes(kw) || (e.realname || '').toLowerCase().includes(kw) || (e.mobile || '').includes(kw) || (e.userid || '').toLowerCase().includes(kw) || (e.email || '').toLowerCase().includes(kw) || (e.position || '').toLowerCase().includes(kw) ); } return list; } setRoleFilter(role: string) { this.roleFilter.set(role); } resetFilters() { this.keyword.set(''); this.roleFilter.set('all'); } // 查看详情(使用新的员工信息面板) // ⭐ 优化:数据预加载后再显示面板,避免数据闪烁 async viewEmployee(emp: Employee) { console.log(`🚀 [Employees] 开始打开员工信息面板: ${emp.realname || emp.name} (${emp.id})`); // 准备基础数据 const baseData: EmployeeFullInfo = { id: emp.id, name: emp.name, realname: emp.realname, mobile: emp.mobile, userid: emp.userid, roleName: emp.roleName, department: emp.department, departmentId: emp.departmentId, isDisabled: emp.isDisabled, createdAt: emp.createdAt, avatar: emp.avatar, email: emp.email, position: emp.position, gender: emp.gender, level: emp.level, skills: emp.skills, joinDate: emp.joinDate, workload: emp.workload, currentProjects: 0, projectData: [], projectNames: [], leaveRecords: [], redMarkExplanation: '' }; // ⭐ 关键修复:如果是设计师,先加载完整数据再显示面板(避免数据闪烁) if (emp.roleName === '组员' || emp.roleName === '组长') { try { console.log(`🔄 [Employees] 预加载员工 ${emp.id} 的完整数据...`); // 1️⃣ 加载项目数据 const wl = await this.employeeService.getEmployeeWorkload(emp.id); console.log(`✅ [Employees] 项目数据加载完成:`, { currentProjects: wl.currentProjects, ongoingProjects: wl.ongoingProjects.length, 项目列表: wl.ongoingProjects.map(p => p.name) }); // 2️⃣ 准备项目数据(前3个核心项目) const coreProjects = (wl.ongoingProjects || []).slice(0, 3).map(p => ({ id: p.id, name: p.name })); // 3️⃣ 保存项目数据(用于月份切换) this.currentEmployeeProjects = wl.ongoingProjects || []; // 4️⃣ 生成日历数据(使用修复后的算法) const calendarData = this.buildCalendarData(this.currentEmployeeProjects); console.log(`📅 [Employees] 日历数据生成完成:`, { days: calendarData.days.length, 有项目的天数: calendarData.days.filter(d => d.projectCount > 0).length }); // 5️⃣ 加载问卷数据 const surveyInfo = await this.loadEmployeeSurvey(emp.id, emp.realname || emp.name); console.log(`📝 [Employees] 问卷数据加载完成:`, { completed: surveyInfo.completed, answers: surveyInfo.data?.answers?.length || 0 }); // 6️⃣ 组装完整数据 this.selectedEmployeeForPanel = { ...baseData, currentProjects: wl.currentProjects || 0, projectData: coreProjects, projectNames: coreProjects.map(p => p.name), calendarData: calendarData, surveyCompleted: surveyInfo.completed, surveyData: surveyInfo.data, profileId: surveyInfo.profileId }; console.log(`🎯 [Employees] 完整数据准备完成,打开面板:`, { currentProjects: this.selectedEmployeeForPanel.currentProjects, projectData: this.selectedEmployeeForPanel.projectData?.length, calendarData: '✅', surveyData: surveyInfo.completed ? '✅' : '❌' }); } catch (err) { console.error(`❌ [Employees] 加载员工数据失败:`, err); // 失败时使用基础数据 this.selectedEmployeeForPanel = baseData; alert('加载员工项目数据失败,仅显示基础信息'); } } else { // ⭐ 修复:非设计师角色也需要加载问卷数据 console.log(`📦 [Employees] 非设计师角色,加载基础数据和问卷...`); try { // 加载问卷数据 const surveyInfo = await this.loadEmployeeSurvey(emp.id, emp.realname || emp.name); console.log(`📝 [Employees] 问卷数据加载完成:`, { completed: surveyInfo.completed, answers: surveyInfo.data?.answers?.length || 0 }); // 组装数据(包含问卷) this.selectedEmployeeForPanel = { ...baseData, surveyCompleted: surveyInfo.completed, surveyData: surveyInfo.data, profileId: surveyInfo.profileId }; console.log(`🎯 [Employees] 非设计师数据准备完成:`, { surveyData: surveyInfo.completed ? '✅' : '❌' }); } catch (err) { console.error(`❌ [Employees] 加载问卷数据失败:`, err); // 失败时使用基础数据(不包含问卷) this.selectedEmployeeForPanel = baseData; } } // ⭐ 关键修复:数据准备完成后才显示面板 this.showEmployeeInfoPanel = true; console.log(`✅ [Employees] 面板已显示`); } /** * 根据项目的整个生命周期生成日历数据(与组长端对齐) * ⭐ 关键修复:基于项目的 [createdAt, deadline] 范围填充所有天数 * @param projects 项目列表 * @param targetMonth 目标月份(可选,默认为当前月) */ private buildCalendarData( projects: Array<{ id: string; name: string; deadline?: any; createdAt?: any }>, targetMonth?: Date ): { currentMonth: Date; days: any[] } { const now = targetMonth || new Date(); const year = now.getFullYear(); const month = now.getMonth(); // 0-11 const firstOfMonth = new Date(year, month, 1); const lastOfMonth = new Date(year, month + 1, 0); const firstWeekday = firstOfMonth.getDay(); // 0(日)-6(六) const daysInMonth = lastOfMonth.getDate(); // 辅助函数:解析日期 const parseDate = (d: any): Date | null => { if (!d) return null; if (d instanceof Date) return d; if (d.toDate && typeof d.toDate === 'function') return d.toDate(); // Parse Date 对象 const t = new Date(d); return isNaN(t.getTime()) ? null : t; }; const sameDay = (a: Date, b: Date): boolean => { return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); }; // ⭐ 计算"今天"和"明天" const today = new Date(); today.setHours(0, 0, 0, 0); const tomorrow = new Date(today); tomorrow.setDate(today.getDate() + 1); const days: any[] = []; // 前置填充(上月尾巴),保持从周日开始的网格对齐 for (let i = 0; i < firstWeekday; i++) { const d = new Date(year, month, 1 - (firstWeekday - i)); days.push({ date: d, projectCount: 0, projects: [], isToday: sameDay(d, today), isTomorrow: sameDay(d, tomorrow), isCurrentMonth: false }); } // ⭐ 关键修复:本月每一天,找出该天在项目生命周期内的所有项目 for (let day = 1; day <= daysInMonth; day++) { const date = new Date(year, month, day); date.setHours(0, 0, 0, 0); // 找出该日期相关的项目(项目在 [startDate, endDate] 范围内) const dayProjects = projects.filter(p => { const createdAt = parseDate((p as any).createdAt); const deadline = parseDate((p as any).deadline); // ⭐ 智能处理:如果项目既没有 deadline 也没有 createdAt,则跳过 if (!deadline && !createdAt) { return false; } // ⭐ 智能处理日期范围(与组长端对齐) let startDate: Date; let endDate: Date; if (deadline && createdAt) { // 情况1:两个日期都有 startDate = new Date(createdAt); endDate = new Date(deadline); } else if (deadline) { // 情况2:只有deadline,往前推30天 startDate = new Date(deadline.getTime() - 30 * 24 * 60 * 60 * 1000); endDate = new Date(deadline); } else { // 情况3:只有createdAt,往后推30天 startDate = new Date(createdAt!); endDate = new Date(createdAt!.getTime() + 30 * 24 * 60 * 60 * 1000); } startDate.setHours(0, 0, 0, 0); endDate.setHours(0, 0, 0, 0); // ⭐ 关键:项目在 [startDate, endDate] 范围内的所有天都显示 const inRange = date >= startDate && date <= endDate; return inRange; }); days.push({ date, projectCount: dayProjects.length, projects: dayProjects.map(p => ({ id: p.id, name: p.name, deadline: parseDate((p as any).deadline) })), isToday: sameDay(date, today), isTomorrow: sameDay(date, tomorrow), // ⭐ 标记明天 isCurrentMonth: true }); } // 后置填充到 6 行 * 7 列 = 42 格 while (days.length % 7 !== 0) { const last = days[days.length - 1].date as Date; const d = new Date(last.getFullYear(), last.getMonth(), last.getDate() + 1); days.push({ date: d, projectCount: 0, projects: [], isToday: sameDay(d, today), isTomorrow: sameDay(d, tomorrow), isCurrentMonth: d.getMonth() === month }); } // ⭐ 详细的调试日志 console.log(`📅 [buildCalendarData] 日历生成完成:`, { 总天数: days.length, 本月天数: daysInMonth, 有项目的天数: days.filter(d => d.isCurrentMonth && d.projectCount > 0).length, 项目总数: projects.length, 项目详情: projects.map(p => ({ name: p.name, createdAt: (p as any).createdAt, deadline: (p as any).deadline })) }); // 输出每一天的项目统计(只输出有项目的天) const daysWithProjects = days.filter(d => d.isCurrentMonth && d.projectCount > 0); if (daysWithProjects.length > 0) { console.log(`📅 [buildCalendarData] 有项目的日期:`, daysWithProjects.map(d => ({ 日期: d.date.toISOString().split('T')[0], 项目数: d.projectCount, 项目: d.projects.map((p: any) => p.name) }))); } return { currentMonth: new Date(year, month, 1), days }; } /** * 加载员工问卷数据(与组长端对齐) */ private async loadEmployeeSurvey(employeeId: string, employeeName: string): Promise<{ completed: boolean; data: any; profileId: string }> { try { const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova')); // ⭐ 与组长端完全一致的查询逻辑 // 通过员工名字查找Profile(同时查询 realname 和 name 字段) const realnameQuery = new Parse.Query('Profile'); realnameQuery.equalTo('realname', employeeName); const nameQuery = new Parse.Query('Profile'); nameQuery.equalTo('name', employeeName); // 使用 or 查询 const profileQuery = Parse.Query.or(realnameQuery, nameQuery); profileQuery.limit(1); const profileResults = await profileQuery.find(); console.log(`🔍 查找员工 ${employeeName},找到 ${profileResults.length} 个结果`); if (profileResults.length > 0) { const profile = profileResults[0]; const profileId = profile.id; // ⭐ 详细输出 Profile 的所有字段,帮助诊断 console.log(`📋 Profile 详细信息:`, { id: profileId, realname: profile.get('realname'), name: profile.get('name'), surveyCompleted: profile.get('surveyCompleted') }); const surveyCompleted = profile.get('surveyCompleted') || false; console.log(`📋 Profile ID: ${profileId}, surveyCompleted: ${surveyCompleted}`); // 如果已完成问卷,加载问卷答案 if (surveyCompleted) { const surveyQuery = new Parse.Query('SurveyLog'); surveyQuery.equalTo('profile', profile.toPointer()); surveyQuery.equalTo('type', 'survey-profile'); surveyQuery.descending('createdAt'); surveyQuery.limit(1); console.log(`📝 开始查询 SurveyLog,查询条件:`, { profileId: profileId, type: 'survey-profile' }); const surveyResults = await surveyQuery.find(); console.log(`📝 找到 ${surveyResults.length} 条问卷记录`); // ⭐ 如果没找到 'survey-profile',尝试查询所有类型 if (surveyResults.length === 0) { console.warn(`⚠️ 未找到 type='survey-profile' 的记录,尝试查询所有类型...`); const allTypeQuery = new Parse.Query('SurveyLog'); allTypeQuery.equalTo('profile', profile.toPointer()); allTypeQuery.descending('createdAt'); allTypeQuery.limit(5); const allResults = await allTypeQuery.find(); console.log(`📝 该员工的所有问卷记录 (${allResults.length} 条):`, allResults.map(s => ({ id: s.id, type: s.get('type'), answersCount: s.get('answers')?.length || 0, createdAt: s.get('createdAt') })) ); } if (surveyResults.length > 0) { const survey = surveyResults[0]; const surveyData = { answers: survey.get('answers') || [], createdAt: survey.get('createdAt'), updatedAt: survey.get('updatedAt') }; console.log(`✅ 加载问卷数据成功,共 ${surveyData.answers.length} 道题`); return { completed: true, data: surveyData, profileId }; } } console.log(`📋 员工 ${employeeName} 问卷状态:`, surveyCompleted ? '已完成' : '未完成'); return { completed: false, data: null, profileId }; } else { console.warn(`⚠️ 未找到员工 ${employeeName} 的 Profile`); return { completed: false, data: null, profileId: '' }; } } catch (error) { console.error(`❌ [loadEmployeeSurvey] 加载员工 ${employeeName} 问卷数据失败:`, error); return { completed: false, data: null, profileId: '' }; } } // 查看详情(旧版本,保留用于向后兼容) viewEmployeeOld(emp: Employee) { this.currentEmployee = emp; this.panelMode = 'detail'; this.showPanel = true; } // 编辑 (员工从企微同步,只能编辑部分字段,不能删除) editEmployee(emp: Employee) { this.currentEmployee = emp; // 复制所有需要编辑的字段 this.formModel = { name: emp.name, // 昵称 realname: emp.realname, // 真实姓名 mobile: emp.mobile, userid: emp.userid, // 企微ID只读,但需要显示 roleName: emp.roleName, departmentId: emp.departmentId, isDisabled: emp.isDisabled || false }; this.panelMode = 'edit'; this.showPanel = true; } // ⭐ 保存当前员工的项目数据(用于切换月份) currentEmployeeProjects: Array<{ id: string; name: string; deadline?: any; createdAt?: any }> = []; // 关闭新的员工信息面板 closeEmployeeInfoPanel() { this.showEmployeeInfoPanel = false; this.selectedEmployeeForPanel = null; this.currentEmployeeProjects = []; // 清空项目数据 } /** * 切换日历月份(与组长端对齐) * @param direction -1=上月, 1=下月 */ onChangeMonth(direction: number): void { if (!this.selectedEmployeeForPanel?.calendarData) { console.warn(`⚠️ [onChangeMonth] 日历数据不存在`); return; } console.log(`📅 [onChangeMonth] 切换月份: ${direction > 0 ? '下月' : '上月'}`); const currentMonth = this.selectedEmployeeForPanel.calendarData.currentMonth; const newMonth = new Date(currentMonth); newMonth.setMonth(newMonth.getMonth() + direction); // 重新生成指定月份的日历数据 const newCalendarData = this.buildCalendarData(this.currentEmployeeProjects, newMonth); console.log(`📅 [onChangeMonth] 新月份日历生成完成:`, { 月份: `${newMonth.getFullYear()}年${newMonth.getMonth() + 1}月`, 有项目的天数: newCalendarData.days.filter(d => d.isCurrentMonth && d.projectCount > 0).length }); // 更新员工详情中的日历数据 this.selectedEmployeeForPanel = { ...this.selectedEmployeeForPanel, calendarData: newCalendarData }; } /** * 处理日历日期点击事件 */ onCalendarDayClick(day: any): void { console.log(`📅 [onCalendarDayClick] 点击日期:`, { 日期: day.date, 项目数: day.projectCount, 项目列表: day.projects }); // TODO: 可以显示当天的项目详情弹窗 } /** * 处理项目点击事件 */ onProjectClick(projectId: string): void { console.log(`🔗 [onProjectClick] 点击项目: ${projectId}`); // TODO: 导航到项目详情页 // this.router.navigate(['/project', projectId]); } /** * 刷新问卷数据 */ async onRefreshSurvey(): Promise { if (!this.selectedEmployeeForPanel) { return; } console.log(`🔄 [onRefreshSurvey] 刷新问卷数据...`); try { const employeeId = this.selectedEmployeeForPanel.id; const employeeName = this.selectedEmployeeForPanel.realname || this.selectedEmployeeForPanel.name; const surveyInfo = await this.loadEmployeeSurvey(employeeId, employeeName); // 更新问卷数据 this.selectedEmployeeForPanel = { ...this.selectedEmployeeForPanel, surveyCompleted: surveyInfo.completed, surveyData: surveyInfo.data, profileId: surveyInfo.profileId }; console.log(`✅ [onRefreshSurvey] 问卷数据刷新完成:`, { completed: surveyInfo.completed, answersCount: surveyInfo.data?.answers?.length || 0 }); } catch (error) { console.error(`❌ [onRefreshSurvey] 刷新失败:`, error); } } // 更新员工信息(从新面板触发) async updateEmployeeInfo(updates: Partial) { try { await this.employeeService.updateEmployee(updates.id!, { name: updates.name, mobile: updates.mobile, roleName: updates.roleName, departmentId: updates.departmentId, isDisabled: updates.isDisabled, data: { realname: updates.realname } }); console.log('✅ 员工信息已更新(从新面板)', updates); // 重新加载员工列表 await this.loadEmployees(); // 关闭面板 this.closeEmployeeInfoPanel(); alert('员工信息更新成功!'); } catch (error) { console.error('更新员工失败:', error); alert('更新员工失败,请重试'); } } // 关闭面板(旧版本) closePanel() { this.showPanel = false; this.panelMode = 'detail'; this.currentEmployee = null; this.formModel = {}; } // 提交编辑 async updateEmployee() { if (!this.currentEmployee) return; // 表单验证 if (!this.formModel.name?.trim()) { alert('请输入员工姓名'); return; } if (!this.formModel.mobile?.trim()) { alert('请输入手机号'); return; } // 手机号格式验证 const mobileRegex = /^1[3-9]\d{9}$/; if (!mobileRegex.test(this.formModel.mobile)) { alert('请输入正确的手机号格式'); return; } if (!this.formModel.roleName) { alert('请选择员工身份'); return; } try { // 保存所有可编辑字段到后端数据库 await this.employeeService.updateEmployee(this.currentEmployee.id, { name: this.formModel.name.trim(), mobile: this.formModel.mobile.trim(), roleName: this.formModel.roleName, departmentId: this.formModel.departmentId, isDisabled: this.formModel.isDisabled || false, data: { realname: this.formModel.realname?.trim() || '' } }); console.log('✅ 员工信息已保存到Parse数据库', { id: this.currentEmployee.id, name: this.formModel.name, realname: this.formModel.realname, mobile: this.formModel.mobile, roleName: this.formModel.roleName, departmentId: this.formModel.departmentId, isDisabled: this.formModel.isDisabled }); await this.loadEmployees(); this.closePanel(); // 显示成功提示 alert('员工信息更新成功!'); } catch (error) { console.error('更新员工失败:', error); window?.fmode?.alert('更新员工失败,请重试'); } } // 禁用/启用员工(从新面板触发) async toggleEmployeeFromPanel(emp: EmployeeFullInfo) { const action = emp.isDisabled ? '启用' : '禁用'; if (!confirm(`确定要${action}员工 "${emp.name}" 吗?`)) { return; } try { await this.employeeService.toggleEmployee(emp.id, !emp.isDisabled); await this.loadEmployees(); // 更新面板中的员工信息 if (this.selectedEmployeeForPanel?.id === emp.id) { this.selectedEmployeeForPanel = { ...this.selectedEmployeeForPanel, isDisabled: !emp.isDisabled }; } alert(`${action}成功!`); } catch (error) { console.error(`${action}员工失败:`, error); alert(`${action}员工失败,请重试`); } } // 禁用/启用员工 async toggleEmployee(emp: Employee) { const action = emp.isDisabled ? '启用' : '禁用'; if (!await window?.fmode?.confirm(`确定要${action}员工 "${emp.name}" 吗?`)) { return; } try { await this.employeeService.toggleEmployee(emp.id, !emp.isDisabled); await this.loadEmployees(); } catch (error) { console.error(`${action}员工失败:`, error); window?.fmode?.alert(`${action}员工失败,请重试`); } } // 导出 exportEmployees() { const header = ['姓名', '手机号', '企微ID', '身份', '部门', '状态', '创建时间']; const rows = this.filtered.map(e => [ e.name, e.mobile, e.userid, e.roleName, e.department, e.isDisabled ? '已禁用' : '正常', e.createdAt instanceof Date ? e.createdAt.toISOString().slice(0, 10) : String(e.createdAt || '') ]); this.downloadCSV('员工列表.csv', [header, ...rows]); } private downloadCSV(filename: string, rows: (string | number)[][]) { const escape = (val: string | number) => { const s = String(val ?? ''); if (/[",\n]/.test(s)) return '"' + s.replace(/"/g, '""') + '"'; return s; }; const csv = rows.map(r => r.map(escape).join(',')).join('\n'); const blob = new Blob(['\ufeff', csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } }