本文档详细说明如何为设计师端(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
维护人:开发团队