本文档详细说明如何为设计师端(Designer/组员端)优化企业微信身份识别并接入真实数据库,包括任务管理、请假申请、个人数据等功能的完整实现。
已实现内容:
WxworkAuthGuard(app.routes.ts 第64行)WxworkAuth 实例(dashboard.ts 第55-72行)authenticateAndLoadData() 方法(第79-96行)代码位置:
// 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 获取模拟数据 | 
| 请假申请 | ❌ 未实现 | 无 | 设计师无法申请或查看请假 | 
'cDL6R1hgSi')第1步:修改路由配置,支持cid参数
// 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组件的认证流程
// 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;
  }
}
// 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 [];
    }
  }
}
// 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 = [];
    }
  }
}
// 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;
  }
}
// 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: ''
    };
  }
}
<!-- 请假申请按钮 -->
<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>
// 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);
    }
  }
}
任务清单:
:cid 参数initAuth() 方法,支持动态cidvalidateDesignerRole() 角色验证验收标准:
任务清单:
DesignerTaskServicegetMyTasks() 方法loadRealTasks() 方法loadRealPendingFeedbacks() 方法验收标准:
任务清单:
LeaveServicesubmitLeaveApplication() 方法验收标准:
任务清单:
loadRealSkillTags() 方法loadRealPerformanceData() 方法验收标准:
任务清单:
// 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);
// 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字段需要索引
// 统一错误处理
try {
  const tasks = await this.taskService.getMyTasks(designerId);
  // 处理成功逻辑
} catch (error) {
  console.error('加载任务失败:', error);
  // 降级到模拟数据或提示用户
  this.showErrorMessage('加载任务失败,请刷新重试');
}
// 缓存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);
}
| 场景 | 操作 | 预期结果 | 
|---|---|---|
| 首次访问 | 访问设计师端 | 跳转企微授权 | 
| 授权成功 | 完成企微授权 | 自动登录并进入工作台 | 
| 角色不匹配 | 用其他角色访问 | 提示"您不是设计师" | 
| 已登录 | 再次访问 | 直接进入(无需重复授权) | 
| 场景 | 操作 | 预期结果 | 
|---|---|---|
| 有任务 | 加载工作台 | 显示所有分配的任务 | 
| 无任务 | 加载工作台 | 显示"暂无任务" | 
| 超期任务 | 查看任务列表 | 超期任务标红显示 | 
| 紧急任务 | 查看任务列表 | 紧急任务优先显示 | 
| 场景 | 操作 | 预期结果 | 
|---|---|---|
| 提交申请 | 填写并提交 | 成功提示,数据保存 | 
| 查看记录 | 打开请假记录 | 显示所有历史申请 | 
| 日期验证 | 选择错误日期 | 提示错误并阻止提交 | 
A: 虽然设计师端已有企微认证,但:
A: 推荐使用 ProjectTeam 表,因为:
降级方案:从 Project.assignee 查询
A: 推荐存在 Profile.data.leave:
如需复杂审批流程,可创建独立的 Leave 表
A: 使用 calculateLeaveDays() 方法自动排除周末:
calculateLeaveDays(startDate, endDate); // 自动排除周六日
工作负载可视化
协作功能
移动端优化
智能助手
成长体系
系统集成
rules/wxwork/auth.md - 企微认证APIrules/schemas.md - 数据表结构src/app/services/project.service.ts - 原有服务参考src/app/pages/designer/dashboard/dashboard.tssrc/app/pages/team-leader/services/designer.service.ts本方案通过以下步骤实现设计师端的完整功能:
预计工作量:3个工作日
实施难度:中等
风险等级:低
实施完成后,设计师端将具备:
为设计师提供高效、智能的工作管理平台!
文档版本:v1.0
创建日期:2024-12-24
最后更新:2024-12-24
维护人:开发团队