|
|
@@ -16,6 +16,46 @@ interface WxworkCurrentChat {
|
|
|
[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[]){
|
|
|
console.log(params)
|
|
|
}
|
|
|
@@ -23,18 +63,16 @@ function wxdebug(...params:any[]){
|
|
|
const Parse = FmodeParse.with('nova');
|
|
|
|
|
|
/**
|
|
|
- * 项目预加载页面
|
|
|
+ * 个人看板页面(重构自项目预加载页面)
|
|
|
*
|
|
|
* 功能:
|
|
|
- * 1. 从企微会话获取上下文(群聊或联系人)
|
|
|
- * 2. 获取当前登录用户(Profile)
|
|
|
- * 3. 根据场景跳转到对应页面
|
|
|
- * - 群聊 → 项目详情 或 创建项目引导
|
|
|
- * - 联系人 → 客户画像
|
|
|
+ * 1. 展示个人信息和自我评价
|
|
|
+ * 2. 技能评分和发展目标
|
|
|
+ * 3. 案例作品集(从完成项目选择)
|
|
|
+ * 4. 月度接单量统计
|
|
|
+ * 5. 支持编辑个人资料和案例
|
|
|
*
|
|
|
* 路由:/wxwork/:cid/project-loader
|
|
|
- *
|
|
|
- * 参考实现:nova-admin/projects/nova-crm/src/modules/chat/page-chat-context
|
|
|
*/
|
|
|
@Component({
|
|
|
selector: 'app-project-loader',
|
|
|
@@ -58,20 +96,46 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
wecorp: WxworkCorp | null = null;
|
|
|
|
|
|
// 上下文数据
|
|
|
- currentUser: FmodeObject | null = null; // Profile 或 UserSocial
|
|
|
+ currentUser: FmodeObject | null = null; // Profile
|
|
|
currentChat: WxworkCurrentChat | null = null;
|
|
|
- chatType: 'group' | 'contact' | 'none' = 'none';
|
|
|
+ chatType: 'group' | 'contact' | 'personal' = 'personal';
|
|
|
groupChat: FmodeObject | null = null; // GroupChat
|
|
|
contact: FmodeObject | null = null; // ContactInfo
|
|
|
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;
|
|
|
defaultProjectName: string = '';
|
|
|
projectName: string = '';
|
|
|
creating: boolean = false;
|
|
|
-
|
|
|
- // 历史项目(当前群聊无项目时展示)
|
|
|
historyProjects: FmodeObject[] = [];
|
|
|
|
|
|
constructor(
|
|
|
@@ -97,7 +161,7 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 加载数据主流程(参考 page-chat-context 实现)
|
|
|
+ * 加载数据主流程
|
|
|
*/
|
|
|
async loadData() {
|
|
|
try {
|
|
|
@@ -112,14 +176,13 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
|
|
|
wxdebug('1. SDK初始化完成', { cid: this.cid, appId: this.appId });
|
|
|
|
|
|
- // 2️⃣ 加载当前登录员工信息(由 WxworkAuthGuard 自动登录)
|
|
|
+ // 2️⃣ 加载当前登录员工信息
|
|
|
this.loadingMessage = '获取用户信息...';
|
|
|
try {
|
|
|
this.currentUser = await this.wxwork.getCurrentUser();
|
|
|
wxdebug('2. 获取当前用户成功', this.currentUser?.toJSON());
|
|
|
} catch (err) {
|
|
|
console.error('获取当前用户失败:', err);
|
|
|
- wxdebug('2. 获取当前用户失败', err);
|
|
|
throw new Error('获取用户信息失败,请重试');
|
|
|
}
|
|
|
|
|
|
@@ -133,60 +196,26 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
wxdebug('3. getCurrentChat失败', err);
|
|
|
}
|
|
|
|
|
|
- // 4️⃣ 根据场景同步数据
|
|
|
+ // 4️⃣ 根据场景处理
|
|
|
if (this.currentChat?.type === "chatId" && this.currentChat?.group) {
|
|
|
- // 群聊场景
|
|
|
- wxdebug('4. 检测到群聊场景', this.currentChat.group);
|
|
|
- this.loadingMessage = '同步群聊信息...';
|
|
|
- try {
|
|
|
+ // 群聊场景 - 保留原有逻辑
|
|
|
this.chatType = 'group';
|
|
|
this.groupChat = await this.wxwork.syncGroupChat(this.currentChat.group);
|
|
|
- wxdebug('5. 群聊同步完成', this.groupChat?.toJSON());
|
|
|
-
|
|
|
- // 处理群聊场景
|
|
|
await this.handleGroupChatScene();
|
|
|
- } catch (err) {
|
|
|
- console.error('群聊同步失败:', err);
|
|
|
- wxdebug('5. 群聊同步失败', err);
|
|
|
- throw new Error('群聊信息同步失败');
|
|
|
- }
|
|
|
} else if (this.currentChat?.type === "userId" && this.currentChat?.id) {
|
|
|
- // 联系人场景
|
|
|
- wxdebug('4. 检测到联系人场景', { id: this.currentChat.id });
|
|
|
- this.loadingMessage = '同步联系人信息...';
|
|
|
- try {
|
|
|
+ // 联系人场景 - 保留原有逻辑
|
|
|
this.chatType = 'contact';
|
|
|
-
|
|
|
- // 获取完整联系人信息
|
|
|
const contactInfo = await this.wecorp!.externalContact.get(this.currentChat.id);
|
|
|
- wxdebug('5. 获取完整联系人信息', contactInfo);
|
|
|
-
|
|
|
this.contact = await this.wxwork.syncContact(contactInfo);
|
|
|
- wxdebug('6. 联系人同步完成', this.contact?.toJSON());
|
|
|
-
|
|
|
- // 处理联系人场景
|
|
|
await this.handleContactScene();
|
|
|
- } catch (err) {
|
|
|
- console.error('联系人同步失败:', err);
|
|
|
- wxdebug('联系人同步失败', err);
|
|
|
- throw new Error('联系人信息同步失败');
|
|
|
- }
|
|
|
} else {
|
|
|
- // 未检测到有效场景
|
|
|
- wxdebug('4. 未检测到有效场景', {
|
|
|
- currentChat: this.currentChat,
|
|
|
- type: this.currentChat?.type,
|
|
|
- hasGroup: !!this.currentChat?.group,
|
|
|
- hasContact: !!this.currentChat?.contact,
|
|
|
- hasId: !!this.currentChat?.id
|
|
|
- });
|
|
|
- throw new Error('无法识别当前会话类型,请在群聊或联系人会话中打开');
|
|
|
+ // 个人看板场景(默认)
|
|
|
+ this.chatType = 'personal';
|
|
|
+ await this.loadPersonalBoard();
|
|
|
}
|
|
|
|
|
|
wxdebug('加载完成', {
|
|
|
chatType: this.chatType,
|
|
|
- hasGroupChat: !!this.groupChat,
|
|
|
- hasContact: !!this.contact,
|
|
|
hasCurrentUser: !!this.currentUser
|
|
|
});
|
|
|
|
|
|
@@ -199,61 +228,446 @@ 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() {
|
|
|
this.loadingMessage = '查询项目信息...';
|
|
|
-
|
|
|
- // 查询群聊关联的项目
|
|
|
const projectPointer = this.groupChat!.get('project');
|
|
|
|
|
|
if (projectPointer) {
|
|
|
- // 有项目,加载项目详情
|
|
|
let pid = projectPointer.id || projectPointer.objectId
|
|
|
try {
|
|
|
const query = new Parse.Query('Project');
|
|
|
query.include('contact', 'assignee');
|
|
|
this.project = await query.get(pid);
|
|
|
-
|
|
|
- wxdebug('找到项目', this.project.toJSON());
|
|
|
-
|
|
|
- // 跳转项目详情
|
|
|
await this.navigateToProjectDetail();
|
|
|
} catch (err) {
|
|
|
console.error('加载项目失败:', err);
|
|
|
- wxdebug('加载项目失败', err);
|
|
|
this.error = '项目已删除或无权访问';
|
|
|
}
|
|
|
} else {
|
|
|
- // 无项目,查询历史项目并显示创建引导
|
|
|
await this.loadHistoryProjects();
|
|
|
this.showCreateProjectGuide();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 处理联系人场景
|
|
|
- */
|
|
|
async handleContactScene() {
|
|
|
- wxdebug('联系人场景,跳转客户画像', {
|
|
|
- contactId: this.contact!.id,
|
|
|
- contactName: this.contact!.get('name')
|
|
|
- });
|
|
|
-
|
|
|
- // 跳转客户画像页面
|
|
|
await this.router.navigate(['/wxwork', this.cid, 'contact', this.contact!.id], {
|
|
|
- queryParams: {
|
|
|
- profileId: this.currentUser!.id
|
|
|
- }
|
|
|
+ queryParams: { profileId: this.currentUser!.id }
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 加载历史项目(当前群聊相关的其他项目)
|
|
|
- */
|
|
|
async loadHistoryProjects() {
|
|
|
try {
|
|
|
- // 通过 ProjectGroup 查询该群聊的所有项目
|
|
|
const pgQuery = new Parse.Query('ProjectGroup');
|
|
|
pgQuery.equalTo('groupChat', this.groupChat!.toPointer());
|
|
|
pgQuery.include('project');
|
|
|
@@ -263,53 +677,32 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
this.historyProjects = projectGroups
|
|
|
.map((pg: any) => pg.get('project'))
|
|
|
.filter((p: any) => p && !p.get('isDeleted'));
|
|
|
-
|
|
|
- wxdebug('找到历史项目', { count: this.historyProjects.length });
|
|
|
} catch (err) {
|
|
|
console.error('加载历史项目失败:', err);
|
|
|
- wxdebug('加载历史项目失败', err);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 显示创建项目引导
|
|
|
- */
|
|
|
showCreateProjectGuide() {
|
|
|
this.showCreateGuide = true;
|
|
|
this.defaultProjectName = this.groupChat!.get('name') || '新项目';
|
|
|
this.projectName = this.defaultProjectName;
|
|
|
- wxdebug('显示创建项目引导', {
|
|
|
- groupName: this.groupChat!.get('name'),
|
|
|
- historyProjectsCount: this.historyProjects.length
|
|
|
- });
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 创建项目
|
|
|
- */
|
|
|
async createProject() {
|
|
|
if (!this.projectName.trim()) {
|
|
|
- window?.fmode?.alert('请输入项目名称');
|
|
|
+ window?.fmode?.alert('请输入项目名称');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 权限检查
|
|
|
const role = this.currentUser!.get('roleName');
|
|
|
if (!['客服', '组长', '管理员'].includes(role)) {
|
|
|
- window?.fmode?.alert('您没有权限创建项目');
|
|
|
+ window?.fmode?.alert('您没有权限创建项目');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
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 = new Project();
|
|
|
|
|
|
@@ -324,14 +717,10 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
});
|
|
|
|
|
|
await project.save();
|
|
|
- wxdebug('项目创建成功', { projectId: project.id });
|
|
|
|
|
|
- // 2. 关联群聊
|
|
|
this.groupChat!.set('project', project.toPointer());
|
|
|
await this.groupChat!.save();
|
|
|
- wxdebug('群聊关联项目成功');
|
|
|
|
|
|
- // 3. 创建 ProjectGroup 关联(支持多项目多群)
|
|
|
const ProjectGroup = Parse.Object.extend('ProjectGroup');
|
|
|
const pg = new ProjectGroup();
|
|
|
pg.set('project', project.toPointer());
|
|
|
@@ -339,78 +728,48 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
pg.set('isPrimary', true);
|
|
|
pg.set('company', this.currentUser!.get('company'));
|
|
|
await pg.save();
|
|
|
- wxdebug('ProjectGroup关联创建成功');
|
|
|
|
|
|
- // 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);
|
|
|
- // 活动日志失败不影响主流程
|
|
|
- }
|
|
|
+ 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: '订单分配'
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- // 5. 跳转项目详情
|
|
|
this.project = project;
|
|
|
await this.navigateToProjectDetail();
|
|
|
} catch (err: any) {
|
|
|
console.error('创建项目失败:', err);
|
|
|
- wxdebug('创建项目失败', err);
|
|
|
- window?.fmode?.alert('创建失败: ' + (err.message || '未知错误'));
|
|
|
+ window?.fmode?.alert('创建失败: ' + (err.message || '未知错误'));
|
|
|
} finally {
|
|
|
this.creating = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 选择历史项目
|
|
|
- */
|
|
|
async selectHistoryProject(project: FmodeObject) {
|
|
|
try {
|
|
|
- wxdebug('选择历史项目', {
|
|
|
- projectId: project.id,
|
|
|
- projectTitle: project.get('title')
|
|
|
- });
|
|
|
-
|
|
|
- // 更新群聊的当前项目
|
|
|
this.groupChat!.set('project', project.toPointer());
|
|
|
await this.groupChat!.save();
|
|
|
-
|
|
|
- // 跳转项目详情
|
|
|
this.project = project;
|
|
|
await this.navigateToProjectDetail();
|
|
|
} catch (err: any) {
|
|
|
console.error('关联项目失败:', err);
|
|
|
- window?.fmode?.alert('关联失败: ' + (err.message || '未知错误'));
|
|
|
+ window?.fmode?.alert('关联失败: ' + (err.message || '未知错误'));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 跳转项目详情
|
|
|
- */
|
|
|
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], {
|
|
|
queryParams: {
|
|
|
groupId: this.groupChat?.id,
|
|
|
@@ -419,36 +778,26 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 重新加载
|
|
|
- */
|
|
|
+ // ==================== 工具方法 ====================
|
|
|
+
|
|
|
async reload() {
|
|
|
this.error = null;
|
|
|
this.showCreateGuide = false;
|
|
|
this.historyProjects = [];
|
|
|
- this.chatType = 'none';
|
|
|
+ this.chatType = 'personal';
|
|
|
await this.loadData();
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取当前员工姓名
|
|
|
- */
|
|
|
getCurrentUserName(): string {
|
|
|
if (!this.currentUser) return '未知';
|
|
|
return this.currentUser.get('name') || this.currentUser.get('userid') || '未知';
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取当前员工角色
|
|
|
- */
|
|
|
getCurrentUserRole(): string {
|
|
|
if (!this.currentUser) return '未知';
|
|
|
return this.currentUser.get('roleName') || '未知';
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取项目状态的显示样式类
|
|
|
- */
|
|
|
getProjectStatusClass(status: string): string {
|
|
|
const classMap: any = {
|
|
|
'待分配': 'status-pending',
|
|
|
@@ -460,12 +809,49 @@ export class ProjectLoaderComponent implements OnInit {
|
|
|
return classMap[status] || 'status-default';
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 格式化日期
|
|
|
- */
|
|
|
- formatDate(date: Date): string {
|
|
|
+ formatDate(date: Date | string): string {
|
|
|
if (!date) return '';
|
|
|
const d = new Date(date);
|
|
|
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);
|
|
|
+ }
|
|
|
}
|