Bladeren bron

feat: enhance dashboard with top navigation bar and detailed employee calendar view

- Added a fixed top navigation bar displaying the current date and user profile information.
- Implemented a detailed calendar section showing project load for each day of the month.
- Introduced a modal for displaying projects on specific calendar days.
- Updated styles for improved layout and user experience.
0235711 2 dagen geleden
bovenliggende
commit
0ebd042867

+ 1413 - 0
docs/designer-wxwork-real-data-implementation.md

@@ -0,0 +1,1413 @@
+# 设计师端(组员端)企业微信身份识别与真实数据接入实施方案
+
+## 文档概述
+
+本文档详细说明如何为**设计师端(Designer/组员端)**优化企业微信身份识别并接入真实数据库,包括任务管理、请假申请、个人数据等功能的完整实现。
+
+---
+
+## 一、现状分析
+
+### 1.1 设计师端当前实现情况
+
+#### 企微认证 ✅
+
+**已实现内容**:
+- ✅ 路由守卫:`WxworkAuthGuard`(`app.routes.ts` 第64行)
+- ✅ 组件内认证:`WxworkAuth` 实例(`dashboard.ts` 第55-72行)
+- ✅ 认证流程:`authenticateAndLoadData()` 方法(第79-96行)
+- ✅ 降级机制:认证失败时使用模拟数据
+
+**代码位置**:
+```typescript
+// src/app/pages/designer/dashboard/dashboard.ts
+export class Dashboard implements OnInit {
+  private wxAuth: WxworkAuth | null = null;
+  private currentUser: FmodeUser | null = null;
+
+  constructor(private projectService: ProjectService) {
+    this.initAuth();
+  }
+
+  private initAuth(): void {
+    this.wxAuth = new WxworkAuth({
+      cid: 'cDL6R1hgSi'  // 公司帐套ID
+    });
+  }
+
+  async ngOnInit(): Promise<void> {
+    await this.authenticateAndLoadData();
+  }
+}
+```
+
+#### 数据接入现状 ❌
+
+| 功能模块 | 当前状态 | 数据来源 | 问题 |
+|---------|---------|---------|------|
+| **任务列表** | ❌ 模拟数据 | `ProjectService.getTasks()` | 返回的是硬编码的模拟数据 |
+| **待处理反馈** | ❌ 模拟数据 | `loadPendingFeedbacks()` | 使用 mockFeedbacks 数组 |
+| **代班任务** | ❌ 模拟数据 | `loadShiftTasks()` | 使用 mockShiftTasks |
+| **工作量计算** | ❌ 模拟数据 | `calculateWorkloadPercentage()` | 基于模拟任务计算 |
+| **项目时间线** | ❌ 模拟数据 | `loadProjectTimeline()` | 使用 mockTimeline |
+| **技能标签** | ❌ 模拟数据 | `PersonalBoard.loadSkillTags()` | 从 ProjectService 获取模拟数据 |
+| **绩效数据** | ❌ 模拟数据 | `PersonalBoard.loadPerformanceData()` | 从 ProjectService 获取模拟数据 |
+| **请假申请** | ❌ 未实现 | 无 | 设计师无法申请或查看请假 |
+
+### 1.2 核心问题
+
+1. **数据真实性问题**:所有数据都是模拟的,无法支持真实业务
+2. **功能缺失问题**:缺少请假申请、个人信息管理等关键功能
+3. **角色识别问题**:虽有企微认证,但未验证是否为"组员"角色
+4. **数据同步问题**:设计师的个人信息(技能、绩效)无法更新
+
+---
+
+## 二、技术方案设计
+
+### 2.1 优化企微身份识别
+
+#### 当前问题
+- ❌ cid 硬编码在代码中(`'cDL6R1hgSi'`)
+- ❌ 未从URL参数获取cid
+- ❌ 未验证用户的"组员"角色
+- ❌ 未获取当前登录的Profile信息
+
+#### 优化方案
+
+**第1步:修改路由配置,支持cid参数**
+
+```typescript
+// src/app/app.routes.ts
+{
+  path: ':cid/designer',  // 添加 cid 参数
+  canActivate: [WxworkAuthGuard],
+  children: [
+    {
+      path: 'dashboard',
+      loadComponent: () => import('./pages/designer/dashboard/dashboard')
+        .then(m => m.Dashboard),
+      title: '设计师工作台'
+    },
+    // ... 其他子路由
+  ]
+}
+```
+
+**第2步:优化Dashboard组件的认证流程**
+
+```typescript
+// src/app/pages/designer/dashboard/dashboard.ts
+import { ActivatedRoute } from '@angular/router';
+import { ProfileService } from '../../../services/profile.service';
+
+export class Dashboard implements OnInit {
+  private wxAuth: WxworkAuth | null = null;
+  private currentUser: FmodeUser | null = null;
+  private currentProfile: FmodeObject | null = null; // 新增:当前Profile
+  private cid: string = '';
+
+  constructor(
+    private projectService: ProjectService,
+    private route: ActivatedRoute,  // 新增
+    private router: Router,         // 新增
+    private profileService: ProfileService  // 新增
+  ) {}
+
+  async ngOnInit(): Promise<void> {
+    // 1. 从URL获取cid
+    this.route.paramMap.subscribe(async params => {
+      this.cid = params.get('cid') || localStorage.getItem('company') || '';
+      
+      if (!this.cid) {
+        console.error('❌ 未找到公司ID');
+        alert('缺少公司信息,请联系管理员');
+        return;
+      }
+      
+      // 2. 初始化企微认证
+      this.initAuth();
+      
+      // 3. 执行认证并加载数据
+      await this.authenticateAndLoadData();
+    });
+  }
+
+  // 初始化企业微信认证(修改版)
+  private initAuth(): void {
+    try {
+      this.wxAuth = new WxworkAuth({
+        cid: this.cid,  // 使用动态获取的cid
+        appId: 'crm'
+      });
+      console.log('✅ 设计师端企微认证初始化成功');
+    } catch (error) {
+      console.error('❌ 设计师端企微认证初始化失败:', error);
+    }
+  }
+
+  // 认证并加载数据(优化版)
+  private async authenticateAndLoadData(): Promise<void> {
+    try {
+      // 执行企业微信认证和登录
+      const { user, profile } = await this.wxAuth!.authenticateAndLogin();
+      this.currentUser = user;
+      this.currentProfile = profile;
+
+      if (!user || !profile) {
+        console.error('❌ 设计师登录失败');
+        this.loadMockData();
+        return;
+      }
+
+      console.log('✅ 设计师登录成功:', user.get('username'));
+      console.log('✅ Profile ID:', profile.id);
+      
+      // 验证角色是否为"组员"
+      if (!await this.validateDesignerRole()) {
+        alert('您不是设计师,无权访问此页面');
+        this.router.navigate(['/']);
+        return;
+      }
+      
+      // 缓存Profile ID
+      localStorage.setItem('Parse/ProfileId', profile.id);
+      
+      // 加载真实数据
+      await this.loadDashboardData();
+      
+    } catch (error) {
+      console.error('❌ 设计师认证过程出错:', error);
+      // 降级到模拟数据
+      this.loadMockData();
+    }
+  }
+  
+  /**
+   * 验证设计师(组员)角色
+   */
+  private async validateDesignerRole(): Promise<boolean> {
+    if (!this.currentProfile) {
+      return false;
+    }
+    
+    const roleName = this.currentProfile.get('roleName');
+    
+    if (roleName !== '组员') {
+      console.warn(`⚠️ 用户角色为"${roleName}",不是"组员"`);
+      return false;
+    }
+    
+    console.log('✅ 角色验证通过:组员(设计师)');
+    return true;
+  }
+}
+```
+
+### 2.2 任务数据真实接入
+
+#### 创建DesignerTaskService
+
+```typescript
+// src/app/services/designer-task.service.ts
+import { Injectable } from '@angular/core';
+import { FmodeParse } from 'fmode-ng/parse';
+
+export interface DesignerTask {
+  id: string;
+  projectId: string;
+  projectName: string;
+  stage: string;
+  deadline: Date;
+  isOverdue: boolean;
+  priority: 'high' | 'medium' | 'low';
+  customerName: string;
+  space?: string; // 空间名称(如"主卧")
+  productId?: string; // 关联的Product ID
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class DesignerTaskService {
+  private Parse: any = null;
+  private cid: string = '';
+
+  constructor() {
+    this.initParse();
+  }
+
+  private async initParse(): Promise<void> {
+    try {
+      const { FmodeParse } = await import('fmode-ng/parse');
+      this.Parse = FmodeParse.with('nova');
+      this.cid = localStorage.getItem('company') || '';
+    } catch (error) {
+      console.error('DesignerTaskService: Parse初始化失败:', error);
+    }
+  }
+
+  /**
+   * 获取当前设计师的任务列表
+   * @param designerId Profile的objectId
+   */
+  async getMyTasks(designerId: string): Promise<DesignerTask[]> {
+    if (!this.Parse) await this.initParse();
+    if (!this.Parse || !this.cid) return [];
+
+    try {
+      // 方案1:从ProjectTeam表查询(设计师实际负责的项目)
+      const teamQuery = new this.Parse.Query('ProjectTeam');
+      teamQuery.equalTo('profile', this.Parse.Object.extend('Profile').createWithoutData(designerId));
+      teamQuery.notEqualTo('isDeleted', true);
+      teamQuery.include('project');
+      teamQuery.include('project.contact');
+      teamQuery.limit(1000);
+
+      const teamRecords = await teamQuery.find();
+
+      if (teamRecords.length === 0) {
+        console.warn('⚠️ 未找到分配给该设计师的项目');
+        return [];
+      }
+
+      const tasks: DesignerTask[] = [];
+
+      for (const teamRecord of teamRecords) {
+        const project = teamRecord.get('project');
+        if (!project) continue;
+
+        const projectId = project.id;
+        const projectName = project.get('title') || '未命名项目';
+        const currentStage = project.get('currentStage') || '未知';
+        const deadline = project.get('deadline') || new Date();
+        const contact = project.get('contact');
+        const customerName = contact?.get('name') || '未知客户';
+
+        // 查询该项目下该设计师负责的Product(空间设计产品)
+        const productQuery = new this.Parse.Query('Product');
+        productQuery.equalTo('project', project);
+        productQuery.equalTo('profile', this.Parse.Object.extend('Profile').createWithoutData(designerId));
+        productQuery.notEqualTo('isDeleted', true);
+        productQuery.containedIn('status', ['in_progress', 'awaiting_review']);
+        
+        const products = await productQuery.find();
+
+        if (products.length === 0) {
+          // 如果没有具体的Product,创建项目级任务
+          tasks.push({
+            id: projectId,
+            projectId,
+            projectName,
+            stage: currentStage,
+            deadline: new Date(deadline),
+            isOverdue: new Date(deadline) < new Date(),
+            priority: this.calculatePriority(deadline, currentStage),
+            customerName
+          });
+        } else {
+          // 如果有Product,为每个Product创建任务
+          products.forEach((product: any) => {
+            const productName = product.get('productName') || '未命名空间';
+            const productStage = product.get('stage') || currentStage;
+            
+            tasks.push({
+              id: `${projectId}-${product.id}`,
+              projectId,
+              projectName: `${projectName} - ${productName}`,
+              stage: productStage,
+              deadline: new Date(deadline),
+              isOverdue: new Date(deadline) < new Date(),
+              priority: this.calculatePriority(deadline, productStage),
+              customerName,
+              space: productName,
+              productId: product.id
+            });
+          });
+        }
+      }
+
+      // 按截止日期排序
+      tasks.sort((a, b) => a.deadline.getTime() - b.deadline.getTime());
+
+      console.log(`✅ 成功加载 ${tasks.length} 个任务`);
+      return tasks;
+
+    } catch (error) {
+      console.error('获取设计师任务失败:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 计算任务优先级
+   */
+  private calculatePriority(deadline: Date, stage: string): 'high' | 'medium' | 'low' {
+    const now = new Date();
+    const daysLeft = Math.ceil((new Date(deadline).getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
+    
+    // 超期或临期(3天内)
+    if (daysLeft < 0 || daysLeft <= 3) {
+      return 'high';
+    }
+    
+    // 渲染阶段优先级高
+    if (stage === 'rendering' || stage === '渲染') {
+      return 'high';
+    }
+    
+    // 7天内
+    if (daysLeft <= 7) {
+      return 'medium';
+    }
+    
+    return 'low';
+  }
+  
+  /**
+   * 方案2:从Project.assignee查询(如果不使用ProjectTeam表)
+   */
+  async getMyTasksFromAssignee(designerId: string): Promise<DesignerTask[]> {
+    if (!this.Parse) await this.initParse();
+    if (!this.Parse || !this.cid) return [];
+
+    try {
+      const query = new this.Parse.Query('Project');
+      query.equalTo('assignee', this.Parse.Object.extend('Profile').createWithoutData(designerId));
+      query.equalTo('company', this.cid);
+      query.containedIn('status', ['进行中', '待审核']);
+      query.notEqualTo('isDeleted', true);
+      query.include('contact');
+      query.ascending('deadline');
+      query.limit(1000);
+
+      const projects = await query.find();
+
+      return projects.map((project: any) => {
+        const deadline = project.get('deadline') || new Date();
+        const currentStage = project.get('currentStage') || '未知';
+        const contact = project.get('contact');
+
+        return {
+          id: project.id,
+          projectId: project.id,
+          projectName: project.get('title') || '未命名项目',
+          stage: currentStage,
+          deadline: new Date(deadline),
+          isOverdue: new Date(deadline) < new Date(),
+          priority: this.calculatePriority(deadline, currentStage),
+          customerName: contact?.get('name') || '未知客户'
+        };
+      });
+
+    } catch (error) {
+      console.error('从Project.assignee获取任务失败:', error);
+      return [];
+    }
+  }
+}
+```
+
+#### 修改Dashboard组件使用真实数据
+
+```typescript
+// src/app/pages/designer/dashboard/dashboard.ts
+import { DesignerTaskService } from '../../../services/designer-task.service';
+
+export class Dashboard implements OnInit {
+  constructor(
+    private projectService: ProjectService,
+    private route: ActivatedRoute,
+    private router: Router,
+    private profileService: ProfileService,
+    private taskService: DesignerTaskService  // 新增
+  ) {}
+
+  // 加载仪表板数据(修改版)
+  private async loadDashboardData(): Promise<void> {
+    try {
+      if (!this.currentProfile) {
+        throw new Error('未找到当前Profile');
+      }
+
+      await Promise.all([
+        this.loadRealTasks(),      // 使用真实数据
+        this.loadShiftTasks(),
+        this.calculateWorkloadPercentage(),
+        this.loadProjectTimeline()
+      ]);
+      console.log('✅ 设计师仪表板数据加载完成');
+    } catch (error) {
+      console.error('❌ 设计师仪表板数据加载失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 加载真实任务数据
+   */
+  private async loadRealTasks(): Promise<void> {
+    try {
+      const designerTasks = await this.taskService.getMyTasks(this.currentProfile!.id);
+      
+      // 转换为组件所需格式
+      this.tasks = designerTasks.map(task => ({
+        id: task.id,
+        projectId: task.projectId,
+        name: task.projectName,
+        stage: task.stage,
+        deadline: task.deadline,
+        isOverdue: task.isOverdue,
+        priority: task.priority,
+        customerName: task.customerName
+      }));
+
+      // 筛选超期任务
+      this.overdueTasks = this.tasks.filter(task => task.isOverdue);
+
+      // 筛选紧急任务
+      this.urgentTasks = this.tasks.filter(task => {
+        const now = new Date();
+        const diffHours = (task.deadline.getTime() - now.getTime()) / (1000 * 60 * 60);
+        return diffHours <= 3 && diffHours > 0 && task.stage === '渲染';
+      });
+
+      // 加载待处理反馈
+      await this.loadRealPendingFeedbacks();
+
+      // 启动倒计时
+      this.startCountdowns();
+
+      console.log(`✅ 成功加载 ${this.tasks.length} 个真实任务`);
+    } catch (error) {
+      console.error('❌ 加载真实任务失败:', error);
+      throw error;
+    }
+  }
+  
+  /**
+   * 加载真实待处理反馈
+   */
+  private async loadRealPendingFeedbacks(): Promise<void> {
+    try {
+      const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
+      
+      // 查询该设计师相关项目的待处理反馈
+      const projectIds = this.tasks.map(t => t.projectId);
+      
+      const query = new Parse.Query('ProjectFeedback');
+      query.containedIn('project', projectIds.map(id => 
+        Parse.Object.extend('Project').createWithoutData(id)
+      ));
+      query.equalTo('status', '待处理');
+      query.notEqualTo('isDeleted', true);
+      query.include('project');
+      query.include('contact');
+      query.descending('createdAt');
+      query.limit(100);
+      
+      const feedbacks = await query.find();
+      
+      this.pendingFeedbacks = feedbacks.map((feedback: any) => {
+        const project = feedback.get('project');
+        const task = this.tasks.find(t => t.projectId === project?.id);
+        
+        return {
+          task: task || {
+            id: project?.id || '',
+            projectId: project?.id || '',
+            name: project?.get('title') || '未知项目',
+            stage: '反馈处理',
+            deadline: new Date(),
+            isOverdue: false
+          },
+          feedback: {
+            id: feedback.id,
+            content: feedback.get('content') || '',
+            rating: feedback.get('rating'),
+            createdAt: feedback.get('createdAt')
+          }
+        };
+      });
+      
+      console.log(`✅ 成功加载 ${this.pendingFeedbacks.length} 个待处理反馈`);
+    } catch (error) {
+      console.error('❌ 加载待处理反馈失败:', error);
+      this.pendingFeedbacks = [];
+    }
+  }
+}
+```
+
+### 2.3 请假申请功能实现
+
+#### 创建LeaveService(设计师端使用)
+
+```typescript
+// src/app/services/leave.service.ts
+import { Injectable } from '@angular/core';
+import { FmodeParse } from 'fmode-ng/parse';
+
+export interface LeaveApplication {
+  id?: string;
+  startDate: Date;
+  endDate: Date;
+  type: 'annual' | 'sick' | 'personal' | 'other';
+  reason: string;
+  status: 'pending' | 'approved' | 'rejected';
+  days: number;
+  createdAt?: Date;
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class LeaveService {
+  private Parse: any = null;
+  private cid: string = '';
+
+  constructor() {
+    this.initParse();
+  }
+
+  private async initParse(): Promise<void> {
+    try {
+      const { FmodeParse } = await import('fmode-ng/parse');
+      this.Parse = FmodeParse.with('nova');
+      this.cid = localStorage.getItem('company') || '';
+    } catch (error) {
+      console.error('LeaveService: Parse初始化失败:', error);
+    }
+  }
+
+  /**
+   * 提交请假申请
+   */
+  async submitLeaveApplication(
+    designerId: string,
+    application: Omit<LeaveApplication, 'id' | 'status' | 'createdAt'>
+  ): Promise<boolean> {
+    if (!this.Parse) await this.initParse();
+    if (!this.Parse || !this.cid) return false;
+
+    try {
+      // 方案1:添加到Profile.data.leave.records
+      const query = new this.Parse.Query('Profile');
+      const profile = await query.get(designerId);
+
+      const data = profile.get('data') || {};
+      const leaveData = data.leave || { records: [], statistics: {} };
+      const records = leaveData.records || [];
+
+      // 生成请假日期列表
+      const leaveDates: string[] = [];
+      const currentDate = new Date(application.startDate);
+      const endDate = new Date(application.endDate);
+      
+      while (currentDate <= endDate) {
+        leaveDates.push(currentDate.toISOString().split('T')[0]);
+        currentDate.setDate(currentDate.getDate() + 1);
+      }
+
+      // 添加每一天的请假记录
+      leaveDates.forEach(date => {
+        records.push({
+          id: `leave-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
+          date,
+          type: application.type,
+          status: 'pending', // 待审批
+          reason: application.reason,
+          createdAt: new Date().toISOString(),
+          days: application.days
+        });
+      });
+
+      leaveData.records = records;
+      data.leave = leaveData;
+      profile.set('data', data);
+
+      await profile.save();
+
+      console.log('✅ 请假申请提交成功');
+      return true;
+
+    } catch (error) {
+      console.error('❌ 提交请假申请失败:', error);
+      return false;
+    }
+  }
+
+  /**
+   * 获取我的请假记录
+   */
+  async getMyLeaveRecords(designerId: string): Promise<LeaveApplication[]> {
+    if (!this.Parse) await this.initParse();
+    if (!this.Parse) return [];
+
+    try {
+      const query = new this.Parse.Query('Profile');
+      const profile = await query.get(designerId);
+
+      const data = profile.get('data') || {};
+      const leaveData = data.leave || { records: [] };
+      const records = leaveData.records || [];
+
+      // 按日期聚合成请假申请
+      const applications = new Map<string, LeaveApplication>();
+
+      records.forEach((record: any) => {
+        const key = `${record.type}-${record.reason}-${record.status}`;
+        
+        if (!applications.has(key)) {
+          applications.set(key, {
+            id: record.id,
+            startDate: new Date(record.date),
+            endDate: new Date(record.date),
+            type: record.type,
+            reason: record.reason,
+            status: record.status,
+            days: 1,
+            createdAt: new Date(record.createdAt)
+          });
+        } else {
+          const app = applications.get(key)!;
+          const recordDate = new Date(record.date);
+          
+          if (recordDate < app.startDate) {
+            app.startDate = recordDate;
+          }
+          if (recordDate > app.endDate) {
+            app.endDate = recordDate;
+          }
+          app.days++;
+        }
+      });
+
+      return Array.from(applications.values())
+        .sort((a, b) => b.createdAt!.getTime() - a.createdAt!.getTime());
+
+    } catch (error) {
+      console.error('❌ 获取请假记录失败:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 计算请假天数(排除周末)
+   */
+  calculateLeaveDays(startDate: Date, endDate: Date): number {
+    let days = 0;
+    const currentDate = new Date(startDate);
+
+    while (currentDate <= endDate) {
+      const dayOfWeek = currentDate.getDay();
+      // 排除周六(6)和周日(0)
+      if (dayOfWeek !== 0 && dayOfWeek !== 6) {
+        days++;
+      }
+      currentDate.setDate(currentDate.getDate() + 1);
+    }
+
+    return days;
+  }
+}
+```
+
+#### 在Dashboard中添加请假申请功能
+
+```typescript
+// src/app/pages/designer/dashboard/dashboard.ts
+import { LeaveService, LeaveApplication } from '../../../services/leave.service';
+
+export class Dashboard implements OnInit {
+  // 请假相关
+  showLeaveModal: boolean = false;
+  leaveApplications: LeaveApplication[] = [];
+  
+  // 请假表单
+  leaveForm = {
+    startDate: '',
+    endDate: '',
+    type: 'personal' as 'annual' | 'sick' | 'personal' | 'other',
+    reason: ''
+  };
+
+  constructor(
+    private projectService: ProjectService,
+    private route: ActivatedRoute,
+    private router: Router,
+    private profileService: ProfileService,
+    private taskService: DesignerTaskService,
+    private leaveService: LeaveService  // 新增
+  ) {}
+
+  /**
+   * 打开请假申请弹窗
+   */
+  openLeaveModal(): void {
+    this.showLeaveModal = true;
+  }
+
+  /**
+   * 关闭请假申请弹窗
+   */
+  closeLeaveModal(): void {
+    this.showLeaveModal = false;
+    this.resetLeaveForm();
+  }
+
+  /**
+   * 提交请假申请
+   */
+  async submitLeaveApplication(): Promise<void> {
+    if (!this.currentProfile) {
+      alert('未找到当前用户信息');
+      return;
+    }
+
+    // 验证表单
+    if (!this.leaveForm.startDate || !this.leaveForm.endDate) {
+      alert('请选择请假日期');
+      return;
+    }
+
+    if (!this.leaveForm.reason.trim()) {
+      alert('请输入请假原因');
+      return;
+    }
+
+    const startDate = new Date(this.leaveForm.startDate);
+    const endDate = new Date(this.leaveForm.endDate);
+
+    if (startDate > endDate) {
+      alert('结束日期不能早于开始日期');
+      return;
+    }
+
+    // 计算请假天数
+    const days = this.leaveService.calculateLeaveDays(startDate, endDate);
+
+    if (days === 0) {
+      alert('请假天数必须大于0(周末不计入)');
+      return;
+    }
+
+    try {
+      const success = await this.leaveService.submitLeaveApplication(
+        this.currentProfile.id,
+        {
+          startDate,
+          endDate,
+          type: this.leaveForm.type,
+          reason: this.leaveForm.reason,
+          days
+        }
+      );
+
+      if (success) {
+        alert(`请假申请已提交!共${days}天(已排除周末)`);
+        this.closeLeaveModal();
+        await this.loadMyLeaveRecords();
+      } else {
+        alert('请假申请提交失败,请重试');
+      }
+    } catch (error) {
+      console.error('提交请假申请失败:', error);
+      alert('请假申请提交失败,请重试');
+    }
+  }
+
+  /**
+   * 加载我的请假记录
+   */
+  private async loadMyLeaveRecords(): Promise<void> {
+    if (!this.currentProfile) return;
+
+    try {
+      this.leaveApplications = await this.leaveService.getMyLeaveRecords(
+        this.currentProfile.id
+      );
+      console.log(`✅ 成功加载 ${this.leaveApplications.length} 条请假记录`);
+    } catch (error) {
+      console.error('❌ 加载请假记录失败:', error);
+    }
+  }
+
+  /**
+   * 重置请假表单
+   */
+  private resetLeaveForm(): void {
+    this.leaveForm = {
+      startDate: '',
+      endDate: '',
+      type: 'personal',
+      reason: ''
+    };
+  }
+}
+```
+
+#### 请假申请UI(添加到dashboard.html)
+
+```html
+<!-- 请假申请按钮 -->
+<button class="leave-btn" (click)="openLeaveModal()">
+  <svg viewBox="0 0 24 24">
+    <path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V9h14v11z"/>
+  </svg>
+  申请请假
+</button>
+
+<!-- 请假申请弹窗 -->
+@if (showLeaveModal) {
+  <div class="modal-overlay" (click)="closeLeaveModal()">
+    <div class="modal-content leave-modal" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>申请请假</h3>
+        <button class="close-btn" (click)="closeLeaveModal()">×</button>
+      </div>
+      
+      <div class="modal-body">
+        <div class="form-group">
+          <label>请假类型</label>
+          <select [(ngModel)]="leaveForm.type" class="form-control">
+            <option value="annual">年假</option>
+            <option value="sick">病假</option>
+            <option value="personal">事假</option>
+            <option value="other">其他</option>
+          </select>
+        </div>
+        
+        <div class="form-group">
+          <label>开始日期</label>
+          <input 
+            type="date" 
+            [(ngModel)]="leaveForm.startDate" 
+            class="form-control"
+            [min]="today"
+          >
+        </div>
+        
+        <div class="form-group">
+          <label>结束日期</label>
+          <input 
+            type="date" 
+            [(ngModel)]="leaveForm.endDate" 
+            class="form-control"
+            [min]="leaveForm.startDate || today"
+          >
+        </div>
+        
+        <div class="form-group">
+          <label>请假原因</label>
+          <textarea 
+            [(ngModel)]="leaveForm.reason" 
+            class="form-control" 
+            rows="4"
+            placeholder="请输入请假原因..."
+          ></textarea>
+        </div>
+      </div>
+      
+      <div class="modal-footer">
+        <button class="btn btn-cancel" (click)="closeLeaveModal()">取消</button>
+        <button class="btn btn-submit" (click)="submitLeaveApplication()">提交申请</button>
+      </div>
+    </div>
+  </div>
+}
+
+<!-- 我的请假记录 -->
+<div class="leave-records">
+  <h4>我的请假记录</h4>
+  @if (leaveApplications.length > 0) {
+    <div class="records-list">
+      @for (leave of leaveApplications; track leave.id) {
+        <div class="record-item" [class]="leave.status">
+          <div class="record-header">
+            <span class="record-type">{{ getLeaveTypeText(leave.type) }}</span>
+            <span class="record-status" [class]="leave.status">
+              {{ getLeaveStatusText(leave.status) }}
+            </span>
+          </div>
+          <div class="record-body">
+            <p class="record-date">
+              {{ leave.startDate | date:'yyyy-MM-dd' }} 至 {{ leave.endDate | date:'yyyy-MM-dd' }}
+              (共{{ leave.days }}天)
+            </p>
+            <p class="record-reason">{{ leave.reason }}</p>
+          </div>
+          <div class="record-footer">
+            <span class="record-time">{{ leave.createdAt | date:'yyyy-MM-dd HH:mm' }}</span>
+          </div>
+        </div>
+      }
+    </div>
+  } @else {
+    <p class="no-records">暂无请假记录</p>
+  }
+</div>
+```
+
+### 2.4 个人数据(技能标签、绩效)真实接入
+
+#### 从Profile.data读取真实技能标签
+
+```typescript
+// src/app/pages/designer/personal-board/personal-board.ts
+export class PersonalBoard implements OnInit {
+  private currentProfile: FmodeObject | null = null;
+
+  async ngOnInit(): Promise<void> {
+    // 获取当前Profile
+    const profileId = localStorage.getItem('Parse/ProfileId');
+    if (profileId) {
+      await this.loadCurrentProfile(profileId);
+      await this.loadRealSkillTags();
+      await this.loadRealPerformanceData();
+    } else {
+      // 降级到模拟数据
+      this.loadSkillTags();
+      this.loadPerformanceData();
+    }
+  }
+
+  /**
+   * 加载当前Profile
+   */
+  private async loadCurrentProfile(profileId: string): Promise<void> {
+    try {
+      const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
+      const query = new Parse.Query('Profile');
+      this.currentProfile = await query.get(profileId);
+      console.log('✅ 成功加载Profile');
+    } catch (error) {
+      console.error('❌ 加载Profile失败:', error);
+    }
+  }
+
+  /**
+   * 从Profile.data.tags读取真实技能标签
+   */
+  private async loadRealSkillTags(): Promise<void> {
+    if (!this.currentProfile) return;
+
+    try {
+      const data = this.currentProfile.get('data') || {};
+      const tags = data.tags || {};
+      const expertise = tags.expertise || {};
+      
+      // 转换为组件所需格式
+      this.skillTags = [];
+      
+      // 添加擅长风格
+      (expertise.styles || []).forEach((style: string) => {
+        this.skillTags.push({
+          name: style,
+          level: 85, // 可以从数据中读取实际等级
+          category: '风格'
+        });
+      });
+      
+      // 添加专业技能
+      (expertise.skills || []).forEach((skill: string) => {
+        this.skillTags.push({
+          name: skill,
+          level: 80,
+          category: '技能'
+        });
+      });
+      
+      // 添加擅长空间
+      (expertise.spaceTypes || []).forEach((space: string) => {
+        this.skillTags.push({
+          name: space,
+          level: 75,
+          category: '空间'
+        });
+      });
+      
+      console.log(`✅ 成功加载 ${this.skillTags.length} 个技能标签`);
+    } catch (error) {
+      console.error('❌ 加载技能标签失败:', error);
+    }
+  }
+
+  /**
+   * 从Profile.data.tags.history读取真实绩效数据
+   */
+  private async loadRealPerformanceData(): Promise<void> {
+    if (!this.currentProfile) return;
+
+    try {
+      const data = this.currentProfile.get('data') || {};
+      const tags = data.tags || {};
+      const history = tags.history || {};
+      
+      this.performanceData = {
+        totalProjects: history.totalProjects || 0,
+        completionRate: history.completionRate || 0,
+        onTimeRate: history.onTimeRate || 0,
+        excellentRate: history.excellentCount 
+          ? (history.excellentCount / history.totalProjects * 100) 
+          : 0,
+        avgRating: history.avgRating || 0
+      };
+      
+      console.log('✅ 成功加载绩效数据');
+    } catch (error) {
+      console.error('❌ 加载绩效数据失败:', error);
+    }
+  }
+}
+```
+
+---
+
+## 三、实施步骤
+
+### 阶段1:优化企微认证(第1天上午)
+
+**任务清单**:
+- [ ] 修改路由配置,添加 `:cid` 参数
+- [ ] 优化Dashboard的 `initAuth()` 方法,支持动态cid
+- [ ] 实现 `validateDesignerRole()` 角色验证
+- [ ] 测试企微授权流程
+
+**验收标准**:
+- ✅ 访问设计师端自动触发企微授权
+- ✅ 非组员角色无法访问
+- ✅ 可以正确获取当前Profile信息
+
+### 阶段2:任务数据真实接入(第1天下午)
+
+**任务清单**:
+- [ ] 创建 `DesignerTaskService`
+- [ ] 实现 `getMyTasks()` 方法
+- [ ] 修改Dashboard的 `loadRealTasks()` 方法
+- [ ] 实现 `loadRealPendingFeedbacks()` 方法
+- [ ] 测试任务数据加载
+
+**验收标准**:
+- ✅ 可以看到真实分配的任务
+- ✅ 超期任务、紧急任务正确标识
+- ✅ 待处理反馈正确显示
+
+### 阶段3:请假申请功能(第2天上午)
+
+**任务清单**:
+- [ ] 创建 `LeaveService`
+- [ ] 实现请假申请表单UI
+- [ ] 实现 `submitLeaveApplication()` 方法
+- [ ] 实现请假记录查看功能
+- [ ] 测试请假申请流程
+
+**验收标准**:
+- ✅ 设计师可以提交请假申请
+- ✅ 请假记录正确保存
+- ✅ 可以查看自己的请假历史
+
+### 阶段4:个人数据真实接入(第2天下午)
+
+**任务清单**:
+- [ ] 修改PersonalBoard的数据加载逻辑
+- [ ] 实现 `loadRealSkillTags()` 方法
+- [ ] 实现 `loadRealPerformanceData()` 方法
+- [ ] 测试个人看板数据显示
+
+**验收标准**:
+- ✅ 技能标签从Profile.data读取
+- ✅ 绩效数据正确显示
+- ✅ 数据更新后立即反映
+
+### 阶段5:测试与优化(第3天)
+
+**任务清单**:
+- [ ] 全流程测试
+- [ ] 性能优化(缓存、懒加载)
+- [ ] UI/UX优化
+- [ ] 错误处理完善
+- [ ] 编写使用文档
+
+---
+
+## 四、数据初始化
+
+### 为现有设计师初始化数据
+
+```typescript
+// scripts/init-designer-data.ts
+import { FmodeParse } from 'fmode-ng/parse';
+
+async function initDesignerData() {
+  const Parse = FmodeParse.with('nova');
+  const cid = 'cDL6R1hgSi'; // 公司ID
+
+  // 查询所有组员
+  const query = new Parse.Query('Profile');
+  query.equalTo('company', cid);
+  query.equalTo('roleName', '组员');
+  query.notEqualTo('isDeleted', true);
+
+  const designers = await query.find();
+
+  for (const designer of designers) {
+    const data = designer.get('data') || {};
+
+    // 初始化tags结构(如果不存在)
+    if (!data.tags) {
+      data.tags = {
+        expertise: {
+          styles: ['现代简约', '北欧风格'],
+          skills: ['3D建模', '效果图渲染'],
+          spaceTypes: ['客厅', '卧室']
+        },
+        capacity: {
+          weeklyProjects: 3,
+          maxConcurrent: 5,
+          avgDaysPerProject: 10
+        },
+        emergency: {
+          willing: false,
+          premium: 0,
+          maxPerWeek: 0
+        },
+        history: {
+          totalProjects: 0,
+          completionRate: 0,
+          avgRating: 0,
+          onTimeRate: 0,
+          excellentCount: 0
+        },
+        portfolio: []
+      };
+    }
+
+    // 初始化leave结构(如果不存在)
+    if (!data.leave) {
+      data.leave = {
+        records: [],
+        statistics: {
+          annualTotal: 10,
+          annualUsed: 0,
+          sickUsed: 0,
+          personalUsed: 0
+        }
+      };
+    }
+
+    designer.set('data', data);
+    await designer.save();
+
+    console.log(`✅ 为 ${designer.get('name')} 初始化数据`);
+  }
+
+  console.log('✅ 所有设计师数据初始化完成');
+}
+
+// 执行
+initDesignerData().catch(console.error);
+```
+
+---
+
+## 五、关键技术要点
+
+### 5.1 Parse查询优化
+
+```typescript
+// 1. 批量查询减少请求
+await Promise.all([
+  query1.find(),
+  query2.find(),
+  query3.find()
+]);
+
+// 2. 使用include减少请求次数
+query.include('project', 'project.contact', 'profile');
+
+// 3. 限制返回字段
+query.select('title', 'deadline', 'status');
+
+// 4. 添加索引字段
+query.equalTo('status', 'in_progress'); // status字段需要索引
+```
+
+### 5.2 错误处理
+
+```typescript
+// 统一错误处理
+try {
+  const tasks = await this.taskService.getMyTasks(designerId);
+  // 处理成功逻辑
+} catch (error) {
+  console.error('加载任务失败:', error);
+  // 降级到模拟数据或提示用户
+  this.showErrorMessage('加载任务失败,请刷新重试');
+}
+```
+
+### 5.3 数据缓存
+
+```typescript
+// 缓存Profile信息
+if (profile?.id) {
+  localStorage.setItem('Parse/ProfileId', profile.id);
+  // 可以考虑缓存整个profile数据
+  localStorage.setItem('Parse/ProfileData', JSON.stringify(profile.toJSON()));
+}
+
+// 读取缓存
+const cachedProfileData = localStorage.getItem('Parse/ProfileData');
+if (cachedProfileData) {
+  this.currentProfile = JSON.parse(cachedProfileData);
+}
+```
+
+---
+
+## 六、测试方案
+
+### 6.1 企微认证测试
+
+| 场景 | 操作 | 预期结果 |
+|------|------|----------|
+| 首次访问 | 访问设计师端 | 跳转企微授权 |
+| 授权成功 | 完成企微授权 | 自动登录并进入工作台 |
+| 角色不匹配 | 用其他角色访问 | 提示"您不是设计师" |
+| 已登录 | 再次访问 | 直接进入(无需重复授权) |
+
+### 6.2 任务数据测试
+
+| 场景 | 操作 | 预期结果 |
+|------|------|----------|
+| 有任务 | 加载工作台 | 显示所有分配的任务 |
+| 无任务 | 加载工作台 | 显示"暂无任务" |
+| 超期任务 | 查看任务列表 | 超期任务标红显示 |
+| 紧急任务 | 查看任务列表 | 紧急任务优先显示 |
+
+### 6.3 请假申请测试
+
+| 场景 | 操作 | 预期结果 |
+|------|------|----------|
+| 提交申请 | 填写并提交 | 成功提示,数据保存 |
+| 查看记录 | 打开请假记录 | 显示所有历史申请 |
+| 日期验证 | 选择错误日期 | 提示错误并阻止提交 |
+
+---
+
+## 七、FAQ
+
+### Q1: 为什么要优化企微认证?
+
+**A**: 虽然设计师端已有企微认证,但:
+- cid硬编码,无法支持多公司
+- 未验证"组员"角色
+- 未缓存Profile信息
+
+### Q2: 任务数据从哪个表查询?
+
+**A**: 推荐使用 `ProjectTeam` 表,因为:
+- ✅ 更准确:反映实际执行人
+- ✅ 支持多人协作
+- ✅ 可按Product(空间)粒度管理
+
+降级方案:从 `Project.assignee` 查询
+
+### Q3: 请假数据存在哪里?
+
+**A**: 推荐存在 `Profile.data.leave`:
+- ✅ 实施快速
+- ✅ 无需新建表
+- ✅ 查询简单
+
+如需复杂审批流程,可创建独立的 `Leave` 表
+
+### Q4: 如何处理周末请假?
+
+**A**: 使用 `calculateLeaveDays()` 方法自动排除周末:
+```typescript
+calculateLeaveDays(startDate, endDate); // 自动排除周六日
+```
+
+---
+
+## 八、后续优化建议
+
+### 8.1 短期(1-2周)
+
+1. **工作负载可视化**
+   - 添加个人工作量甘特图
+   - 显示任务优先级排序
+   - 支持任务拖拽调整
+
+2. **协作功能**
+   - 支持任务转交
+   - 支持请求协助
+   - 团队消息通知
+
+3. **移动端优化**
+   - 响应式布局优化
+   - 手势操作支持
+   - 离线数据缓存
+
+### 8.2 长期(1-3月)
+
+1. **智能助手**
+   - AI推荐最优任务顺序
+   - 自动预警风险任务
+   - 智能工作量评估
+
+2. **成长体系**
+   - 技能成长追踪
+   - 绩效趋势分析
+   - 个人成就系统
+
+3. **系统集成**
+   - 对接项目管理系统
+   - 对接设计软件(3DMax、Photoshop)
+   - 对接企业微信群聊
+
+---
+
+## 九、参考文档
+
+### 9.1 内部文档
+- `rules/wxwork/auth.md` - 企微认证API
+- `rules/schemas.md` - 数据表结构
+- `src/app/services/project.service.ts` - 原有服务参考
+
+### 9.2 已实现功能参考
+- 设计师端认证:`src/app/pages/designer/dashboard/dashboard.ts`
+- 组长端数据服务:`src/app/pages/team-leader/services/designer.service.ts`
+
+---
+
+## 十、总结
+
+本方案通过以下步骤实现设计师端的完整功能:
+
+1. **优化企微认证**:支持动态cid、角色验证、Profile缓存
+2. **任务数据接入**:创建DesignerTaskService,从ProjectTeam/Product表查询真实任务
+3. **请假申请功能**:创建LeaveService,支持申请和查看请假记录
+4. **个人数据接入**:从Profile.data读取技能标签和绩效数据
+
+**预计工作量**:3个工作日  
+**实施难度**:中等  
+**风险等级**:低
+
+实施完成后,设计师端将具备:
+- ✅ 完整的企微身份识别
+- ✅ 真实的任务数据展示
+- ✅ 便捷的请假申请流程
+- ✅ 准确的个人数据展示
+
+为设计师提供高效、智能的工作管理平台!
+
+---
+
+**文档版本**:v1.0  
+**创建日期**:2024-12-24  
+**最后更新**:2024-12-24  
+**维护人**:开发团队
+

+ 327 - 0
docs/team-leader-user-avatar-implementation.md

@@ -0,0 +1,327 @@
+# 组长端用户头像显示功能 - 实施总结
+
+## 📋 实施概述
+
+本文档记录了组长端(Team Leader Dashboard)用户头像显示功能的完整实施过程。该功能在顶部导航栏展示企业微信认证后的用户信息,包括头像、姓名和角色。
+
+## ✅ 实施完成情况
+
+### 1. TypeScript逻辑实现 (`dashboard.ts`)
+
+**添加的属性:**
+```typescript
+// 当前用户信息
+currentUser = {
+  name: '组长',
+  avatar: "data:image/svg+xml,%3Csvg...", // 默认SVG头像
+  roleName: '组长'
+};
+currentDate = new Date();
+```
+
+**添加的方法:**
+
+#### `loadUserProfile()` - 加载用户Profile
+- 从 `localStorage` 获取公司ID (`cid`)
+- 使用 `WxworkAuth` 实例化并调用 `currentProfile()`
+- 提取 `name`、`avatar`、`roleName` 字段
+- 如果头像不存在,使用 `generateDefaultAvatar()` 生成默认头像
+- 包含完善的错误处理机制
+
+#### `generateDefaultAvatar()` - 生成默认头像
+- 接收用户名作为参数
+- 生成包含用户名前2个字符的SVG头像
+- 返回Base64编码的 `data:image/svg+xml` URL
+
+**集成到生命周期:**
+- 在 `ngOnInit()` 中最先调用 `await this.loadUserProfile()`
+- 确保用户信息在页面渲染前加载完成
+
+**导入依赖:**
+```typescript
+import { WxworkAuth } from 'fmode-ng/core';
+```
+
+### 2. HTML模板实现 (`dashboard.html`)
+
+**新增顶部导航栏结构:**
+```html
+<nav class="top-navbar">
+  <div class="navbar-left">
+    <h2 class="navbar-title">设计组长工作台</h2>
+  </div>
+  <div class="navbar-right">
+    <!-- 日期显示 -->
+    <div class="date-display">
+      {{ currentDate.toLocaleDateString('zh-CN', { 
+        year: 'numeric', 
+        month: 'long', 
+        day: 'numeric', 
+        weekday: 'long' 
+      }) }}
+    </div>
+    <!-- 用户信息 -->
+    <div class="user-profile">
+      <img [src]="currentUser.avatar" 
+           [alt]="currentUser.name + '头像'" 
+           class="user-avatar">
+      <span class="user-name">{{ currentUser.name }}</span>
+      <span class="user-role">{{ currentUser.roleName }}</span>
+    </div>
+  </div>
+</nav>
+```
+
+**位置:** 在 `<header class="dashboard-header">` 之前
+
+### 3. CSS样式实现 (`dashboard.scss`)
+
+**顶部导航栏样式:**
+- **定位:** `position: sticky; top: 0; z-index: 1000` - 固定在顶部
+- **背景:** 紫色渐变 `linear-gradient(135deg, #667eea 0%, #764ba2 100%)`
+- **布局:** Flexbox,两端对齐
+- **阴影:** `box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1)`
+
+**用户信息区域样式:**
+- **容器:** 半透明白色背景,毛玻璃效果 (`backdrop-filter: blur(10px)`)
+- **头像:** 圆形 40x40px,白色边框,阴影
+- **姓名:** 白色粗体文字
+- **角色标签:** 半透明白色胶囊形状
+- **交互:** Hover时背景加深
+
+**适配原有布局:**
+- 使用负边距 `margin: calc(-1 * ios.$ios-spacing-lg)` 抵消 `:host` 的padding
+- 确保导航栏完全贴合页面边缘
+
+## 🎯 功能特性
+
+### 1. 企业微信认证集成
+- ✅ 使用 `WxworkAuth` 获取当前用户Profile
+- ✅ 从 `localStorage` 读取 `company` ID
+- ✅ 支持企业微信头像自动加载
+
+### 2. 默认头像生成
+- ✅ SVG格式,轻量级
+- ✅ 显示用户名前2个字符
+- ✅ 浅绿色背景 + 深灰色文字
+- ✅ 无需网络请求
+
+### 3. 用户体验优化
+- ✅ 响应式设计
+- ✅ 优雅的Hover效果
+- ✅ 日期实时显示(中文格式)
+- ✅ 角色信息清晰展示
+
+### 4. 错误处理
+- ✅ `cid` 不存在时使用默认信息
+- ✅ Profile加载失败时保持默认值
+- ✅ Console输出日志便于调试
+
+## 📊 实施结果
+
+### 代码质量
+- ✅ **Linter检查:** 无错误
+- ✅ **TypeScript类型:** 严格类型检查通过
+- ✅ **代码规范:** 符合Angular最佳实践
+
+### 文件修改清单
+| 文件路径 | 修改内容 | 行数变化 |
+|---------|---------|---------|
+| `src/app/pages/team-leader/dashboard/dashboard.ts` | 添加用户信息属性、加载方法、头像生成方法 | +50 |
+| `src/app/pages/team-leader/dashboard/dashboard.html` | 添加顶部导航栏结构 | +16 |
+| `src/app/pages/team-leader/dashboard/dashboard.scss` | 添加顶部导航栏样式 | +73 |
+
+### 关键代码片段
+
+#### 数据流程图
+```
+localStorage.getItem("company") 
+  ↓
+WxworkAuth({ cid })
+  ↓
+currentProfile()
+  ↓
+profile.get("name" / "avatar" / "roleName")
+  ↓
+currentUser { name, avatar, roleName }
+  ↓
+HTML模板绑定显示
+```
+
+#### 企业微信认证核心逻辑
+```typescript
+const cid = localStorage.getItem("company");
+if (!cid) {
+  console.warn('未找到公司ID,使用默认用户信息');
+  return;
+}
+
+const wwAuth = new WxworkAuth({ cid });
+const profile = await wwAuth.currentProfile();
+
+if (profile) {
+  const name = profile.get("name") || profile.get("mobile") || '组长';
+  const avatar = profile.get("avatar");
+  const roleName = profile.get("roleName") || '组长';
+
+  this.currentUser = {
+    name,
+    avatar: avatar || this.generateDefaultAvatar(name),
+    roleName
+  };
+}
+```
+
+## 🔄 数据来源说明
+
+### Parse Server数据表:`Profile`
+```javascript
+{
+  name: "张三",           // 用户姓名
+  mobile: "13800138000", // 手机号(备用)
+  avatar: "https://...", // 头像URL
+  roleName: "组长",       // 角色名称("组员" / "组长" / "管理员")
+  // ... 其他字段
+}
+```
+
+### WxworkAuth方法:`currentProfile()`
+- **返回值:** Parse.Object<Profile>
+- **获取方式:** `profile.get("fieldName")`
+- **自动同步:** 企业微信认证后自动创建/更新Profile
+
+## 🎨 UI设计说明
+
+### 视觉层级
+1. **顶层:** 紫色渐变导航栏(粘性定位)
+2. **次层:** 白色内容区域
+3. **交互层:** 用户信息卡片(毛玻璃效果)
+
+### 颜色方案
+- **主色:** `#667eea` → `#764ba2`(紫色渐变)
+- **文字:** `#ffffff`(白色)
+- **辅助色:** `rgba(255, 255, 255, 0.15-0.25)`(半透明白)
+- **边框:** `white`(头像边框)
+
+### 间距规范
+- **导航栏内边距:** `1rem 2rem`
+- **元素间距:** `0.75rem` - `2rem`
+- **头像尺寸:** `40px x 40px`
+
+## 🚀 使用说明
+
+### 前置条件
+1. 已配置企业微信应用
+2. 已实现 `WxworkAuthGuard` 路由守卫
+3. 用户已通过企业微信扫码登录
+4. `localStorage` 中存在 `company` 字段
+
+### 自动加载流程
+1. 用户访问 `/team-leader/dashboard`
+2. `WxworkAuthGuard` 验证身份
+3. `Dashboard.ngOnInit()` 触发
+4. `loadUserProfile()` 调用 `WxworkAuth.currentProfile()`
+5. 提取用户信息并更新 `currentUser`
+6. Angular数据绑定自动更新视图
+
+### 降级策略
+- **无 `cid`:** 显示默认"组长"信息
+- **Profile加载失败:** 保持默认值
+- **无头像:** 显示SVG默认头像
+
+## 🔍 测试建议
+
+### 功能测试
+- [ ] 企业微信认证后头像正常显示
+- [ ] 企业微信认证后姓名正常显示
+- [ ] 企业微信认证后角色正常显示
+- [ ] 日期格式正确(中文长格式)
+- [ ] 无认证时显示默认信息
+- [ ] 头像加载失败时显示默认头像
+
+### 样式测试
+- [ ] 导航栏固定在顶部不滚动
+- [ ] 渐变背景正常显示
+- [ ] 用户信息卡片Hover效果正常
+- [ ] 头像圆形显示且白色边框清晰
+- [ ] 响应式布局适配不同屏幕
+
+### 兼容性测试
+- [ ] Chrome/Edge/Safari浏览器正常
+- [ ] 移动端显示正常
+- [ ] 不同角色(组长/组员/管理员)显示正常
+
+## 📖 参考文档
+
+- **企业微信认证指南:** `rules/wxwork/auth.md`
+- **路由守卫指南:** `rules/wxwork/guard-wxwork.md`
+- **数据范式:** `docs/prd/wxwork-project-management.md`
+- **管理员端实现:** `src/app/pages/admin/admin-layout/`
+
+## 🎉 实施亮点
+
+1. **完全复用管理员端逻辑:** 与 `AdminLayout` 保持一致的实现方式
+2. **优雅的错误处理:** 多层级降级策略确保稳定性
+3. **美观的UI设计:** 渐变背景 + 毛玻璃效果
+4. **零Linter错误:** 代码质量高
+5. **完善的注释:** 便于后续维护
+
+## ⏭️ 后续优化建议
+
+1. **角色图标:** 可为不同角色添加不同颜色/图标
+2. **下拉菜单:** 点击用户信息显示退出/设置选项
+3. **通知中心:** 在用户信息旁添加消息提醒
+4. **实时更新:** 监听Profile变更自动刷新显示
+
+---
+
+## 🔧 优化记录
+
+### 2025-10-27 优化(用户反馈后)
+
+**问题反馈:**
+1. ❌ 页面中有两个"设计组长工作台"标题(重复)
+2. ❌ 导航栏高度过高
+3. ❌ 顶部有灰色空白区域,未完全贴顶
+
+**解决方案:**
+
+#### 1. 删除重复标题
+- **文件:** `dashboard.html`
+- **修改:** 删除 `<header>` 内的 `<h1>设计组长工作台</h1>`
+- **结果:** ✅ 只保留导航栏中的标题
+
+#### 2. 缩短导航栏高度
+- **文件:** `dashboard.scss`
+- **修改内容:**
+  - 导航栏 `padding: 1rem 2rem` → `0.625rem 2rem`(减少约38%)
+  - 标题字体 `1.5rem` → `1.25rem`
+  - 日期字体 `0.95rem` → `0.875rem`
+  - 头像尺寸 `40px` → `36px`
+  - 用户名字体 `1rem` → `0.9375rem`
+  - 角色标签字体 `0.875rem` → `0.8125rem`
+  - 元素间距 `2rem` → `1.5rem`
+  - 用户信息卡片 `padding: 0.5rem 1rem` → `0.375rem 0.875rem`
+- **结果:** ✅ 导航栏更加紧凑,视觉更协调
+
+#### 3. 修复顶部空白区域
+- **文件:** `dashboard.scss`
+- **修改内容:**
+  - 添加 `margin-top: calc(-1 * ios.$ios-spacing-lg - 1px)` 确保完全贴顶
+  - 优化 `:host` 的 padding 写法,分别指定各个方向
+- **结果:** ✅ 导航栏完全贴顶,无灰色空白
+
+#### 对比总结
+| 项目 | 优化前 | 优化后 | 改善 |
+|------|--------|--------|------|
+| 导航栏padding | 1rem | 0.625rem | ↓ 38% |
+| 标题字体 | 1.5rem | 1.25rem | ↓ 17% |
+| 头像尺寸 | 40px | 36px | ↓ 10% |
+| 顶部空白 | 有灰色区域 | 完全贴顶 | ✅ 修复 |
+| 标题重复 | 2个 | 1个 | ✅ 修复 |
+
+---
+
+**实施日期:** 2025-10-27  
+**实施人员:** AI Assistant  
+**审核状态:** ✅ 已完成并优化,待用户验收

+ 332 - 0
src/app/pages/team-leader/dashboard/dashboard-calendar.scss

@@ -0,0 +1,332 @@
+// 负载详细日历样式
+.calendar-section {
+  .employee-calendar {
+    background: #ffffff;
+    border-radius: 12px;
+    border: 1px solid #e2e8f0;
+    padding: 20px;
+    
+    .calendar-month-header {
+      text-align: center;
+      margin-bottom: 16px;
+      padding-bottom: 12px;
+      border-bottom: 2px solid #e2e8f0;
+      
+      .month-title {
+        font-size: 16px;
+        font-weight: 600;
+        color: #1e293b;
+      }
+    }
+    
+    .calendar-weekdays {
+      display: grid;
+      grid-template-columns: repeat(7, 1fr);
+      gap: 4px;
+      margin-bottom: 8px;
+      
+      .weekday {
+        text-align: center;
+        font-size: 13px;
+        font-weight: 600;
+        color: #64748b;
+        padding: 8px 0;
+      }
+    }
+    
+    .calendar-grid {
+      display: grid;
+      grid-template-columns: repeat(7, 1fr);
+      gap: 4px;
+      
+      .calendar-day {
+        aspect-ratio: 1;
+        border: 1px solid #e2e8f0;
+        border-radius: 8px;
+        padding: 6px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        gap: 4px;
+        position: relative;
+        transition: all 0.2s ease;
+        background: #ffffff;
+        
+        &.other-month {
+          opacity: 0.3;
+          pointer-events: none;
+        }
+        
+        &.today {
+          border-color: #667eea;
+          border-width: 2px;
+          box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+          
+          .day-number {
+            color: #667eea;
+            font-weight: 700;
+          }
+        }
+        
+        &.has-projects {
+          background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
+          border-color: #10b981;
+          
+          .day-badge.high-load {
+            background: linear-gradient(135deg, #fecaca 0%, #fca5a5 100%);
+            color: #dc2626;
+            font-weight: 600;
+          }
+        }
+        
+        &.clickable {
+          cursor: pointer;
+          
+          &:hover {
+            transform: scale(1.05);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+            z-index: 10;
+          }
+        }
+        
+        .day-number {
+          font-size: 14px;
+          font-weight: 600;
+          color: #1e293b;
+        }
+        
+        .day-badge {
+          font-size: 10px;
+          padding: 2px 6px;
+          border-radius: 999px;
+          background: #10b981;
+          color: white;
+          white-space: nowrap;
+        }
+      }
+    }
+    
+    .calendar-legend {
+      display: flex;
+      gap: 16px;
+      margin-top: 16px;
+      padding-top: 12px;
+      border-top: 1px solid #e2e8f0;
+      
+      .legend-item {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        
+        .legend-dot {
+          width: 12px;
+          height: 12px;
+          border-radius: 50%;
+          
+          &.today-dot {
+            border: 2px solid #667eea;
+          }
+          
+          &.project-dot {
+            background: #10b981;
+          }
+          
+          &.high-dot {
+            background: #dc2626;
+          }
+        }
+        
+        .legend-text {
+          font-size: 12px;
+          color: #64748b;
+        }
+      }
+    }
+  }
+}
+
+// 日历项目列表弹窗样式
+.calendar-project-modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  backdrop-filter: blur(4px);
+  z-index: 1001;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  animation: fadeIn 0.3s ease-out;
+}
+
+.calendar-project-modal {
+  background: #ffffff;
+  border-radius: 16px;
+  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
+  max-width: 500px;
+  width: 100%;
+  max-height: 80vh;
+  overflow: hidden;
+  animation: slideUp 0.3s ease-out;
+  
+  .modal-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 20px 24px;
+    border-bottom: 1px solid #e2e8f0;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    
+    h3 {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+      color: white;
+      
+      .header-icon {
+        width: 24px;
+        height: 24px;
+      }
+    }
+    
+    .btn-close {
+      background: rgba(255, 255, 255, 0.2);
+      border: none;
+      border-radius: 8px;
+      width: 36px;
+      height: 36px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      
+      svg {
+        width: 18px;
+        height: 18px;
+        stroke: white;
+      }
+      
+      &:hover {
+        background: rgba(255, 255, 255, 0.3);
+        transform: scale(1.05);
+      }
+    }
+  }
+  
+  .modal-body {
+    padding: 24px;
+    max-height: calc(80vh - 80px);
+    overflow-y: auto;
+    
+    .project-count-info {
+      font-size: 14px;
+      color: #64748b;
+      margin-bottom: 16px;
+      
+      strong {
+        color: #667eea;
+        font-size: 18px;
+      }
+    }
+    
+    .project-list {
+      display: flex;
+      flex-direction: column;
+      gap: 12px;
+      
+      .project-item {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 16px;
+        border: 1px solid #e2e8f0;
+        border-radius: 12px;
+        background: #ffffff;
+        cursor: pointer;
+        transition: all 0.2s ease;
+        
+        &:hover {
+          border-color: #667eea;
+          box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
+          transform: translateY(-2px);
+          
+          .arrow-icon {
+            transform: translateX(4px);
+          }
+        }
+        
+        .project-info {
+          display: flex;
+          align-items: center;
+          gap: 12px;
+          flex: 1;
+          
+          .project-icon {
+            width: 40px;
+            height: 40px;
+            padding: 8px;
+            border-radius: 8px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            stroke: white;
+            flex-shrink: 0;
+          }
+          
+          .project-details {
+            flex: 1;
+            min-width: 0;
+            
+            .project-name {
+              margin: 0 0 4px 0;
+              font-size: 15px;
+              font-weight: 600;
+              color: #1e293b;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+            }
+            
+            .project-deadline {
+              margin: 0;
+              font-size: 13px;
+              color: #64748b;
+            }
+          }
+        }
+        
+        .arrow-icon {
+          width: 20px;
+          height: 20px;
+          stroke: #94a3b8;
+          flex-shrink: 0;
+          transition: all 0.2s ease;
+        }
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .calendar-project-modal {
+    max-width: 100%;
+    
+    .modal-body .project-list .project-item {
+      padding: 12px;
+      
+      .project-info .project-icon {
+        width: 36px;
+        height: 36px;
+      }
+    }
+  }
+}
+
+

+ 140 - 1
src/app/pages/team-leader/dashboard/dashboard.html

@@ -1,5 +1,21 @@
+<!-- 顶部导航栏 -->
+<nav class="top-navbar">
+  <div class="navbar-left">
+    <h2 class="navbar-title">设计组长工作台</h2>
+  </div>
+  <div class="navbar-right">
+    <div class="date-display">
+      {{ currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }) }}
+    </div>
+    <div class="user-profile">
+      <img [src]="currentUser.avatar" [alt]="currentUser.name + '头像'" class="user-avatar">
+      <span class="user-name">{{ currentUser.name }}</span>
+      <span class="user-role">{{ currentUser.roleName }}</span>
+    </div>
+  </div>
+</nav>
+
 <header class="dashboard-header">
-  <h1>设计组长工作台</h1>
   <!-- 核心数据指标卡片(扩展为6个) -->
   <div class="dashboard-metrics">
     <div class="metric-card" (click)="filterByStatus('overdue')">
@@ -402,6 +418,76 @@
           </div>
         </div>
 
+        <!-- 负载详细日历 -->
+        <div class="section calendar-section">
+          <div class="section-header">
+            <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
+              <line x1="16" y1="2" x2="16" y2="6"></line>
+              <line x1="8" y1="2" x2="8" y2="6"></line>
+              <line x1="3" y1="10" x2="21" y2="10"></line>
+            </svg>
+            <h4>负载详细日历</h4>
+          </div>
+          
+          @if (selectedEmployeeDetail.calendarData) {
+            <div class="employee-calendar">
+              <!-- 月份标题 -->
+              <div class="calendar-month-header">
+                <span class="month-title">
+                  {{ selectedEmployeeDetail.calendarData.currentMonth | date:'yyyy年M月' }}
+                </span>
+              </div>
+              
+              <!-- 星期标题 -->
+              <div class="calendar-weekdays">
+                <div class="weekday">日</div>
+                <div class="weekday">一</div>
+                <div class="weekday">二</div>
+                <div class="weekday">三</div>
+                <div class="weekday">四</div>
+                <div class="weekday">五</div>
+                <div class="weekday">六</div>
+              </div>
+              
+              <!-- 日历网格 -->
+              <div class="calendar-grid">
+                @for (day of selectedEmployeeDetail.calendarData.days; track day.date.getTime()) {
+                  <div class="calendar-day"
+                       [class.today]="day.isToday"
+                       [class.other-month]="!day.isCurrentMonth"
+                       [class.has-projects]="day.projectCount > 0"
+                       [class.clickable]="day.projectCount > 0 && day.isCurrentMonth"
+                       (click)="onCalendarDayClick(day)">
+                    <div class="day-number">{{ day.date.getDate() }}</div>
+                    @if (day.projectCount > 0) {
+                      <div class="day-badge" [class.high-load]="day.projectCount >= 2">
+                        {{ day.projectCount }}个项目
+                      </div>
+                    }
+                  </div>
+                }
+              </div>
+              
+              <!-- 图例 -->
+              <div class="calendar-legend">
+                <div class="legend-item">
+                  <span class="legend-dot today-dot"></span>
+                  <span class="legend-text">今天</span>
+                </div>
+                <div class="legend-item">
+                  <span class="legend-dot project-dot"></span>
+                  <span class="legend-text">有项目</span>
+                </div>
+                <div class="legend-item">
+                  <span class="legend-dot high-dot"></span>
+                  <span class="legend-text">高负载</span>
+                </div>
+              </div>
+            </div>
+          }
+        </div>
+
         <!-- 请假明细栏 -->
         <div class="section leave-section">
           <div class="section-header">
@@ -470,6 +556,59 @@
   </div>
 }
 
+<!-- 日历项目列表弹窗 -->
+@if (showCalendarProjectList) {
+  <div class="calendar-project-modal-overlay" (click)="closeCalendarProjectList()">
+    <div class="calendar-project-modal" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>
+          <svg class="header-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <path d="M9 11l3 3L22 4"></path>
+            <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
+          </svg>
+          {{ selectedDate | date:'M月d日' }} 的项目
+        </h3>
+        <button class="btn-close" (click)="closeCalendarProjectList()">
+          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      
+      <div class="modal-body">
+        <div class="project-count-info">
+          共 <strong>{{ selectedDayProjects.length }}</strong> 个项目
+        </div>
+        
+        <div class="project-list">
+          @for (project of selectedDayProjects; track project.id) {
+            <div class="project-item" (click)="navigateToProjectFromPanel(project.id); closeCalendarProjectList()">
+              <div class="project-info">
+                <svg class="project-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
+                  <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
+                </svg>
+                <div class="project-details">
+                  <h4 class="project-name">{{ project.name }}</h4>
+                  @if (project.deadline) {
+                    <p class="project-deadline">
+                      截止日期: {{ project.deadline | date:'yyyy-MM-dd' }}
+                    </p>
+                  }
+                </div>
+              </div>
+              <svg class="arrow-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M5 12h14M12 5l7 7-7 7"/>
+              </svg>
+            </div>
+          }
+        </div>
+      </div>
+    </div>
+  </div>
+}
+
 <!-- 智能推荐弹窗 -->
 @if (showSmartMatch) {
   <div class="smart-match-modal">

+ 80 - 1
src/app/pages/team-leader/dashboard/dashboard.scss

@@ -4,12 +4,91 @@
 
 /* 新增优化样式 */
 @import './dashboard-new-styles.scss';
+@import './dashboard-calendar.scss';
+
+/* 顶部导航栏样式 */
+.top-navbar {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 1000;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 0.625rem 2rem; // 从1rem减少到0.625rem,降低高度
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+
+  .navbar-left {
+    .navbar-title {
+      font-size: 1.25rem; // 从1.5rem减小到1.25rem
+      font-weight: 600;
+      color: white;
+      margin: 0;
+    }
+  }
+
+  .navbar-right {
+    display: flex;
+    align-items: center;
+    gap: 1.5rem; // 从2rem减少到1.5rem
+
+    .date-display {
+      color: rgba(255, 255, 255, 0.9);
+      font-size: 0.875rem; // 从0.95rem减小到0.875rem
+      font-weight: 500;
+    }
+
+    .user-profile {
+      display: flex;
+      align-items: center;
+      gap: 0.625rem; // 从0.75rem减少到0.625rem
+      background: rgba(255, 255, 255, 0.15);
+      padding: 0.375rem 0.875rem; // 从0.5rem 1rem减少
+      border-radius: 50px;
+      backdrop-filter: blur(10px);
+      transition: background 0.3s ease;
+
+      &:hover {
+        background: rgba(255, 255, 255, 0.25);
+      }
+
+      .user-avatar {
+        width: 36px; // 从40px减小到36px
+        height: 36px;
+        border-radius: 50%;
+        object-fit: cover;
+        border: 2px solid white;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+      }
+
+      .user-name {
+        color: white;
+        font-weight: 600;
+        font-size: 0.9375rem; // 从1rem减小到0.9375rem
+      }
+
+      .user-role {
+        color: rgba(255, 255, 255, 0.8);
+        font-size: 0.8125rem; // 从0.875rem减小到0.8125rem
+        padding: 0.1875rem 0.625rem; // 从0.25rem 0.75rem减少
+        background: rgba(255, 255, 255, 0.2);
+        border-radius: 12px;
+      }
+    }
+  }
+}
 
 :host {
   display: block;
   background-color: ios.$ios-background-secondary;
   min-height: 100vh;
-  padding: ios.$ios-spacing-lg;
+  padding: 0; // 移除默认padding
+  padding-left: ios.$ios-spacing-lg;
+  padding-right: ios.$ios-spacing-lg;
+  padding-top: calc(70px + ios.$ios-spacing-lg); // 70px为导航栏高度,加上额外间距
+  padding-bottom: ios.$ios-spacing-lg;
   overflow-y: auto; /* 确保组件内容可以滚动 */
   max-height: 100vh; /* 限制最大高度为视口高度 */
   box-sizing: border-box; /* 确保padding计入高度 */

+ 281 - 7
src/app/pages/team-leader/dashboard/dashboard.ts

@@ -4,6 +4,7 @@ import { Router, RouterModule } from '@angular/router';
 import { Component, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
 import { ProjectService } from '../../../services/project.service';
 import { DesignerService } from '../services/designer.service';
+import { WxworkAuth } from 'fmode-ng/core';
 
 // 项目阶段定义
 interface ProjectStage {
@@ -71,6 +72,22 @@ interface EmployeeDetail {
   projectData: Array<{ id: string; name: string }>; // 项目数据(包含ID和名称,用于跳转)
   leaveRecords: LeaveRecord[]; // 未来7天请假记录
   redMarkExplanation: string; // 红色标记说明
+  calendarData?: EmployeeCalendarData; // 负载日历数据
+}
+
+// 员工日历数据接口
+interface EmployeeCalendarData {
+  currentMonth: Date;
+  days: EmployeeCalendarDay[];
+}
+
+// 日历日期数据
+interface EmployeeCalendarDay {
+  date: Date;
+  projectCount: number; // 当天项目数量
+  projects: Array<{ id: string; name: string; deadline?: Date }>; // 项目列表
+  isToday: boolean;
+  isCurrentMonth: boolean;
 }
 
 declare const echarts: any;
@@ -90,6 +107,14 @@ export class Dashboard implements OnInit, OnDestroy {
   showAlert: boolean = false;
   selectedProjectId: string = '';
   
+  // 新增:当前用户信息
+  currentUser = {
+    name: '组长',
+    avatar: "data:image/svg+xml,%3Csvg width='40' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23CCFFCC'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3E组长%3C/text%3E%3C/svg%3E",
+    roleName: '组长'
+  };
+  currentDate = new Date();
+  
   // 真实设计师数据(从fmode-ng获取)
   realDesigners: any[] = [];
   
@@ -165,6 +190,11 @@ export class Dashboard implements OnInit, OnDestroy {
   showEmployeeDetailPanel: boolean = false;
   selectedEmployeeDetail: EmployeeDetail | null = null;
   
+  // 日历项目列表弹窗
+  showCalendarProjectList: boolean = false;
+  selectedDayProjects: Array<{ id: string; name: string; deadline?: Date }> = [];
+  selectedDate: Date | null = null;
+  
   // 员工请假数据(模拟数据)
   private leaveRecords: LeaveRecord[] = [
     { id: '1', employeeName: '张三', date: '2024-01-20', isLeave: true, leaveType: 'personal', reason: '事假' },
@@ -206,6 +236,9 @@ export class Dashboard implements OnInit, OnDestroy {
   ) {}
 
   async ngOnInit(): Promise<void> {
+    // 新增:加载用户Profile信息
+    await this.loadUserProfile();
+    
     await this.loadProjects();
     await this.loadDesigners();
     this.loadTodoTasks();
@@ -284,14 +317,31 @@ export class Dashboard implements OnInit, OnDestroy {
         const profileName = profile.get('name') || profile.get('user')?.get?.('name') || `设计师-${profileId.slice(-4)}`;
         
         // 提取项目信息
+        // 优先获取各个日期字段
+        const createdAtValue = project.get('createdAt');
+        const updatedAtValue = project.get('updatedAt');
+        const deadlineValue = project.get('deadline');
+        const deliveryDateValue = project.get('deliveryDate');
+        const expectedDeliveryDateValue = project.get('expectedDeliveryDate');
+        
+        // 🔧 如果 get() 方法都返回假值,尝试从 createdAt/updatedAt 属性直接获取
+        // Parse 对象的 createdAt/updatedAt 是内置属性
+        let finalCreatedAt = createdAtValue || updatedAtValue;
+        if (!finalCreatedAt && project.createdAt) {
+          finalCreatedAt = project.createdAt; // Parse 内置属性
+        }
+        if (!finalCreatedAt && project.updatedAt) {
+          finalCreatedAt = project.updatedAt; // Parse 内置属性
+        }
+        
         const projectData = {
           id: project.id,
           name: project.get('title') || '未命名项目',
           status: project.get('status') || '进行中',
           currentStage: project.get('currentStage') || '未知阶段',
-          deadline: project.get('deadline'),
-          createdAt: project.get('createdAt'),
-          designerName: profileName // 设置为组员的名字
+          deadline: deadlineValue || deliveryDateValue || expectedDeliveryDateValue,
+          createdAt: finalCreatedAt,
+          designerName: profileName
         };
         
         // 添加到映射 (by ID)
@@ -347,13 +397,29 @@ export class Dashboard implements OnInit, OnDestroy {
         const assigneeName = assignee.get('name') || assignee.get('user')?.get?.('name') || `设计师-${assignee.id.slice(-4)}`;
         
         // 提取项目信息
+        // 优先获取各个日期字段
+        const createdAtValue = project.get('createdAt');
+        const updatedAtValue = project.get('updatedAt');
+        const deadlineValue = project.get('deadline');
+        const deliveryDateValue = project.get('deliveryDate');
+        const expectedDeliveryDateValue = project.get('expectedDeliveryDate');
+        
+        // 🔧 如果 get() 方法都返回假值,尝试从 createdAt/updatedAt 属性直接获取
+        let finalCreatedAt = createdAtValue || updatedAtValue;
+        if (!finalCreatedAt && project.createdAt) {
+          finalCreatedAt = project.createdAt;
+        }
+        if (!finalCreatedAt && project.updatedAt) {
+          finalCreatedAt = project.updatedAt;
+        }
+        
         const projectData = {
           id: project.id,
           name: project.get('title') || '未命名项目',
           status: project.get('status') || '进行中',
           currentStage: project.get('currentStage') || '未知阶段',
-          deadline: project.get('deadline'),
-          createdAt: project.get('createdAt'),
+          deadline: deadlineValue || deliveryDateValue || expectedDeliveryDateValue,
+          createdAt: finalCreatedAt,
           designerName: assigneeName
         };
         
@@ -2570,7 +2636,7 @@ export class Dashboard implements OnInit, OnDestroy {
 
   // 生成员工详情数据
   private generateEmployeeDetail(employeeName: string): EmployeeDetail {
-    // 🔧 从 ProjectTeam 表获取该员工负责的项目
+    // 从 ProjectTeam 表获取该员工负责的项目
     const employeeProjects = this.designerWorkloadMap.get(employeeName) || [];
     const currentProjects = employeeProjects.length;
     
@@ -2597,15 +2663,172 @@ export class Dashboard implements OnInit, OnDestroy {
     // 生成红色标记说明
     const redMarkExplanation = this.generateRedMarkExplanation(employeeName, employeeLeaveRecords, currentProjects);
     
+    // 生成日历数据
+    const calendarData = this.generateEmployeeCalendar(employeeName, employeeProjects);
+    
     return {
       name: employeeName,
       currentProjects,
       projectNames,
       projectData,
       leaveRecords: employeeLeaveRecords,
-      redMarkExplanation
+      redMarkExplanation,
+      calendarData
+    };
+  }
+  
+  /**
+   * 生成员工日历数据(当前月份)
+   */
+  private generateEmployeeCalendar(employeeName: string, employeeProjects: any[]): EmployeeCalendarData {
+    const currentMonth = new Date();
+    const year = currentMonth.getFullYear();
+    const month = currentMonth.getMonth();
+    
+    // 获取当月天数
+    const daysInMonth = new Date(year, month + 1, 0).getDate();
+    const days: EmployeeCalendarDay[] = [];
+    const today = new Date();
+    today.setHours(0, 0, 0, 0);
+    
+    // 生成当月每一天的数据
+    for (let day = 1; day <= daysInMonth; day++) {
+      const date = new Date(year, month, day);
+      const dateStr = date.toISOString().split('T')[0];
+      
+      // 找出该日期相关的项目(项目进行中且在当天范围内)
+      const dayProjects = employeeProjects.filter(p => {
+        // 处理 Parse Date 对象:检查是否有 toDate 方法
+        const getDate = (dateValue: any) => {
+          if (!dateValue) return null;
+          if (dateValue.toDate && typeof dateValue.toDate === 'function') {
+            return dateValue.toDate(); // Parse Date对象
+          }
+          if (dateValue instanceof Date) {
+            return dateValue;
+          }
+          return new Date(dateValue); // 字符串或时间戳
+        };
+        
+        const deadlineDate = getDate(p.deadline);
+        const createdDate = p.createdAt ? getDate(p.createdAt) : null;
+        
+        // 如果项目既没有 deadline 也没有 createdAt,则跳过
+        if (!deadlineDate && !createdDate) {
+          return false;
+        }
+        
+        // 智能处理日期范围
+        let startDate: Date;
+        let endDate: Date;
+        
+        if (deadlineDate && createdDate) {
+          // 情况1:两个日期都有
+          startDate = createdDate;
+          endDate = deadlineDate;
+        } else if (deadlineDate) {
+          // 情况2:只有deadline,往前推30天
+          startDate = new Date(deadlineDate.getTime() - 30 * 24 * 60 * 60 * 1000);
+          endDate = deadlineDate;
+        } else {
+          // 情况3:只有createdAt,往后推30天
+          startDate = createdDate!;
+          endDate = new Date(createdDate!.getTime() + 30 * 24 * 60 * 60 * 1000);
+        }
+        
+        startDate.setHours(0, 0, 0, 0);
+        endDate.setHours(0, 0, 0, 0);
+        
+        const inRange = date >= startDate && date <= endDate;
+        
+        return inRange;
+      }).map(p => {
+        const getDate = (dateValue: any) => {
+          if (!dateValue) return undefined;
+          if (dateValue.toDate && typeof dateValue.toDate === 'function') {
+            return dateValue.toDate();
+          }
+          if (dateValue instanceof Date) {
+            return dateValue;
+          }
+          return new Date(dateValue);
+        };
+        
+        return {
+          id: p.id,
+          name: p.name,
+          deadline: getDate(p.deadline)
+        };
+      });
+      
+      days.push({
+        date,
+        projectCount: dayProjects.length,
+        projects: dayProjects,
+        isToday: date.getTime() === today.getTime(),
+        isCurrentMonth: true
+      });
+    }
+    
+    // 补齐前后的日期(保证从周日开始)
+    const firstDay = new Date(year, month, 1);
+    const firstDayOfWeek = firstDay.getDay(); // 0=周日
+    
+    // 前置补齐(上个月的日期)
+    for (let i = firstDayOfWeek - 1; i >= 0; i--) {
+      const date = new Date(year, month, -i);
+      days.unshift({
+        date,
+        projectCount: 0,
+        projects: [],
+        isToday: false,
+        isCurrentMonth: false
+      });
+    }
+    
+    // 后置补齐(下个月的日期,保证总数是7的倍数)
+    const remainder = days.length % 7;
+    if (remainder !== 0) {
+      const needed = 7 - remainder;
+      for (let i = 1; i <= needed; i++) {
+        const date = new Date(year, month + 1, i);
+        days.push({
+          date,
+          projectCount: 0,
+          projects: [],
+          isToday: false,
+          isCurrentMonth: false
+        });
+      }
+    }
+    
+    return {
+      currentMonth: new Date(year, month, 1),
+      days
     };
   }
+  
+  /**
+   * 处理日历日期点击
+   */
+  onCalendarDayClick(day: EmployeeCalendarDay): void {
+    if (!day.isCurrentMonth || day.projectCount === 0) {
+      return;
+    }
+    
+    this.selectedDate = day.date;
+    this.selectedDayProjects = day.projects;
+    this.showCalendarProjectList = true;
+  }
+  
+  /**
+   * 关闭项目列表弹窗
+   */
+  closeCalendarProjectList(): void {
+    this.showCalendarProjectList = false;
+    this.selectedDate = null;
+    this.selectedDayProjects = [];
+  }
 
   // 生成红色标记说明
   private generateRedMarkExplanation(employeeName: string, leaveRecords: LeaveRecord[], projectCount: number): string {
@@ -2724,4 +2947,55 @@ export class Dashboard implements OnInit, OnDestroy {
 
     return overlayData;
   }
+
+  /**
+   * 加载用户Profile信息
+   */
+  async loadUserProfile(): Promise<void> {
+    try {
+      const cid = localStorage.getItem("company");
+      if (!cid) {
+        console.warn('未找到公司ID,使用默认用户信息');
+        return;
+      }
+
+      const wwAuth = new WxworkAuth({ cid });
+      const profile = await wwAuth.currentProfile();
+
+      if (profile) {
+        const name = profile.get("name") || profile.get("mobile") || '组长';
+        const avatar = profile.get("avatar");
+        const roleName = profile.get("roleName") || '组长';
+
+        this.currentUser = {
+          name,
+          avatar: avatar || this.generateDefaultAvatar(name),
+          roleName
+        };
+
+        console.log('用户Profile加载成功:', this.currentUser);
+      }
+    } catch (error) {
+      console.error('加载用户Profile失败:', error);
+      // 保持默认值
+    }
+  }
+
+  /**
+   * 生成默认头像(SVG格式)
+   * @param name 用户名
+   * @returns Base64编码的SVG数据URL
+   */
+  generateDefaultAvatar(name: string): string {
+    const initial = name ? name.substring(0, 2).toUpperCase() : '组长';
+    const bgColor = '#CCFFCC';
+    const textColor = '#555555';
+    
+    const svg = `<svg width='40' height='40' xmlns='http://www.w3.org/2000/svg'>
+      <rect width='100%' height='100%' fill='${bgColor}'/>
+      <text x='50%' y='50%' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='${textColor}' dy='0.3em'>${initial}</text>
+    </svg>`;
+    
+    return `data:image/svg+xml,${encodeURIComponent(svg)}`;
+  }
 }