|
|
@@ -1,72 +1,119 @@
|
|
|
-// 修复 OnDestroy 导入和使用
|
|
|
+// 客服工作台 - 对接Parse Server真实数据
|
|
|
import { Component, OnInit, OnDestroy, signal, computed } from '@angular/core';
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
import { RouterModule, Router, ActivatedRoute } from '@angular/router';
|
|
|
-import { ProjectService } from '../../../services/project.service';
|
|
|
-import { Project, Task, CustomerFeedback } from '../../../models/project.model';
|
|
|
-import { FmodeQuery } from 'fmode-ng/core';
|
|
|
-import { WxworkAuth } from 'fmode-ng/core';
|
|
|
+import { ProfileService } from '../../../services/profile.service';
|
|
|
+import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
|
|
|
+
|
|
|
+const Parse = FmodeParse.with('nova');
|
|
|
+
|
|
|
+// 项目数据接口
|
|
|
+interface ProjectData {
|
|
|
+ id: string;
|
|
|
+ title: string;
|
|
|
+ customerName: string;
|
|
|
+ customerPhone?: string;
|
|
|
+ status: string;
|
|
|
+ stage: string;
|
|
|
+ assigneeName?: string;
|
|
|
+ createdAt: Date;
|
|
|
+ updatedAt: Date;
|
|
|
+ deadline?: Date;
|
|
|
+ priority?: string;
|
|
|
+ description?: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 任务数据接口
|
|
|
+interface Task {
|
|
|
+ id: string;
|
|
|
+ projectId: string;
|
|
|
+ projectName: string;
|
|
|
+ title: string;
|
|
|
+ stage: string;
|
|
|
+ deadline: Date;
|
|
|
+ isOverdue: boolean;
|
|
|
+ isCompleted: boolean;
|
|
|
+ priority: 'high' | 'medium' | 'low';
|
|
|
+ assignee: string;
|
|
|
+ description?: string;
|
|
|
+ status: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 项目更新联合类型
|
|
|
+interface ProjectUpdate {
|
|
|
+ id: string;
|
|
|
+ name?: string;
|
|
|
+ customerName: string;
|
|
|
+ status: string;
|
|
|
+ updatedAt?: Date;
|
|
|
+ createdAt?: Date;
|
|
|
+}
|
|
|
+
|
|
|
+interface FeedbackUpdate {
|
|
|
+ id: string;
|
|
|
+ customerName: string;
|
|
|
+ content: string;
|
|
|
+ status: string;
|
|
|
+ createdAt: Date;
|
|
|
+ feedbackType: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 项目类型(用于项目动态)
|
|
|
+interface Project {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ customerName: string;
|
|
|
+ status: string;
|
|
|
+ updatedAt?: Date;
|
|
|
+ createdAt?: Date;
|
|
|
+ deadline?: Date;
|
|
|
+}
|
|
|
+
|
|
|
+// 客户反馈类型
|
|
|
+interface CustomerFeedback {
|
|
|
+ id: string;
|
|
|
+ projectId: string;
|
|
|
+ customerName: string;
|
|
|
+ content: string;
|
|
|
+ status: string;
|
|
|
+ createdAt: Date;
|
|
|
+}
|
|
|
|
|
|
@Component({
|
|
|
selector: 'app-dashboard',
|
|
|
standalone: true,
|
|
|
imports: [CommonModule, FormsModule, RouterModule],
|
|
|
templateUrl: './dashboard.html',
|
|
|
- styleUrls: ['./dashboard.scss', '../customer-service-styles.scss'],
|
|
|
- providers: [ProjectService]
|
|
|
+ styleUrls: ['./dashboard.scss', '../customer-service-styles.scss']
|
|
|
})
|
|
|
export class Dashboard implements OnInit, OnDestroy {
|
|
|
// 数据看板统计
|
|
|
stats = {
|
|
|
- newConsultations: signal(12),
|
|
|
- pendingAssignments: signal(5),
|
|
|
- exceptionProjects: signal(2),
|
|
|
- afterSalesCount: signal(15), // 售后服务数量
|
|
|
- // 新增核心指标
|
|
|
- conversionRateToday: signal(36), // 当日成交率(%)
|
|
|
- pendingComplaints: signal(3), // 待处理投诉数
|
|
|
- unRepliedConsultations: signal(7), // 未回复咨询数
|
|
|
- // 新客户触达统计
|
|
|
- newCustomerReachCount: signal(8), // 新客户待触达数量
|
|
|
- newCustomerConversionRate: signal(24), // 新客户转化率(%)
|
|
|
- // 老客户回访统计
|
|
|
- oldCustomerFollowUpCount: signal(6), // 老客户待回访数量
|
|
|
- oldCustomerRetentionRate: signal(78) // 老客户留存率(%)
|
|
|
+ totalProjects: signal(0), // 项目总数
|
|
|
+ newConsultations: signal(0), // 新咨询数
|
|
|
+ pendingAssignments: signal(0), // 待分配项目数(原待派单数)
|
|
|
+ exceptionProjects: signal(0), // 异常项目数
|
|
|
+ afterSalesCount: signal(0) // 售后服务数量
|
|
|
};
|
|
|
-
|
|
|
- // 新增:新客户触达/老客户回访列表(增强版本,包含客户标签和策略)
|
|
|
- newReachOutCustomers = signal<Array<{
|
|
|
- name: string;
|
|
|
- demandType: string;
|
|
|
- lastContactAt: Date;
|
|
|
- customerTag: 'value-sensitive' | 'price-sensitive';
|
|
|
- recommendedPhrase: string;
|
|
|
- caseStrategy: string;
|
|
|
- }>>([]);
|
|
|
- oldCustomerFollowUps = signal<Array<{
|
|
|
- name: string;
|
|
|
- demandType: string;
|
|
|
- lastContactAt: Date;
|
|
|
- customerTag: 'value-sensitive' | 'price-sensitive';
|
|
|
- recommendedPhrase: string;
|
|
|
- caseStrategy: string;
|
|
|
- }>>([]);
|
|
|
+
|
|
|
+ // 紧急任务列表
|
|
|
urgentTasks = signal<Task[]>([]);
|
|
|
|
|
|
// 任务处理状态
|
|
|
taskProcessingState = signal<Partial<Record<string, { inProgress: boolean; progress: number }>>>({});
|
|
|
|
|
|
- // 新增:待跟进尾款项目列表
|
|
|
+ // 新增:待跟进尾款项目列表(真实数据)
|
|
|
pendingFinalPaymentProjects = signal<Array<{
|
|
|
+ id: string;
|
|
|
projectId: string;
|
|
|
projectName: string;
|
|
|
customerName: string;
|
|
|
customerPhone: string;
|
|
|
finalPaymentAmount: number;
|
|
|
- notificationTime: Date;
|
|
|
- status: 'pending_followup' | 'following_up' | 'payment_completed';
|
|
|
- largeImagesSent?: boolean;
|
|
|
+ dueDate: Date;
|
|
|
+ status: string;
|
|
|
+ overdueDay: number;
|
|
|
}>>([]);
|
|
|
|
|
|
// 项目动态流
|
|
|
@@ -113,7 +160,8 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
isCompleted: false,
|
|
|
priority: 'high',
|
|
|
assignee: '当前用户',
|
|
|
- description: ''
|
|
|
+ description: '',
|
|
|
+ status: '待处理'
|
|
|
};
|
|
|
|
|
|
// 用于日期时间输入的属性
|
|
|
@@ -160,34 +208,69 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
- private projectService: ProjectService,
|
|
|
private router: Router,
|
|
|
- private activatedRoute: ActivatedRoute
|
|
|
- ) {
|
|
|
- this.loadProfile();
|
|
|
- }
|
|
|
-
|
|
|
- currentUser:any = {}
|
|
|
- async loadProfile(){
|
|
|
- let cid = localStorage.getItem("company");
|
|
|
- if(cid){
|
|
|
- let wwAuth = new WxworkAuth({cid:cid})
|
|
|
- let profile = await wwAuth.currentProfile();
|
|
|
- this.currentUser = {
|
|
|
- name: profile?.get("name") || profile?.get("mobile"),
|
|
|
- avatar: profile?.get("avatar"),
|
|
|
- roleName: profile?.get("roleName")
|
|
|
- }
|
|
|
+ private route: ActivatedRoute,
|
|
|
+ private profileService: ProfileService
|
|
|
+ ) {}
|
|
|
+
|
|
|
+ // 当前用户和公司信息
|
|
|
+ currentUser = signal<any>(null);
|
|
|
+ company = signal<any>(null);
|
|
|
+
|
|
|
+ // 初始化用户和公司信息
|
|
|
+ private async initializeUserAndCompany(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const profile = await this.profileService.getCurrentProfile();
|
|
|
+ this.currentUser.set(profile);
|
|
|
+
|
|
|
+ // 获取公司信息 - 映三色帐套
|
|
|
+ const companyQuery = new Parse.Query('Company');
|
|
|
+ companyQuery.equalTo('objectId', 'cDL6R1hgSi');
|
|
|
+ const company = await companyQuery.first();
|
|
|
+
|
|
|
+ if (!company) {
|
|
|
+ throw new Error('未找到公司信息');
|
|
|
}
|
|
|
+
|
|
|
+ this.company.set(company);
|
|
|
+ console.log('✅ 用户和公司信息初始化完成');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ 用户和公司信息初始化失败:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取公司指针
|
|
|
+ private getCompanyPointer(): any {
|
|
|
+ if (!this.company()) {
|
|
|
+ throw new Error('公司信息未加载');
|
|
|
}
|
|
|
+ return {
|
|
|
+ __type: 'Pointer',
|
|
|
+ className: 'Company',
|
|
|
+ objectId: this.company().id
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
+ // 创建带公司过滤的查询
|
|
|
+ private createQuery(className: string): any {
|
|
|
+ const query = new Parse.Query(className);
|
|
|
+ query.equalTo('company', this.getCompanyPointer());
|
|
|
+ query.notEqualTo('isDeleted', true);
|
|
|
+ return query;
|
|
|
+ }
|
|
|
|
|
|
async ngOnInit(): Promise<void> {
|
|
|
+ try {
|
|
|
+ await this.initializeUserAndCompany();
|
|
|
+ await this.loadDashboardData();
|
|
|
// 添加滚动事件监听
|
|
|
window.addEventListener('scroll', this.onScroll.bind(this));
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ 客服工作台初始化失败:', error);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-
|
|
|
// 加载仪表板数据
|
|
|
private async loadDashboardData(): Promise<void> {
|
|
|
try {
|
|
|
@@ -208,34 +291,45 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
// 加载咨询统计数据
|
|
|
private async loadConsultationStats(): Promise<void> {
|
|
|
try {
|
|
|
- // 新咨询数
|
|
|
- const consultationQuery = new FmodeQuery('Consultation');
|
|
|
- consultationQuery.equalTo('status', 'new');
|
|
|
- consultationQuery.greaterThanOrEqualTo('createdAt', new Date(new Date().setHours(0,0,0,0)));
|
|
|
+ const todayStart = new Date();
|
|
|
+ todayStart.setHours(0, 0, 0, 0);
|
|
|
+
|
|
|
+ // 项目总数
|
|
|
+ const totalProjectQuery = this.createQuery('Project');
|
|
|
+ const totalProjects = await totalProjectQuery.count();
|
|
|
+ this.stats.totalProjects.set(totalProjects);
|
|
|
+
|
|
|
+ // 新咨询数(今日新增的项目)
|
|
|
+ const consultationQuery = this.createQuery('Project');
|
|
|
+ consultationQuery.greaterThanOrEqualTo('createdAt', todayStart);
|
|
|
const newConsultations = await consultationQuery.count();
|
|
|
this.stats.newConsultations.set(newConsultations);
|
|
|
|
|
|
- // 待派单数
|
|
|
- consultationQuery.equalTo('status', 'pending_assignment');
|
|
|
- const pendingAssignments = await consultationQuery.count();
|
|
|
+ // 待分配项目数(状态为待分配且未分配设计师)
|
|
|
+ const pendingQuery = this.createQuery('Project');
|
|
|
+ pendingQuery.equalTo('status', '待分配');
|
|
|
+ pendingQuery.doesNotExist('assignee');
|
|
|
+ const pendingAssignments = await pendingQuery.count();
|
|
|
this.stats.pendingAssignments.set(pendingAssignments);
|
|
|
|
|
|
- // 异常项目数
|
|
|
- const projectQuery = new FmodeQuery('Project');
|
|
|
- projectQuery.equalTo('status', 'exception');
|
|
|
- const exceptionProjects = await projectQuery.count();
|
|
|
+ // 异常项目数(使用ProjectIssue表)
|
|
|
+ const issueQuery = this.createQuery('ProjectIssue');
|
|
|
+ issueQuery.equalTo('priority', 'high');
|
|
|
+ issueQuery.equalTo('status', 'open');
|
|
|
+ const exceptionProjects = await issueQuery.count();
|
|
|
this.stats.exceptionProjects.set(exceptionProjects);
|
|
|
|
|
|
- // 售后服务数量
|
|
|
- const afterSalesQuery = new FmodeQuery('AfterSales');
|
|
|
- afterSalesQuery.equalTo('status', 'pending');
|
|
|
- const afterSalesCount = await afterSalesQuery.count();
|
|
|
+ // 售后服务数量(使用ProjectFeedback表,类型为投诉的待处理反馈)
|
|
|
+ const feedbackQuery = this.createQuery('ProjectFeedback');
|
|
|
+ feedbackQuery.equalTo('status', 'pending');
|
|
|
+ feedbackQuery.equalTo('feedbackType', 'complaint');
|
|
|
+ const afterSalesCount = await feedbackQuery.count();
|
|
|
this.stats.afterSalesCount.set(afterSalesCount);
|
|
|
|
|
|
- console.log(`✅ 咨询统计: 新咨询${newConsultations}, 待派单${pendingAssignments}, 异常${exceptionProjects}, 售后${afterSalesCount}`);
|
|
|
+ console.log(`✅ 咨询统计: 项目总数${totalProjects}, 新咨询${newConsultations}, 待分配${pendingAssignments}, 异常${exceptionProjects}, 售后${afterSalesCount}`);
|
|
|
} catch (error) {
|
|
|
console.error('❌ 咨询统计加载失败:', error);
|
|
|
- throw error;
|
|
|
+ // 不抛出错误,允许其他数据继续加载
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -245,7 +339,7 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
this.loadUrgentTasks();
|
|
|
this.loadProjectUpdates();
|
|
|
this.loadCRMQueues();
|
|
|
- this.loadPendingFinalPaymentProjects();
|
|
|
+ // loadPendingFinalPaymentProjects 已改为异步真实数据查询
|
|
|
}
|
|
|
|
|
|
// 添加滚动事件处理方法
|
|
|
@@ -274,78 +368,94 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
this.router.navigate(['/hr/attendance']);
|
|
|
}
|
|
|
|
|
|
- // 修改loadUrgentTasks方法,添加status属性
|
|
|
- loadUrgentTasks(): void {
|
|
|
- // 从服务获取任务数据,筛选出紧急任务
|
|
|
- this.projectService.getTasks().subscribe(tasks => {
|
|
|
- const filteredTasks = tasks.map(task => ({...task, status: task.isOverdue ? '已逾期' : task.isCompleted ? '已完成' : '进行中'}))
|
|
|
- .filter(task => task.isOverdue || task.deadline.toDateString() === new Date().toDateString());
|
|
|
-
|
|
|
- this.urgentTasks.set(filteredTasks.sort((a, b) => {
|
|
|
- // 按紧急程度排序
|
|
|
- if (a.isOverdue && !b.isOverdue) return -1;
|
|
|
- if (!a.isOverdue && b.isOverdue) return 1;
|
|
|
+ // 加载紧急任务
|
|
|
+ private async loadUrgentTasks(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const now = new Date();
|
|
|
+ const todayEnd = new Date();
|
|
|
+ todayEnd.setHours(23, 59, 59, 999);
|
|
|
+ const urgentTasks: Task[] = [];
|
|
|
+
|
|
|
+ // 1. 查询今日截止的项目
|
|
|
+ const todayDeadlineQuery = this.createQuery('Project');
|
|
|
+ todayDeadlineQuery.equalTo('status', '进行中');
|
|
|
+ todayDeadlineQuery.greaterThanOrEqualTo('deadline', now);
|
|
|
+ todayDeadlineQuery.lessThanOrEqualTo('deadline', todayEnd);
|
|
|
+ todayDeadlineQuery.include(['contact', 'assignee']);
|
|
|
+ todayDeadlineQuery.limit(10);
|
|
|
+ const todayProjects = await todayDeadlineQuery.find();
|
|
|
+
|
|
|
+ for (const project of todayProjects) {
|
|
|
+ const contact = project.get('contact');
|
|
|
+ const assignee = project.get('assignee');
|
|
|
+ urgentTasks.push({
|
|
|
+ id: project.id,
|
|
|
+ projectId: project.id,
|
|
|
+ projectName: project.get('title') || '未命名项目',
|
|
|
+ title: `项目截止:${project.get('title')}`,
|
|
|
+ stage: project.get('currentStage') || '进行中',
|
|
|
+ deadline: project.get('deadline'),
|
|
|
+ isOverdue: false,
|
|
|
+ isCompleted: false,
|
|
|
+ priority: 'high',
|
|
|
+ assignee: assignee?.get('name') || '未分配',
|
|
|
+ description: `客户:${contact?.get('name') || '未知'}`,
|
|
|
+ status: '进行中'
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 查询逾期项目
|
|
|
+ const overdueQuery = this.createQuery('Project');
|
|
|
+ overdueQuery.equalTo('status', '进行中');
|
|
|
+ overdueQuery.lessThan('deadline', now);
|
|
|
+ overdueQuery.include(['contact', 'assignee']);
|
|
|
+ overdueQuery.limit(5);
|
|
|
+ const overdueProjects = await overdueQuery.find();
|
|
|
+
|
|
|
+ for (const project of overdueProjects) {
|
|
|
+ const contact = project.get('contact');
|
|
|
+ const assignee = project.get('assignee');
|
|
|
+ urgentTasks.push({
|
|
|
+ id: project.id + '_overdue',
|
|
|
+ projectId: project.id,
|
|
|
+ projectName: project.get('title') || '未命名项目',
|
|
|
+ title: `逾期项目:${project.get('title')}`,
|
|
|
+ stage: project.get('currentStage') || '进行中',
|
|
|
+ deadline: project.get('deadline'),
|
|
|
+ isOverdue: true,
|
|
|
+ isCompleted: false,
|
|
|
+ priority: 'high',
|
|
|
+ assignee: assignee?.get('name') || '未分配',
|
|
|
+ description: `客户:${contact?.get('name') || '未知'}`,
|
|
|
+ status: '逾期'
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按优先级和截止时间排序
|
|
|
+ urgentTasks.sort((a, b) => {
|
|
|
+ if (a.isOverdue !== b.isOverdue) {
|
|
|
+ return a.isOverdue ? -1 : 1;
|
|
|
+ }
|
|
|
+ if (a.priority !== b.priority) {
|
|
|
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
|
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
|
|
|
+ }
|
|
|
return a.deadline.getTime() - b.deadline.getTime();
|
|
|
- }));
|
|
|
- });
|
|
|
+ });
|
|
|
+
|
|
|
+ this.urgentTasks.set(urgentTasks);
|
|
|
+ console.log(`✅ 紧急任务加载完成: ${urgentTasks.length} 个任务`);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ 紧急任务加载失败:', error);
|
|
|
+ // 不抛出错误,允许其他数据继续加载
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // 加载新客户触达与老客户回访数据(示例数据,后续可接入接口)
|
|
|
+ // 加载CRM队列数据(已隐藏,暂不使用真实数据)
|
|
|
private loadCRMQueues(): void {
|
|
|
- const now = new Date();
|
|
|
- this.newReachOutCustomers.set([
|
|
|
- {
|
|
|
- name: '陈女士',
|
|
|
- demandType: '全屋定制',
|
|
|
- lastContactAt: new Date(now.getTime() - 2 * 60 * 60 * 1000),
|
|
|
- customerTag: 'value-sensitive',
|
|
|
- recommendedPhrase: '我们的全屋定制方案注重品质与设计的完美结合,为您打造独一无二的家居空间',
|
|
|
- caseStrategy: '推荐高端别墅案例,强调设计理念和材料品质'
|
|
|
- },
|
|
|
- {
|
|
|
- name: '赵先生',
|
|
|
- demandType: '厨房改造',
|
|
|
- lastContactAt: new Date(now.getTime() - 26 * 60 * 60 * 1000),
|
|
|
- customerTag: 'price-sensitive',
|
|
|
- recommendedPhrase: '我们的厨房改造方案性价比极高,在预算范围内实现最大化的功能提升',
|
|
|
- caseStrategy: '推荐经济实用案例,突出成本控制和实用功能'
|
|
|
- },
|
|
|
- {
|
|
|
- name: '吴先生',
|
|
|
- demandType: '客厅软装',
|
|
|
- lastContactAt: new Date(now.getTime() - 5 * 60 * 60 * 1000),
|
|
|
- customerTag: 'value-sensitive',
|
|
|
- recommendedPhrase: '我们的软装设计师将为您量身定制,打造有品味的生活空间',
|
|
|
- caseStrategy: '推荐精品软装案例,强调设计师专业度和美学价值'
|
|
|
- }
|
|
|
- ]);
|
|
|
-
|
|
|
- this.oldCustomerFollowUps.set([
|
|
|
- {
|
|
|
- name: '王女士',
|
|
|
- demandType: '别墅整装',
|
|
|
- lastContactAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000),
|
|
|
- customerTag: 'value-sensitive',
|
|
|
- recommendedPhrase: '感谢您对我们的信任,我们将继续为您提供高品质的服务体验',
|
|
|
- caseStrategy: '展示同档次别墅案例,强调服务品质和后续保障'
|
|
|
- },
|
|
|
- {
|
|
|
- name: '李先生',
|
|
|
- demandType: '卧室升级',
|
|
|
- lastContactAt: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000),
|
|
|
- customerTag: 'price-sensitive',
|
|
|
- recommendedPhrase: '我们为老客户准备了特别优惠,让您以更实惠的价格享受升级服务',
|
|
|
- caseStrategy: '推荐性价比升级方案,提供老客户专属优惠'
|
|
|
- },
|
|
|
- {
|
|
|
- name: '孙女士',
|
|
|
- demandType: '卫生间翻新',
|
|
|
- lastContactAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000),
|
|
|
- customerTag: 'value-sensitive',
|
|
|
- recommendedPhrase: '基于您之前的项目经验,我们为您推荐更加精致的翻新方案',
|
|
|
- caseStrategy: '展示精品卫生间案例,强调细节工艺和材料升级'
|
|
|
- }
|
|
|
- ]);
|
|
|
+ // CRM功能暂时隐藏,后续开发时再从Parse查询真实数据
|
|
|
+ // 可以从ProjectFeedback表查询客户反馈和咨询记录
|
|
|
+ console.log('⏸️ CRM队列功能暂时隐藏');
|
|
|
}
|
|
|
|
|
|
// 查看全部咨询列表
|
|
|
@@ -353,23 +463,62 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
this.router.navigate(['/customer-service/consultation-list']);
|
|
|
}
|
|
|
|
|
|
- loadProjectUpdates(): void {
|
|
|
- // 模拟项目更新数据
|
|
|
- this.projectService.getProjects().subscribe(projects => {
|
|
|
- this.projectService.getCustomerFeedbacks().subscribe(feedbacks => {
|
|
|
- // 合并项目和反馈,按时间倒序排序
|
|
|
- const updates: (Project | CustomerFeedback)[] = [
|
|
|
- ...projects,
|
|
|
- ...feedbacks
|
|
|
- ].sort((a, b) => {
|
|
|
- const dateA = 'createdAt' in a ? a.createdAt : new Date(a['updatedAt'] || a['deadline']);
|
|
|
- const dateB = 'createdAt' in b ? b.createdAt : new Date(b['updatedAt'] || b['deadline']);
|
|
|
- return dateB.getTime() - dateA.getTime();
|
|
|
- }).slice(0, 20); // 限制显示20条
|
|
|
-
|
|
|
- this.projectUpdates.set(updates);
|
|
|
+ // 加载项目动态
|
|
|
+ private async loadProjectUpdates(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const updates: (Project | CustomerFeedback)[] = [];
|
|
|
+
|
|
|
+ // 1. 查询最新更新的项目
|
|
|
+ const projectQuery = this.createQuery('Project');
|
|
|
+ projectQuery.include(['contact', 'assignee']);
|
|
|
+ projectQuery.descending('updatedAt');
|
|
|
+ projectQuery.limit(10);
|
|
|
+ const projects = await projectQuery.find();
|
|
|
+
|
|
|
+ for (const project of projects) {
|
|
|
+ const contact = project.get('contact');
|
|
|
+ updates.push({
|
|
|
+ id: project.id,
|
|
|
+ name: project.get('title') || '未命名项目',
|
|
|
+ customerName: contact?.get('name') || '未知客户',
|
|
|
+ status: project.get('status') || '进行中',
|
|
|
+ updatedAt: project.get('updatedAt'),
|
|
|
+ createdAt: project.get('createdAt')
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 查询最新客户反馈
|
|
|
+ const feedbackQuery = this.createQuery('ProjectFeedback');
|
|
|
+ feedbackQuery.include(['contact', 'project']);
|
|
|
+ feedbackQuery.descending('createdAt');
|
|
|
+ feedbackQuery.limit(10);
|
|
|
+ const feedbacks = await feedbackQuery.find();
|
|
|
+
|
|
|
+ for (const feedback of feedbacks) {
|
|
|
+ const contact = feedback.get('contact');
|
|
|
+ updates.push({
|
|
|
+ id: feedback.id,
|
|
|
+ projectId: feedback.get('project')?.id || '',
|
|
|
+ customerName: contact?.get('name') || '未知客户',
|
|
|
+ content: feedback.get('content') || '无内容',
|
|
|
+ status: feedback.get('status') || 'pending',
|
|
|
+ createdAt: feedback.get('createdAt')
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按时间排序
|
|
|
+ updates.sort((a, b) => {
|
|
|
+ const aTime = ('updatedAt' in a && a.updatedAt) ? a.updatedAt.getTime() : (a.createdAt?.getTime() || 0);
|
|
|
+ const bTime = ('updatedAt' in b && b.updatedAt) ? b.updatedAt.getTime() : (b.createdAt?.getTime() || 0);
|
|
|
+ return bTime - aTime;
|
|
|
});
|
|
|
- });
|
|
|
+
|
|
|
+ this.projectUpdates.set(updates);
|
|
|
+ console.log(`✅ 项目动态加载完成: ${updates.length} 条动态`);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ 项目动态加载失败:', error);
|
|
|
+ // 不抛出错误,允许其他数据继续加载
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 处理任务完成
|
|
|
@@ -438,7 +587,8 @@ export class Dashboard implements OnInit, OnDestroy {
|
|
|
isCompleted: false,
|
|
|
priority: 'high',
|
|
|
assignee: '当前用户',
|
|
|
- description: ''
|
|
|
+ description: '',
|
|
|
+ status: '待处理'
|
|
|
};
|
|
|
|
|
|
// 重置相关状态
|
|
|
@@ -788,43 +938,65 @@ onSearchInput(event: Event): void {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 新增:加载待跟进尾款项目
|
|
|
- loadPendingFinalPaymentProjects(): void {
|
|
|
- // 模拟数据,实际应该从API获取
|
|
|
- const mockProjects = [
|
|
|
- {
|
|
|
- projectId: 'P001',
|
|
|
- projectName: '现代简约客厅设计',
|
|
|
- customerName: '张女士',
|
|
|
- customerPhone: '138****8888',
|
|
|
- finalPaymentAmount: 15000,
|
|
|
- notificationTime: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2小时前
|
|
|
- status: 'pending_followup' as const,
|
|
|
- largeImagesSent: false
|
|
|
- },
|
|
|
- {
|
|
|
- projectId: 'P002',
|
|
|
- projectName: '北欧风格卧室装修',
|
|
|
- customerName: '李先生',
|
|
|
- customerPhone: '139****9999',
|
|
|
- finalPaymentAmount: 22000,
|
|
|
- notificationTime: new Date(Date.now() - 4 * 60 * 60 * 1000), // 4小时前
|
|
|
- status: 'following_up' as const,
|
|
|
- largeImagesSent: false
|
|
|
- },
|
|
|
- {
|
|
|
- projectId: 'P003',
|
|
|
- projectName: '工业风办公室设计',
|
|
|
- customerName: '王总',
|
|
|
- customerPhone: '137****7777',
|
|
|
- finalPaymentAmount: 35000,
|
|
|
- notificationTime: new Date(Date.now() - 6 * 60 * 60 * 1000), // 6小时前
|
|
|
- status: 'payment_completed' as const,
|
|
|
- largeImagesSent: false
|
|
|
+ // 新增:加载待跟进尾款项目(从Parse真实数据)
|
|
|
+ private async loadPendingFinalPaymentProjects(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const now = new Date();
|
|
|
+ const pendingProjects: Array<{
|
|
|
+ id: string;
|
|
|
+ projectId: string;
|
|
|
+ projectName: string;
|
|
|
+ customerName: string;
|
|
|
+ customerPhone: string;
|
|
|
+ finalPaymentAmount: number;
|
|
|
+ dueDate: Date;
|
|
|
+ status: string;
|
|
|
+ overdueDay: number;
|
|
|
+ }> = [];
|
|
|
+
|
|
|
+ // 查询所有待付款的尾款记录
|
|
|
+ const paymentQuery = this.createQuery('ProjectPayment');
|
|
|
+ paymentQuery.equalTo('type', 'final'); // 尾款类型
|
|
|
+ paymentQuery.containedIn('status', ['pending', 'overdue']); // 待付款或逾期状态
|
|
|
+ paymentQuery.include(['project', 'paidBy']); // 关联项目和付款人信息
|
|
|
+ paymentQuery.descending('dueDate'); // 按应付时间倒序
|
|
|
+ paymentQuery.limit(20);
|
|
|
+
|
|
|
+ const payments = await paymentQuery.find();
|
|
|
+
|
|
|
+ for (const payment of payments) {
|
|
|
+ const project = payment.get('project');
|
|
|
+ const paidBy = payment.get('paidBy');
|
|
|
+ const dueDate = payment.get('dueDate');
|
|
|
+ const amount = payment.get('amount');
|
|
|
+ const status = payment.get('status');
|
|
|
+
|
|
|
+ if (project && paidBy) {
|
|
|
+ // 计算逾期天数
|
|
|
+ const overdueDays = status === 'overdue'
|
|
|
+ ? Math.floor((now.getTime() - dueDate.getTime()) / (1000 * 60 * 60 * 24))
|
|
|
+ : 0;
|
|
|
+
|
|
|
+ pendingProjects.push({
|
|
|
+ id: payment.id,
|
|
|
+ projectId: project.id,
|
|
|
+ projectName: project.get('title') || '未命名项目',
|
|
|
+ customerName: paidBy.get('name') || '未知客户',
|
|
|
+ customerPhone: paidBy.get('mobile') || '无电话',
|
|
|
+ finalPaymentAmount: amount || 0,
|
|
|
+ dueDate: dueDate || new Date(),
|
|
|
+ status: status === 'overdue' ? '已逾期' : '待付款',
|
|
|
+ overdueDay: overdueDays
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
- ];
|
|
|
-
|
|
|
- this.pendingFinalPaymentProjects.set(mockProjects);
|
|
|
+
|
|
|
+ this.pendingFinalPaymentProjects.set(pendingProjects);
|
|
|
+ console.log(`✅ 待跟进尾款项目加载完成: ${pendingProjects.length} 个项目`);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ 待跟进尾款项目加载失败:', error);
|
|
|
+ // 不抛出错误,允许其他数据继续加载
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 新增:格式化日期时间
|
|
|
@@ -860,17 +1032,15 @@ onSearchInput(event: Event): void {
|
|
|
|
|
|
// 新增:开始跟进尾款
|
|
|
followUpFinalPayment(projectId: string): void {
|
|
|
- const projects = this.pendingFinalPaymentProjects();
|
|
|
- const updatedProjects = projects.map(project => {
|
|
|
- if (project.projectId === projectId) {
|
|
|
- return { ...project, status: 'following_up' as const };
|
|
|
- }
|
|
|
- return project;
|
|
|
- });
|
|
|
- this.pendingFinalPaymentProjects.set(updatedProjects);
|
|
|
-
|
|
|
console.log(`开始跟进项目 ${projectId} 的尾款`);
|
|
|
// 这里可以添加实际的跟进逻辑,比如发送消息、创建任务等
|
|
|
+ // 导航到项目详情页或打开跟进对话框
|
|
|
+ this.router.navigate(['/customer-service/project-detail', projectId]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增:查看项目详情
|
|
|
+ viewProjectDetail(projectId: string): void {
|
|
|
+ this.router.navigate(['/customer-service/project-detail', projectId]);
|
|
|
}
|
|
|
|
|
|
// 新增:一键发送大图
|