|
@@ -16,46 +16,6 @@ interface WxworkCurrentChat {
|
|
|
[key: string]: any;
|
|
[key: string]: any;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 个人技能评分
|
|
|
|
|
-interface SkillRating {
|
|
|
|
|
- name: string;
|
|
|
|
|
- currentScore: number;
|
|
|
|
|
- targetScore: number;
|
|
|
|
|
- category: '设计能力' | '沟通能力' | '技术能力' | '项目管理';
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 案例作品
|
|
|
|
|
-interface CaseWork {
|
|
|
|
|
- id: string;
|
|
|
|
|
- projectId: string;
|
|
|
|
|
- projectTitle: string;
|
|
|
|
|
- coverImage: string;
|
|
|
|
|
- description: string;
|
|
|
|
|
- tags: string[];
|
|
|
|
|
- completionDate: Date;
|
|
|
|
|
- customerName: string;
|
|
|
|
|
- status: string;
|
|
|
|
|
- totalPrice?: number;
|
|
|
|
|
- roomType?: string;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 月度统计
|
|
|
|
|
-interface MonthlyStats {
|
|
|
|
|
- month: string;
|
|
|
|
|
- totalProjects: number;
|
|
|
|
|
- completedProjects: number;
|
|
|
|
|
- revenue: number;
|
|
|
|
|
- avgScore: number;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 自我评价
|
|
|
|
|
-interface SelfEvaluation {
|
|
|
|
|
- strengths: string[]; // 优势
|
|
|
|
|
- improvements: string[]; // 待提升
|
|
|
|
|
- personalStatement: string; // 个人陈述
|
|
|
|
|
- lastUpdated: Date;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
function wxdebug(...params:any[]){
|
|
function wxdebug(...params:any[]){
|
|
|
console.log(params)
|
|
console.log(params)
|
|
|
}
|
|
}
|
|
@@ -63,16 +23,18 @@ function wxdebug(...params:any[]){
|
|
|
const Parse = FmodeParse.with('nova');
|
|
const Parse = FmodeParse.with('nova');
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 个人看板页面(重构自项目预加载页面)
|
|
|
|
|
|
|
+ * 项目预加载页面
|
|
|
*
|
|
*
|
|
|
* 功能:
|
|
* 功能:
|
|
|
- * 1. 展示个人信息和自我评价
|
|
|
|
|
- * 2. 技能评分和发展目标
|
|
|
|
|
- * 3. 案例作品集(从完成项目选择)
|
|
|
|
|
- * 4. 月度接单量统计
|
|
|
|
|
- * 5. 支持编辑个人资料和案例
|
|
|
|
|
|
|
+ * 1. 从企微会话获取上下文(群聊或联系人)
|
|
|
|
|
+ * 2. 获取当前登录用户(Profile)
|
|
|
|
|
+ * 3. 根据场景跳转到对应页面
|
|
|
|
|
+ * - 群聊 → 项目详情 或 创建项目引导
|
|
|
|
|
+ * - 联系人 → 客户画像
|
|
|
*
|
|
*
|
|
|
* 路由:/wxwork/:cid/project-loader
|
|
* 路由:/wxwork/:cid/project-loader
|
|
|
|
|
+ *
|
|
|
|
|
+ * 参考实现:nova-admin/projects/nova-crm/src/modules/chat/page-chat-context
|
|
|
*/
|
|
*/
|
|
|
@Component({
|
|
@Component({
|
|
|
selector: 'app-project-loader',
|
|
selector: 'app-project-loader',
|
|
@@ -96,46 +58,20 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
wecorp: WxworkCorp | null = null;
|
|
wecorp: WxworkCorp | null = null;
|
|
|
|
|
|
|
|
// 上下文数据
|
|
// 上下文数据
|
|
|
- currentUser: FmodeObject | null = null; // Profile
|
|
|
|
|
|
|
+ currentUser: FmodeObject | null = null; // Profile 或 UserSocial
|
|
|
currentChat: WxworkCurrentChat | null = null;
|
|
currentChat: WxworkCurrentChat | null = null;
|
|
|
- chatType: 'group' | 'contact' | 'personal' = 'personal';
|
|
|
|
|
|
|
+ chatType: 'group' | 'contact' | 'none' = 'none';
|
|
|
groupChat: FmodeObject | null = null; // GroupChat
|
|
groupChat: FmodeObject | null = null; // GroupChat
|
|
|
contact: FmodeObject | null = null; // ContactInfo
|
|
contact: FmodeObject | null = null; // ContactInfo
|
|
|
project: FmodeObject | null = null; // Project
|
|
project: FmodeObject | null = null; // Project
|
|
|
|
|
|
|
|
- // 个人看板数据
|
|
|
|
|
- skillRatings: SkillRating[] = [];
|
|
|
|
|
- caseWorks: CaseWork[] = [];
|
|
|
|
|
- monthlyStats: MonthlyStats[] = [];
|
|
|
|
|
- selfEvaluation: SelfEvaluation = {
|
|
|
|
|
- strengths: [],
|
|
|
|
|
- improvements: [],
|
|
|
|
|
- personalStatement: '',
|
|
|
|
|
- lastUpdated: new Date()
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // UI状态
|
|
|
|
|
- activeTab: 'overview' | 'cases' | 'stats' | 'skills' = 'overview';
|
|
|
|
|
- showEditEvaluation: boolean = false;
|
|
|
|
|
- showCaseSelector: boolean = false;
|
|
|
|
|
- showSkillEditor: boolean = false;
|
|
|
|
|
-
|
|
|
|
|
- // 编辑状态
|
|
|
|
|
- editingEvaluation: SelfEvaluation | null = null;
|
|
|
|
|
- availableProjects: FmodeObject[] = [];
|
|
|
|
|
- selectedProjectIds: string[] = [];
|
|
|
|
|
-
|
|
|
|
|
- // 统计数据
|
|
|
|
|
- totalProjects: number = 0;
|
|
|
|
|
- completedProjects: number = 0;
|
|
|
|
|
- currentMonthProjects: number = 0;
|
|
|
|
|
- avgCustomerRating: number = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 创建项目引导(保留原有功能)
|
|
|
|
|
|
|
+ // 创建项目引导
|
|
|
showCreateGuide: boolean = false;
|
|
showCreateGuide: boolean = false;
|
|
|
defaultProjectName: string = '';
|
|
defaultProjectName: string = '';
|
|
|
projectName: string = '';
|
|
projectName: string = '';
|
|
|
creating: boolean = false;
|
|
creating: boolean = false;
|
|
|
|
|
+
|
|
|
|
|
+ // 历史项目(当前群聊无项目时展示)
|
|
|
historyProjects: FmodeObject[] = [];
|
|
historyProjects: FmodeObject[] = [];
|
|
|
|
|
|
|
|
constructor(
|
|
constructor(
|
|
@@ -161,7 +97,7 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 加载数据主流程
|
|
|
|
|
|
|
+ * 加载数据主流程(参考 page-chat-context 实现)
|
|
|
*/
|
|
*/
|
|
|
async loadData() {
|
|
async loadData() {
|
|
|
try {
|
|
try {
|
|
@@ -176,13 +112,14 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
|
|
|
|
|
wxdebug('1. SDK初始化完成', { cid: this.cid, appId: this.appId });
|
|
wxdebug('1. SDK初始化完成', { cid: this.cid, appId: this.appId });
|
|
|
|
|
|
|
|
- // 2️⃣ 加载当前登录员工信息
|
|
|
|
|
|
|
+ // 2️⃣ 加载当前登录员工信息(由 WxworkAuthGuard 自动登录)
|
|
|
this.loadingMessage = '获取用户信息...';
|
|
this.loadingMessage = '获取用户信息...';
|
|
|
try {
|
|
try {
|
|
|
this.currentUser = await this.wxwork.getCurrentUser();
|
|
this.currentUser = await this.wxwork.getCurrentUser();
|
|
|
wxdebug('2. 获取当前用户成功', this.currentUser?.toJSON());
|
|
wxdebug('2. 获取当前用户成功', this.currentUser?.toJSON());
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error('获取当前用户失败:', err);
|
|
console.error('获取当前用户失败:', err);
|
|
|
|
|
+ wxdebug('2. 获取当前用户失败', err);
|
|
|
throw new Error('获取用户信息失败,请重试');
|
|
throw new Error('获取用户信息失败,请重试');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -196,26 +133,60 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
wxdebug('3. getCurrentChat失败', err);
|
|
wxdebug('3. getCurrentChat失败', err);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 4️⃣ 根据场景处理
|
|
|
|
|
|
|
+ // 4️⃣ 根据场景同步数据
|
|
|
if (this.currentChat?.type === "chatId" && this.currentChat?.group) {
|
|
if (this.currentChat?.type === "chatId" && this.currentChat?.group) {
|
|
|
- // 群聊场景 - 保留原有逻辑
|
|
|
|
|
|
|
+ // 群聊场景
|
|
|
|
|
+ wxdebug('4. 检测到群聊场景', this.currentChat.group);
|
|
|
|
|
+ this.loadingMessage = '同步群聊信息...';
|
|
|
|
|
+ try {
|
|
|
this.chatType = 'group';
|
|
this.chatType = 'group';
|
|
|
this.groupChat = await this.wxwork.syncGroupChat(this.currentChat.group);
|
|
this.groupChat = await this.wxwork.syncGroupChat(this.currentChat.group);
|
|
|
|
|
+ wxdebug('5. 群聊同步完成', this.groupChat?.toJSON());
|
|
|
|
|
+
|
|
|
|
|
+ // 处理群聊场景
|
|
|
await this.handleGroupChatScene();
|
|
await this.handleGroupChatScene();
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('群聊同步失败:', err);
|
|
|
|
|
+ wxdebug('5. 群聊同步失败', err);
|
|
|
|
|
+ throw new Error('群聊信息同步失败');
|
|
|
|
|
+ }
|
|
|
} else if (this.currentChat?.type === "userId" && this.currentChat?.id) {
|
|
} else if (this.currentChat?.type === "userId" && this.currentChat?.id) {
|
|
|
- // 联系人场景 - 保留原有逻辑
|
|
|
|
|
|
|
+ // 联系人场景
|
|
|
|
|
+ wxdebug('4. 检测到联系人场景', { id: this.currentChat.id });
|
|
|
|
|
+ this.loadingMessage = '同步联系人信息...';
|
|
|
|
|
+ try {
|
|
|
this.chatType = 'contact';
|
|
this.chatType = 'contact';
|
|
|
|
|
+
|
|
|
|
|
+ // 获取完整联系人信息
|
|
|
const contactInfo = await this.wecorp!.externalContact.get(this.currentChat.id);
|
|
const contactInfo = await this.wecorp!.externalContact.get(this.currentChat.id);
|
|
|
|
|
+ wxdebug('5. 获取完整联系人信息', contactInfo);
|
|
|
|
|
+
|
|
|
this.contact = await this.wxwork.syncContact(contactInfo);
|
|
this.contact = await this.wxwork.syncContact(contactInfo);
|
|
|
|
|
+ wxdebug('6. 联系人同步完成', this.contact?.toJSON());
|
|
|
|
|
+
|
|
|
|
|
+ // 处理联系人场景
|
|
|
await this.handleContactScene();
|
|
await this.handleContactScene();
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('联系人同步失败:', err);
|
|
|
|
|
+ wxdebug('联系人同步失败', err);
|
|
|
|
|
+ throw new Error('联系人信息同步失败');
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
- // 个人看板场景(默认)
|
|
|
|
|
- this.chatType = 'personal';
|
|
|
|
|
- await this.loadPersonalBoard();
|
|
|
|
|
|
|
+ // 未检测到有效场景
|
|
|
|
|
+ wxdebug('4. 未检测到有效场景', {
|
|
|
|
|
+ currentChat: this.currentChat,
|
|
|
|
|
+ type: this.currentChat?.type,
|
|
|
|
|
+ hasGroup: !!this.currentChat?.group,
|
|
|
|
|
+ hasContact: !!this.currentChat?.contact,
|
|
|
|
|
+ hasId: !!this.currentChat?.id
|
|
|
|
|
+ });
|
|
|
|
|
+ throw new Error('无法识别当前会话类型,请在群聊或联系人会话中打开');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
wxdebug('加载完成', {
|
|
wxdebug('加载完成', {
|
|
|
chatType: this.chatType,
|
|
chatType: this.chatType,
|
|
|
|
|
+ hasGroupChat: !!this.groupChat,
|
|
|
|
|
+ hasContact: !!this.contact,
|
|
|
hasCurrentUser: !!this.currentUser
|
|
hasCurrentUser: !!this.currentUser
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -228,446 +199,61 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 加载个人看板数据
|
|
|
|
|
- */
|
|
|
|
|
- async loadPersonalBoard() {
|
|
|
|
|
- if (!this.currentUser) {
|
|
|
|
|
- throw new Error('用户信息不存在');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.loadingMessage = '加载个人信息...';
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- // 并行加载所有数据
|
|
|
|
|
- await Promise.all([
|
|
|
|
|
- this.loadProfileData(),
|
|
|
|
|
- this.loadSkillRatings(),
|
|
|
|
|
- this.loadCaseWorks(),
|
|
|
|
|
- this.loadMonthlyStats(),
|
|
|
|
|
- this.loadSelfEvaluation()
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- console.log('✅ 个人看板数据加载完成');
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('加载个人看板数据失败:', err);
|
|
|
|
|
- throw err;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 加载个人资料数据
|
|
|
|
|
- */
|
|
|
|
|
- async loadProfileData() {
|
|
|
|
|
- try {
|
|
|
|
|
- // 从Profile表获取最新数据
|
|
|
|
|
- const query = new Parse.Query('Profile');
|
|
|
|
|
- const profile = await query.get(this.currentUser!.id);
|
|
|
|
|
- this.currentUser = profile;
|
|
|
|
|
-
|
|
|
|
|
- const data = profile.get('data') || {};
|
|
|
|
|
-
|
|
|
|
|
- // 计算统计数据
|
|
|
|
|
- await this.calculateStatistics();
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('加载个人资料失败:', err);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 加载技能评分
|
|
|
|
|
|
|
+ * 处理群聊场景
|
|
|
*/
|
|
*/
|
|
|
- async loadSkillRatings() {
|
|
|
|
|
- try {
|
|
|
|
|
- const data = this.currentUser!.get('data') || {};
|
|
|
|
|
- const skills = data.skillRatings || [];
|
|
|
|
|
-
|
|
|
|
|
- // 如果没有技能评分,创建默认值
|
|
|
|
|
- if (skills.length === 0) {
|
|
|
|
|
- this.skillRatings = this.getDefaultSkillRatings();
|
|
|
|
|
- } else {
|
|
|
|
|
- this.skillRatings = skills;
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('加载技能评分失败:', err);
|
|
|
|
|
- this.skillRatings = this.getDefaultSkillRatings();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 加载案例作品
|
|
|
|
|
- */
|
|
|
|
|
- async loadCaseWorks() {
|
|
|
|
|
- try {
|
|
|
|
|
- const data = this.currentUser!.get('data') || {};
|
|
|
|
|
- const caseProjectIds = data.caseWorks || [];
|
|
|
|
|
-
|
|
|
|
|
- if (caseProjectIds.length === 0) {
|
|
|
|
|
- this.caseWorks = [];
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 查询案例对应的项目
|
|
|
|
|
- const query = new Parse.Query('Project');
|
|
|
|
|
- query.containedIn('objectId', caseProjectIds);
|
|
|
|
|
- query.equalTo('currentStage', '售后归档');
|
|
|
|
|
- query.notEqualTo('isDeleted', true);
|
|
|
|
|
- query.include('contact');
|
|
|
|
|
- query.descending('updatedAt');
|
|
|
|
|
- query.limit(20);
|
|
|
|
|
-
|
|
|
|
|
- const projects = await query.find();
|
|
|
|
|
-
|
|
|
|
|
- this.caseWorks = projects.map(p => this.transformProjectToCase(p));
|
|
|
|
|
- console.log(`✅ 加载了 ${this.caseWorks.length} 个案例作品`);
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('加载案例作品失败:', err);
|
|
|
|
|
- this.caseWorks = [];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 加载月度统计
|
|
|
|
|
- */
|
|
|
|
|
- async loadMonthlyStats() {
|
|
|
|
|
- try {
|
|
|
|
|
- // 查询最近6个月的项目
|
|
|
|
|
- const sixMonthsAgo = new Date();
|
|
|
|
|
- sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
|
|
|
|
|
-
|
|
|
|
|
- const query = new Parse.Query('Project');
|
|
|
|
|
- query.equalTo('assignee', this.currentUser!.toPointer());
|
|
|
|
|
- query.greaterThanOrEqualTo('createdAt', sixMonthsAgo);
|
|
|
|
|
- query.notEqualTo('isDeleted', true);
|
|
|
|
|
- query.limit(1000);
|
|
|
|
|
-
|
|
|
|
|
- const projects = await query.find();
|
|
|
|
|
-
|
|
|
|
|
- // 按月分组统计
|
|
|
|
|
- const monthlyMap = new Map<string, MonthlyStats>();
|
|
|
|
|
-
|
|
|
|
|
- projects.forEach(p => {
|
|
|
|
|
- const date = p.get('createdAt');
|
|
|
|
|
- const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
|
|
|
|
-
|
|
|
|
|
- if (!monthlyMap.has(monthKey)) {
|
|
|
|
|
- monthlyMap.set(monthKey, {
|
|
|
|
|
- month: monthKey,
|
|
|
|
|
- totalProjects: 0,
|
|
|
|
|
- completedProjects: 0,
|
|
|
|
|
- revenue: 0,
|
|
|
|
|
- avgScore: 0
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const stats = monthlyMap.get(monthKey)!;
|
|
|
|
|
- stats.totalProjects++;
|
|
|
|
|
-
|
|
|
|
|
- if (p.get('currentStage') === '售后归档' || p.get('status') === '已完成') {
|
|
|
|
|
- stats.completedProjects++;
|
|
|
|
|
-
|
|
|
|
|
- // 计算收入
|
|
|
|
|
- const pricing = p.get('data')?.pricing || {};
|
|
|
|
|
- const totalPrice = pricing.totalAmount || pricing.total || pricing.finalPrice || 0;
|
|
|
|
|
- stats.revenue += totalPrice;
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 转换为数组并排序
|
|
|
|
|
- this.monthlyStats = Array.from(monthlyMap.values())
|
|
|
|
|
- .sort((a, b) => b.month.localeCompare(a.month))
|
|
|
|
|
- .slice(0, 6);
|
|
|
|
|
-
|
|
|
|
|
- console.log(`✅ 加载了 ${this.monthlyStats.length} 个月的统计数据`);
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('加载月度统计失败:', err);
|
|
|
|
|
- this.monthlyStats = [];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 加载自我评价
|
|
|
|
|
- */
|
|
|
|
|
- async loadSelfEvaluation() {
|
|
|
|
|
- try {
|
|
|
|
|
- const data = this.currentUser!.get('data') || {};
|
|
|
|
|
- const evaluation = data.selfEvaluation;
|
|
|
|
|
-
|
|
|
|
|
- if (evaluation) {
|
|
|
|
|
- this.selfEvaluation = {
|
|
|
|
|
- strengths: evaluation.strengths || [],
|
|
|
|
|
- improvements: evaluation.improvements || [],
|
|
|
|
|
- personalStatement: evaluation.personalStatement || '',
|
|
|
|
|
- lastUpdated: evaluation.lastUpdated ? new Date(evaluation.lastUpdated) : new Date()
|
|
|
|
|
- };
|
|
|
|
|
- } else {
|
|
|
|
|
- // 默认值
|
|
|
|
|
- this.selfEvaluation = {
|
|
|
|
|
- strengths: ['专业扎实', '责任心强'],
|
|
|
|
|
- improvements: ['沟通效率', '时间管理'],
|
|
|
|
|
- personalStatement: '我是一名热爱设计的专业人士,致力于为客户提供优质的服务。',
|
|
|
|
|
- lastUpdated: new Date()
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('加载自我评价失败:', err);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 计算统计数据
|
|
|
|
|
- */
|
|
|
|
|
- async calculateStatistics() {
|
|
|
|
|
- try {
|
|
|
|
|
- const profilePointer = this.currentUser!.toPointer();
|
|
|
|
|
-
|
|
|
|
|
- // 查询总项目数
|
|
|
|
|
- const totalQuery = new Parse.Query('Project');
|
|
|
|
|
- totalQuery.equalTo('assignee', profilePointer);
|
|
|
|
|
- totalQuery.notEqualTo('isDeleted', true);
|
|
|
|
|
- this.totalProjects = await totalQuery.count();
|
|
|
|
|
-
|
|
|
|
|
- // 查询已完成项目数
|
|
|
|
|
- const completedQuery = new Parse.Query('Project');
|
|
|
|
|
- completedQuery.equalTo('assignee', profilePointer);
|
|
|
|
|
- completedQuery.equalTo('currentStage', '售后归档');
|
|
|
|
|
- completedQuery.notEqualTo('isDeleted', true);
|
|
|
|
|
- this.completedProjects = await completedQuery.count();
|
|
|
|
|
-
|
|
|
|
|
- // 查询本月项目数
|
|
|
|
|
- const currentMonth = new Date();
|
|
|
|
|
- currentMonth.setDate(1);
|
|
|
|
|
- currentMonth.setHours(0, 0, 0, 0);
|
|
|
|
|
-
|
|
|
|
|
- const monthQuery = new Parse.Query('Project');
|
|
|
|
|
- monthQuery.equalTo('assignee', profilePointer);
|
|
|
|
|
- monthQuery.greaterThanOrEqualTo('createdAt', currentMonth);
|
|
|
|
|
- monthQuery.notEqualTo('isDeleted', true);
|
|
|
|
|
- this.currentMonthProjects = await monthQuery.count();
|
|
|
|
|
-
|
|
|
|
|
- console.log(`✅ 统计数据:总项目=${this.totalProjects}, 已完成=${this.completedProjects}, 本月=${this.currentMonthProjects}`);
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('计算统计数据失败:', err);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 将项目转换为案例
|
|
|
|
|
- */
|
|
|
|
|
- transformProjectToCase(project: FmodeObject): CaseWork {
|
|
|
|
|
- const data = project.get('data') || {};
|
|
|
|
|
- const pricing = data.pricing || {};
|
|
|
|
|
- const contact = project.get('contact');
|
|
|
|
|
-
|
|
|
|
|
- // 获取封面图片
|
|
|
|
|
- let coverImage = '/assets/images/default-project.jpg';
|
|
|
|
|
- if (data.referenceImages && data.referenceImages.length > 0) {
|
|
|
|
|
- coverImage = data.referenceImages[0];
|
|
|
|
|
- } else if (data.deliverables && data.deliverables.length > 0) {
|
|
|
|
|
- const firstDeliverable = data.deliverables[0];
|
|
|
|
|
- if (firstDeliverable.files && firstDeliverable.files.length > 0) {
|
|
|
|
|
- coverImage = firstDeliverable.files[0];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- id: project.id,
|
|
|
|
|
- projectId: project.id,
|
|
|
|
|
- projectTitle: project.get('title') || '未命名项目',
|
|
|
|
|
- coverImage: coverImage,
|
|
|
|
|
- description: data.description || project.get('title') || '',
|
|
|
|
|
- tags: data.tags || data.stylePreferences || [],
|
|
|
|
|
- completionDate: project.get('updatedAt') || new Date(),
|
|
|
|
|
- customerName: contact?.get('name') || '客户',
|
|
|
|
|
- status: project.get('status') || '已完成',
|
|
|
|
|
- totalPrice: pricing.totalAmount || pricing.total || pricing.finalPrice,
|
|
|
|
|
- roomType: data.roomType || data.spaceType
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 获取默认技能评分
|
|
|
|
|
- */
|
|
|
|
|
- getDefaultSkillRatings(): SkillRating[] {
|
|
|
|
|
- const role = this.currentUser?.get('roleName') || '组员';
|
|
|
|
|
-
|
|
|
|
|
- if (role === '组员' || role === '设计师') {
|
|
|
|
|
- return [
|
|
|
|
|
- { name: '空间设计', currentScore: 70, targetScore: 90, category: '设计能力' },
|
|
|
|
|
- { name: '色彩搭配', currentScore: 65, targetScore: 85, category: '设计能力' },
|
|
|
|
|
- { name: '软装搭配', currentScore: 75, targetScore: 90, category: '设计能力' },
|
|
|
|
|
- { name: '客户沟通', currentScore: 60, targetScore: 80, category: '沟通能力' },
|
|
|
|
|
- { name: '需求分析', currentScore: 65, targetScore: 85, category: '沟通能力' },
|
|
|
|
|
- { name: '3D建模', currentScore: 70, targetScore: 85, category: '技术能力' },
|
|
|
|
|
- { name: '效果图渲染', currentScore: 75, targetScore: 90, category: '技术能力' },
|
|
|
|
|
- { name: '项目管理', currentScore: 60, targetScore: 80, category: '项目管理' }
|
|
|
|
|
- ];
|
|
|
|
|
- } else if (role === '客服') {
|
|
|
|
|
- return [
|
|
|
|
|
- { name: '客户接待', currentScore: 80, targetScore: 95, category: '沟通能力' },
|
|
|
|
|
- { name: '需求挖掘', currentScore: 75, targetScore: 90, category: '沟通能力' },
|
|
|
|
|
- { name: '订单管理', currentScore: 70, targetScore: 85, category: '项目管理' },
|
|
|
|
|
- { name: '售后服务', currentScore: 75, targetScore: 90, category: '沟通能力' },
|
|
|
|
|
- { name: '问题解决', currentScore: 65, targetScore: 85, category: '项目管理' }
|
|
|
|
|
- ];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // ==================== 编辑功能 ====================
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 打开编辑自我评价
|
|
|
|
|
- */
|
|
|
|
|
- openEditEvaluation() {
|
|
|
|
|
- this.editingEvaluation = JSON.parse(JSON.stringify(this.selfEvaluation));
|
|
|
|
|
- this.showEditEvaluation = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 保存自我评价
|
|
|
|
|
- */
|
|
|
|
|
- async saveEvaluation() {
|
|
|
|
|
- if (!this.editingEvaluation) return;
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- this.editingEvaluation.lastUpdated = new Date();
|
|
|
|
|
-
|
|
|
|
|
- const data = this.currentUser!.get('data') || {};
|
|
|
|
|
- data.selfEvaluation = this.editingEvaluation;
|
|
|
|
|
-
|
|
|
|
|
- this.currentUser!.set('data', data);
|
|
|
|
|
- await this.currentUser!.save();
|
|
|
|
|
-
|
|
|
|
|
- this.selfEvaluation = this.editingEvaluation;
|
|
|
|
|
- this.showEditEvaluation = false;
|
|
|
|
|
- this.editingEvaluation = null;
|
|
|
|
|
-
|
|
|
|
|
- window?.fmode?.alert('保存成功!');
|
|
|
|
|
- } catch (err: any) {
|
|
|
|
|
- console.error('保存自我评价失败:', err);
|
|
|
|
|
- window?.fmode?.alert('保存失败: ' + (err.message || '未知错误'));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 打开案例选择器
|
|
|
|
|
- */
|
|
|
|
|
- async openCaseSelector() {
|
|
|
|
|
- try {
|
|
|
|
|
- this.loadingMessage = '加载可选项目...';
|
|
|
|
|
- this.loading = true;
|
|
|
|
|
-
|
|
|
|
|
- // 查询已完成的项目
|
|
|
|
|
- const query = new Parse.Query('Project');
|
|
|
|
|
- query.equalTo('assignee', this.currentUser!.toPointer());
|
|
|
|
|
- query.equalTo('currentStage', '售后归档');
|
|
|
|
|
- query.notEqualTo('isDeleted', true);
|
|
|
|
|
- query.include('contact');
|
|
|
|
|
- query.descending('updatedAt');
|
|
|
|
|
- query.limit(100);
|
|
|
|
|
-
|
|
|
|
|
- this.availableProjects = await query.find();
|
|
|
|
|
- this.selectedProjectIds = this.caseWorks.map(c => c.projectId);
|
|
|
|
|
-
|
|
|
|
|
- this.showCaseSelector = true;
|
|
|
|
|
- console.log(`✅ 找到 ${this.availableProjects.length} 个可选项目`);
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- console.error('加载可选项目失败:', err);
|
|
|
|
|
- window?.fmode?.alert('加载失败,请重试');
|
|
|
|
|
- } finally {
|
|
|
|
|
- this.loading = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 切换项目选择
|
|
|
|
|
- */
|
|
|
|
|
- toggleProjectSelection(projectId: string) {
|
|
|
|
|
- const index = this.selectedProjectIds.indexOf(projectId);
|
|
|
|
|
- if (index > -1) {
|
|
|
|
|
- this.selectedProjectIds.splice(index, 1);
|
|
|
|
|
- } else {
|
|
|
|
|
- if (this.selectedProjectIds.length >= 12) {
|
|
|
|
|
- window?.fmode?.alert('最多选择12个案例');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- this.selectedProjectIds.push(projectId);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 保存案例选择
|
|
|
|
|
- */
|
|
|
|
|
- async saveCaseSelection() {
|
|
|
|
|
- try {
|
|
|
|
|
- const data = this.currentUser!.get('data') || {};
|
|
|
|
|
- data.caseWorks = this.selectedProjectIds;
|
|
|
|
|
-
|
|
|
|
|
- this.currentUser!.set('data', data);
|
|
|
|
|
- await this.currentUser!.save();
|
|
|
|
|
-
|
|
|
|
|
- // 重新加载案例
|
|
|
|
|
- await this.loadCaseWorks();
|
|
|
|
|
-
|
|
|
|
|
- this.showCaseSelector = false;
|
|
|
|
|
- window?.fmode?.alert('保存成功!');
|
|
|
|
|
- } catch (err: any) {
|
|
|
|
|
- console.error('保存案例选择失败:', err);
|
|
|
|
|
- window?.fmode?.alert('保存失败: ' + (err.message || '未知错误'));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 保存技能评分
|
|
|
|
|
- */
|
|
|
|
|
- async saveSkillRatings() {
|
|
|
|
|
- try {
|
|
|
|
|
- const data = this.currentUser!.get('data') || {};
|
|
|
|
|
- data.skillRatings = this.skillRatings;
|
|
|
|
|
-
|
|
|
|
|
- this.currentUser!.set('data', data);
|
|
|
|
|
- await this.currentUser!.save();
|
|
|
|
|
-
|
|
|
|
|
- this.showSkillEditor = false;
|
|
|
|
|
- window?.fmode?.alert('保存成功!');
|
|
|
|
|
- } catch (err: any) {
|
|
|
|
|
- console.error('保存技能评分失败:', err);
|
|
|
|
|
- window?.fmode?.alert('保存失败: ' + (err.message || '未知错误'));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // ==================== 原有群聊/联系人场景功能(保留) ====================
|
|
|
|
|
-
|
|
|
|
|
async handleGroupChatScene() {
|
|
async handleGroupChatScene() {
|
|
|
this.loadingMessage = '查询项目信息...';
|
|
this.loadingMessage = '查询项目信息...';
|
|
|
|
|
+
|
|
|
|
|
+ // 查询群聊关联的项目
|
|
|
const projectPointer = this.groupChat!.get('project');
|
|
const projectPointer = this.groupChat!.get('project');
|
|
|
|
|
|
|
|
if (projectPointer) {
|
|
if (projectPointer) {
|
|
|
|
|
+ // 有项目,加载项目详情
|
|
|
let pid = projectPointer.id || projectPointer.objectId
|
|
let pid = projectPointer.id || projectPointer.objectId
|
|
|
try {
|
|
try {
|
|
|
const query = new Parse.Query('Project');
|
|
const query = new Parse.Query('Project');
|
|
|
query.include('contact', 'assignee');
|
|
query.include('contact', 'assignee');
|
|
|
this.project = await query.get(pid);
|
|
this.project = await query.get(pid);
|
|
|
|
|
+
|
|
|
|
|
+ wxdebug('找到项目', this.project.toJSON());
|
|
|
|
|
+
|
|
|
|
|
+ // 跳转项目详情
|
|
|
await this.navigateToProjectDetail();
|
|
await this.navigateToProjectDetail();
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error('加载项目失败:', err);
|
|
console.error('加载项目失败:', err);
|
|
|
|
|
+ wxdebug('加载项目失败', err);
|
|
|
this.error = '项目已删除或无权访问';
|
|
this.error = '项目已删除或无权访问';
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ // 无项目,查询历史项目并显示创建引导
|
|
|
await this.loadHistoryProjects();
|
|
await this.loadHistoryProjects();
|
|
|
this.showCreateProjectGuide();
|
|
this.showCreateProjectGuide();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 处理联系人场景
|
|
|
|
|
+ */
|
|
|
async handleContactScene() {
|
|
async handleContactScene() {
|
|
|
|
|
+ wxdebug('联系人场景,跳转客户画像', {
|
|
|
|
|
+ contactId: this.contact!.id,
|
|
|
|
|
+ contactName: this.contact!.get('name')
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 跳转客户画像页面
|
|
|
await this.router.navigate(['/wxwork', this.cid, 'contact', this.contact!.id], {
|
|
await this.router.navigate(['/wxwork', this.cid, 'contact', this.contact!.id], {
|
|
|
- queryParams: { profileId: this.currentUser!.id }
|
|
|
|
|
|
|
+ queryParams: {
|
|
|
|
|
+ profileId: this.currentUser!.id
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 加载历史项目(当前群聊相关的其他项目)
|
|
|
|
|
+ */
|
|
|
async loadHistoryProjects() {
|
|
async loadHistoryProjects() {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ // 通过 ProjectGroup 查询该群聊的所有项目
|
|
|
const pgQuery = new Parse.Query('ProjectGroup');
|
|
const pgQuery = new Parse.Query('ProjectGroup');
|
|
|
pgQuery.equalTo('groupChat', this.groupChat!.toPointer());
|
|
pgQuery.equalTo('groupChat', this.groupChat!.toPointer());
|
|
|
pgQuery.include('project');
|
|
pgQuery.include('project');
|
|
@@ -677,32 +263,53 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
this.historyProjects = projectGroups
|
|
this.historyProjects = projectGroups
|
|
|
.map((pg: any) => pg.get('project'))
|
|
.map((pg: any) => pg.get('project'))
|
|
|
.filter((p: any) => p && !p.get('isDeleted'));
|
|
.filter((p: any) => p && !p.get('isDeleted'));
|
|
|
|
|
+
|
|
|
|
|
+ wxdebug('找到历史项目', { count: this.historyProjects.length });
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error('加载历史项目失败:', err);
|
|
console.error('加载历史项目失败:', err);
|
|
|
|
|
+ wxdebug('加载历史项目失败', err);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 显示创建项目引导
|
|
|
|
|
+ */
|
|
|
showCreateProjectGuide() {
|
|
showCreateProjectGuide() {
|
|
|
this.showCreateGuide = true;
|
|
this.showCreateGuide = true;
|
|
|
this.defaultProjectName = this.groupChat!.get('name') || '新项目';
|
|
this.defaultProjectName = this.groupChat!.get('name') || '新项目';
|
|
|
this.projectName = this.defaultProjectName;
|
|
this.projectName = this.defaultProjectName;
|
|
|
|
|
+ wxdebug('显示创建项目引导', {
|
|
|
|
|
+ groupName: this.groupChat!.get('name'),
|
|
|
|
|
+ historyProjectsCount: this.historyProjects.length
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建项目
|
|
|
|
|
+ */
|
|
|
async createProject() {
|
|
async createProject() {
|
|
|
if (!this.projectName.trim()) {
|
|
if (!this.projectName.trim()) {
|
|
|
- window?.fmode?.alert('请输入项目名称');
|
|
|
|
|
|
|
+ window?.fmode?.alert('请输入项目名称');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 权限检查
|
|
|
const role = this.currentUser!.get('roleName');
|
|
const role = this.currentUser!.get('roleName');
|
|
|
if (!['客服', '组长', '管理员'].includes(role)) {
|
|
if (!['客服', '组长', '管理员'].includes(role)) {
|
|
|
- window?.fmode?.alert('您没有权限创建项目');
|
|
|
|
|
|
|
+ window?.fmode?.alert('您没有权限创建项目');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
this.creating = true;
|
|
this.creating = true;
|
|
|
|
|
+ wxdebug('开始创建项目', {
|
|
|
|
|
+ projectName: this.projectName,
|
|
|
|
|
+ groupChatId: this.groupChat!.id,
|
|
|
|
|
+ currentUserId: this.currentUser!.id,
|
|
|
|
|
+ role: role
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
|
|
+ // 1. 创建项目
|
|
|
const Project = Parse.Object.extend('Project');
|
|
const Project = Parse.Object.extend('Project');
|
|
|
const project = new Project();
|
|
const project = new Project();
|
|
|
|
|
|
|
@@ -717,10 +324,14 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
await project.save();
|
|
await project.save();
|
|
|
|
|
+ wxdebug('项目创建成功', { projectId: project.id });
|
|
|
|
|
|
|
|
|
|
+ // 2. 关联群聊
|
|
|
this.groupChat!.set('project', project.toPointer());
|
|
this.groupChat!.set('project', project.toPointer());
|
|
|
await this.groupChat!.save();
|
|
await this.groupChat!.save();
|
|
|
|
|
+ wxdebug('群聊关联项目成功');
|
|
|
|
|
|
|
|
|
|
+ // 3. 创建 ProjectGroup 关联(支持多项目多群)
|
|
|
const ProjectGroup = Parse.Object.extend('ProjectGroup');
|
|
const ProjectGroup = Parse.Object.extend('ProjectGroup');
|
|
|
const pg = new ProjectGroup();
|
|
const pg = new ProjectGroup();
|
|
|
pg.set('project', project.toPointer());
|
|
pg.set('project', project.toPointer());
|
|
@@ -728,48 +339,78 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
pg.set('isPrimary', true);
|
|
pg.set('isPrimary', true);
|
|
|
pg.set('company', this.currentUser!.get('company'));
|
|
pg.set('company', this.currentUser!.get('company'));
|
|
|
await pg.save();
|
|
await pg.save();
|
|
|
|
|
+ wxdebug('ProjectGroup关联创建成功');
|
|
|
|
|
|
|
|
- await this.activityLogService.logActivity({
|
|
|
|
|
- actorId: this.currentUser!.id,
|
|
|
|
|
- actorName: this.currentUser!.get('name') || '系统',
|
|
|
|
|
- actorRole: role,
|
|
|
|
|
- actionType: 'create',
|
|
|
|
|
- module: 'project',
|
|
|
|
|
- entityType: 'Project',
|
|
|
|
|
- entityId: project.id,
|
|
|
|
|
- entityName: this.projectName.trim(),
|
|
|
|
|
- description: '创建了新项目',
|
|
|
|
|
- metadata: {
|
|
|
|
|
- createdFrom: 'wxwork_groupchat',
|
|
|
|
|
- groupChatId: this.groupChat!.id,
|
|
|
|
|
- status: '待分配',
|
|
|
|
|
- stage: '订单分配'
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ // 4. 记录活动日志
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.activityLogService.logActivity({
|
|
|
|
|
+ actorId: this.currentUser!.id,
|
|
|
|
|
+ actorName: this.currentUser!.get('name') || '系统',
|
|
|
|
|
+ actorRole: role,
|
|
|
|
|
+ actionType: 'create',
|
|
|
|
|
+ module: 'project',
|
|
|
|
|
+ entityType: 'Project',
|
|
|
|
|
+ entityId: project.id,
|
|
|
|
|
+ entityName: this.projectName.trim(),
|
|
|
|
|
+ description: '创建了新项目',
|
|
|
|
|
+ metadata: {
|
|
|
|
|
+ createdFrom: 'wxwork_groupchat',
|
|
|
|
|
+ groupChatId: this.groupChat!.id,
|
|
|
|
|
+ status: '待分配',
|
|
|
|
|
+ stage: '订单分配'
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ wxdebug('活动日志记录成功');
|
|
|
|
|
+ } catch (logError) {
|
|
|
|
|
+ console.error('记录活动日志失败:', logError);
|
|
|
|
|
+ // 活动日志失败不影响主流程
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ // 5. 跳转项目详情
|
|
|
this.project = project;
|
|
this.project = project;
|
|
|
await this.navigateToProjectDetail();
|
|
await this.navigateToProjectDetail();
|
|
|
} catch (err: any) {
|
|
} catch (err: any) {
|
|
|
console.error('创建项目失败:', err);
|
|
console.error('创建项目失败:', err);
|
|
|
- window?.fmode?.alert('创建失败: ' + (err.message || '未知错误'));
|
|
|
|
|
|
|
+ wxdebug('创建项目失败', err);
|
|
|
|
|
+ window?.fmode?.alert('创建失败: ' + (err.message || '未知错误'));
|
|
|
} finally {
|
|
} finally {
|
|
|
this.creating = false;
|
|
this.creating = false;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 选择历史项目
|
|
|
|
|
+ */
|
|
|
async selectHistoryProject(project: FmodeObject) {
|
|
async selectHistoryProject(project: FmodeObject) {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ wxdebug('选择历史项目', {
|
|
|
|
|
+ projectId: project.id,
|
|
|
|
|
+ projectTitle: project.get('title')
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 更新群聊的当前项目
|
|
|
this.groupChat!.set('project', project.toPointer());
|
|
this.groupChat!.set('project', project.toPointer());
|
|
|
await this.groupChat!.save();
|
|
await this.groupChat!.save();
|
|
|
|
|
+
|
|
|
|
|
+ // 跳转项目详情
|
|
|
this.project = project;
|
|
this.project = project;
|
|
|
await this.navigateToProjectDetail();
|
|
await this.navigateToProjectDetail();
|
|
|
} catch (err: any) {
|
|
} catch (err: any) {
|
|
|
console.error('关联项目失败:', err);
|
|
console.error('关联项目失败:', err);
|
|
|
- window?.fmode?.alert('关联失败: ' + (err.message || '未知错误'));
|
|
|
|
|
|
|
+ window?.fmode?.alert('关联失败: ' + (err.message || '未知错误'));
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 跳转项目详情
|
|
|
|
|
+ */
|
|
|
async navigateToProjectDetail() {
|
|
async navigateToProjectDetail() {
|
|
|
|
|
+ wxdebug('跳转项目详情', {
|
|
|
|
|
+ projectId: this.project!.id,
|
|
|
|
|
+ cid: this.cid,
|
|
|
|
|
+ groupChatId: this.groupChat?.id
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
await this.router.navigate(['/wxwork', this.cid, 'project', this.project!.id], {
|
|
await this.router.navigate(['/wxwork', this.cid, 'project', this.project!.id], {
|
|
|
queryParams: {
|
|
queryParams: {
|
|
|
groupId: this.groupChat?.id,
|
|
groupId: this.groupChat?.id,
|
|
@@ -778,26 +419,36 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // ==================== 工具方法 ====================
|
|
|
|
|
-
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 重新加载
|
|
|
|
|
+ */
|
|
|
async reload() {
|
|
async reload() {
|
|
|
this.error = null;
|
|
this.error = null;
|
|
|
this.showCreateGuide = false;
|
|
this.showCreateGuide = false;
|
|
|
this.historyProjects = [];
|
|
this.historyProjects = [];
|
|
|
- this.chatType = 'personal';
|
|
|
|
|
|
|
+ this.chatType = 'none';
|
|
|
await this.loadData();
|
|
await this.loadData();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前员工姓名
|
|
|
|
|
+ */
|
|
|
getCurrentUserName(): string {
|
|
getCurrentUserName(): string {
|
|
|
if (!this.currentUser) return '未知';
|
|
if (!this.currentUser) return '未知';
|
|
|
return this.currentUser.get('name') || this.currentUser.get('userid') || '未知';
|
|
return this.currentUser.get('name') || this.currentUser.get('userid') || '未知';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前员工角色
|
|
|
|
|
+ */
|
|
|
getCurrentUserRole(): string {
|
|
getCurrentUserRole(): string {
|
|
|
if (!this.currentUser) return '未知';
|
|
if (!this.currentUser) return '未知';
|
|
|
return this.currentUser.get('roleName') || '未知';
|
|
return this.currentUser.get('roleName') || '未知';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取项目状态的显示样式类
|
|
|
|
|
+ */
|
|
|
getProjectStatusClass(status: string): string {
|
|
getProjectStatusClass(status: string): string {
|
|
|
const classMap: any = {
|
|
const classMap: any = {
|
|
|
'待分配': 'status-pending',
|
|
'待分配': 'status-pending',
|
|
@@ -809,49 +460,12 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
return classMap[status] || 'status-default';
|
|
return classMap[status] || 'status-default';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- formatDate(date: Date | string): string {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 格式化日期
|
|
|
|
|
+ */
|
|
|
|
|
+ formatDate(date: Date): string {
|
|
|
if (!date) return '';
|
|
if (!date) return '';
|
|
|
const d = new Date(date);
|
|
const d = new Date(date);
|
|
|
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- formatMonth(monthStr: string): string {
|
|
|
|
|
- const [year, month] = monthStr.split('-');
|
|
|
|
|
- return `${year}年${month}月`;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- formatCurrency(amount: number): string {
|
|
|
|
|
- if (!amount) return '¥0';
|
|
|
|
|
- return `¥${amount.toLocaleString()}`;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- getScoreColor(score: number): string {
|
|
|
|
|
- if (score >= 80) return 'score-high';
|
|
|
|
|
- if (score >= 60) return 'score-medium';
|
|
|
|
|
- return 'score-low';
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- getScoreProgress(current: number, target: number): number {
|
|
|
|
|
- if (target === 0) return 0;
|
|
|
|
|
- return Math.min((current / target) * 100, 100);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- filterSkillsByCategory(category: string): SkillRating[] {
|
|
|
|
|
- return this.skillRatings.filter(s => s.category === category);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- getMaxMonthlyProjects(): number {
|
|
|
|
|
- if (this.monthlyStats.length === 0) return 1;
|
|
|
|
|
- return Math.max(...this.monthlyStats.map(m => m.totalProjects));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- updateStrengths(value: string) {
|
|
|
|
|
- if (!this.editingEvaluation) return;
|
|
|
|
|
- this.editingEvaluation.strengths = value.split(',').map(s => s.trim()).filter(s => s.length > 0);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- updateImprovements(value: string) {
|
|
|
|
|
- if (!this.editingEvaluation) return;
|
|
|
|
|
- this.editingEvaluation.improvements = value.split(',').map(s => s.trim()).filter(s => s.length > 0);
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|