| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931 |
- 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<Employee[]>([]);
- departments = signal<Department[]>([]);
- loading = signal(false);
- // 筛选
- keyword = signal('');
- roleFilter = signal<string>('all');
- // 侧边面板(原有的,保留用于向后兼容)
- showPanel = false;
- panelMode: 'detail' | 'edit' = 'detail';
- currentEmployee: Employee | null = null;
- formModel: Partial<Employee> = {};
- // 新的员工信息面板
- 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<void> {
- 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<void> {
- 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<void> {
- 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<EmployeeFullInfo>) {
- 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);
- }
- }
|