| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188 |
- // 客服工作台 - 对接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 { ProfileService } from '../../../services/profile.service';
- import { UrgentTaskService } from '../../../services/urgent-task.service';
- import { ActivityLogService } from '../../../services/activity-log.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', './dashboard-urgent-tasks-enhanced.scss', '../customer-service-styles.scss']
- })
- export class Dashboard implements OnInit, OnDestroy {
- // 数据看板统计
- stats = {
- totalProjects: signal(0), // 项目总数
- newConsultations: signal(0), // 新咨询数
- pendingAssignments: signal(0), // 待分配项目数(原待派单数)
- exceptionProjects: signal(0), // 异常项目数
- afterSalesCount: signal(0) // 售后服务数量
- };
-
- // 紧急任务列表
- 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;
- dueDate: Date;
- status: string;
- overdueDay: number;
- }>>([]);
-
- // 项目动态流
- projectUpdates = signal<(Project | CustomerFeedback)[]>([]);
-
- // 搜索关键词
- searchTerm = signal('');
-
- // 筛选后的项目更新
- filteredUpdates = computed(() => {
- if (!this.searchTerm()) return this.projectUpdates();
-
- return this.projectUpdates().filter(item => {
- if ('name' in item) {
- // 项目
- return item.name.toLowerCase().includes(this.searchTerm().toLowerCase()) ||
- item.customerName.toLowerCase().includes(this.searchTerm().toLowerCase()) ||
- item.status.toLowerCase().includes(this.searchTerm().toLowerCase());
- } else {
- // 反馈
- return 'content' in item && item.content.toLowerCase().includes(this.searchTerm().toLowerCase()) ||
- 'status' in item && item.status.toLowerCase().includes(this.searchTerm().toLowerCase());
- }
- });
- });
- currentDate = new Date();
-
- // 回到顶部按钮可见性信号
- showBackToTopSignal = signal(false);
- // 任务表单可见性
- isTaskFormVisible = signal(false);
-
- // 项目列表(用于下拉选择)
- projectList = signal<any[]>([]);
-
- // 空间列表(用于下拉选择)
- spaceList = signal<any[]>([]);
-
- // 团队成员列表(用于指派)
- teamMembers = signal<any[]>([]);
-
- // 新任务数据
- newTask: any = {
- title: '',
- description: '',
- projectId: '',
- spaceId: '',
- stage: '订单分配',
- region: '',
- priority: 'high',
- assigneeId: '',
- deadline: new Date()
- };
-
- // 用于日期时间输入的属性
- deadlineInput = '';
-
- // 预设快捷时长选项
- timePresets = [
- { label: '1小时内', hours: 1 },
- { label: '3小时内', hours: 3 },
- { label: '6小时内', hours: 6 },
- { label: '12小时内', hours: 12 },
- { label: '24小时内', hours: 24 }
- ];
-
- // 选中的预设时长
- selectedPreset = '';
-
- // 自定义时间弹窗可见性
- isCustomTimeVisible = false;
-
- // 自定义选择的日期和时间
- customDate = new Date();
- customTime = '';
-
- // 错误提示信息
- deadlineError = '';
-
- // 提交按钮是否禁用
- isSubmitDisabled = false;
-
- // 下拉框可见性
- deadlineDropdownVisible = false;
-
- // 日期范围限制
- get todayDate(): string {
- return new Date().toISOString().split('T')[0];
- }
-
- get sevenDaysLaterDate(): string {
- const date = new Date();
- date.setDate(date.getDate() + 7);
- return date.toISOString().split('T')[0];
- }
- constructor(
- private router: Router,
- private route: ActivatedRoute,
- private profileService: ProfileService,
- private urgentTaskService: UrgentTaskService,
- private activityLogService: ActivityLogService
- ) {}
- // 当前用户和公司信息
- 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 {
- await Promise.all([
- this.loadConsultationStats(),
- this.loadUrgentTasks(),
- this.loadProjectUpdates(),
- this.loadCRMQueues(),
- this.loadPendingFinalPaymentProjects()
- ]);
- console.log('✅ 客服仪表板数据加载完成');
- } catch (error) {
- console.error('❌ 客服仪表板数据加载失败:', error);
- throw error;
- }
- }
- // 加载咨询统计数据
- private async loadConsultationStats(): Promise<void> {
- try {
- 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);
- // 待分配项目数(阶段处于"订单分配"的项目)
- // 参考组长工作台的筛选逻辑:根据四大板块筛选
- // 订单分配阶段包括:order, pendingApproval, pendingAssignment, 订单分配, 待审批, 待分配
-
- // 查询所有项目,然后在客户端筛选(与组长工作台保持一致)
- const allProjectsQuery = this.createQuery('Project');
- allProjectsQuery.limit(1000); // 限制最多1000个项目
- const allProjects = await allProjectsQuery.find();
-
- // 使用与组长工作台相同的筛选逻辑
- const orderPhaseProjects = allProjects.filter(p => {
- const currentStage = p.get('currentStage') || '';
- const stage = p.get('stage') || '';
- const stageValue = (currentStage || stage).toString().trim().toLowerCase();
-
- // 订单分配阶段的所有变体(与组长工作台mapStageToCorePhase保持一致)
- const isOrderPhase = stageValue === 'order' ||
- stageValue === 'pendingapproval' ||
- stageValue === 'pendingassignment' ||
- stageValue === '订单分配' ||
- stageValue === '待审批' ||
- stageValue === '待分配';
-
- // 调试日志:输出每个项目的阶段信息
- if (isOrderPhase) {
- console.log(`📋 订单分配项目: ${p.get('title')}, currentStage="${currentStage}", stage="${stage}", 匹配值="${stageValue}"`);
- }
-
- return isOrderPhase;
- });
-
- const pendingAssignments = orderPhaseProjects.length;
- this.stats.pendingAssignments.set(pendingAssignments);
-
- console.log(`✅ 待分配项目统计: 总项目数=${allProjects.length}, 订单分配阶段项目数=${pendingAssignments}`);
- // 异常项目数(使用ProjectIssue表)
- const issueQuery = this.createQuery('ProjectIssue');
- issueQuery.equalTo('priority', 'high');
- issueQuery.equalTo('status', 'open');
- const exceptionProjects = await issueQuery.count();
- this.stats.exceptionProjects.set(exceptionProjects);
- // 售后服务数量(使用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(`✅ 咨询统计: 项目总数${totalProjects}, 新咨询${newConsultations}, 待分配${pendingAssignments}, 异常${exceptionProjects}, 售后${afterSalesCount}`);
- } catch (error) {
- console.error('❌ 咨询统计加载失败:', error);
- // 不抛出错误,允许其他数据继续加载
- }
- }
- // 降级到模拟数据
- private loadMockData(): void {
- console.warn('⚠️ 使用模拟数据');
- this.loadUrgentTasks();
- this.loadProjectUpdates();
- this.loadCRMQueues();
- // loadPendingFinalPaymentProjects 已改为异步真实数据查询
- }
- // 添加滚动事件处理方法
- private onScroll(): void {
- this.showBackToTopSignal.set(window.scrollY > 300);
- }
-
- // 添加显示回到顶部按钮的计算属性
- showBackToTop = computed(() => this.showBackToTopSignal());
- // 清理事件监听器
- ngOnDestroy(): void {
- window.removeEventListener('scroll', this.onScroll.bind(this));
- }
- // 添加scrollToTop方法
- scrollToTop(): void {
- window.scrollTo({
- top: 0,
- behavior: 'smooth'
- });
- }
- // 查看人员考勤
- viewAttendance(): void {
- this.router.navigate(['/hr/attendance']);
- }
-
- // 加载紧急任务
- private async loadUrgentTasks(): Promise<void> {
- try {
- // 使用UrgentTaskService加载紧急事项
- const result = await this.urgentTaskService.findUrgentTasks({
- isCompleted: false
- }, 1, 20);
-
- // 转换数据格式以兼容现有UI
- const formattedTasks: Task[] = result.tasks.map(task => ({
- id: task.id,
- projectId: task.projectId,
- projectName: task.projectName,
- title: task.title,
- stage: task.stage,
- deadline: task.deadline,
- isOverdue: task.isOverdue,
- isCompleted: task.isCompleted,
- priority: task.priority as 'high' | 'medium' | 'low',
- assignee: task.assigneeName,
- description: task.description || '',
- status: task.status
- }));
-
- this.urgentTasks.set(formattedTasks);
- console.log(`✅ 紧急任务加载完成: ${formattedTasks.length} 个任务`);
- } catch (error) {
- console.error('❌ 紧急任务加载失败:', error);
- this.urgentTasks.set([]);
- }
- }
- // 加载CRM队列数据(已隐藏,暂不使用真实数据)
- private loadCRMQueues(): void {
- // CRM功能暂时隐藏,后续开发时再从Parse查询真实数据
- // 可以从ProjectFeedback表查询客户反馈和咨询记录
- console.log('⏸️ CRM队列功能暂时隐藏');
- }
- // 查看全部咨询列表
- goToConsultationList(): void {
- this.router.navigate(['/customer-service/consultation-list']);
- }
-
- // 加载项目动态
- 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);
- // 不抛出错误,允许其他数据继续加载
- }
- }
- // 处理任务完成
- async markTaskAsCompleted(taskId: string): Promise<void> {
- try {
- const task = this.urgentTasks().find(t => t.id === taskId);
-
- await this.urgentTaskService.markAsCompleted(taskId);
-
- // 记录活动日志
- if (task) {
- try {
- const user = this.currentUser();
- await this.activityLogService.logActivity({
- actorId: user?.id || 'unknown',
- actorName: user?.get('name') || '客服',
- actorRole: user?.get('roleName') || 'customer_service',
- actionType: 'complete',
- module: 'urgent_task',
- entityType: 'UrgentTask',
- entityId: taskId,
- entityName: task.title,
- description: '完成了紧急事项',
- metadata: {
- priority: task.priority,
- projectName: task.projectName
- }
- });
- } catch (logError) {
- console.error('记录活动日志失败:', logError);
- }
- }
-
- // 重新加载任务列表
- await this.loadUrgentTasks();
- console.log('✅ 任务标记为已完成');
- } catch (error) {
- console.error('❌ 标记任务完成失败:', error);
- alert('操作失败,请稍后重试');
- }
- }
-
- // 删除任务
- async deleteTask(taskId: string): Promise<void> {
- if (!await window?.fmode?.confirm('确定要删除这个紧急事项吗?')) {
- return;
- }
-
- try {
- await this.urgentTaskService.deleteUrgentTask(taskId);
- // 重新加载任务列表
- await this.loadUrgentTasks();
- console.log('✅ 任务删除成功');
- } catch (error) {
- console.error('❌ 删除任务失败:', error);
- alert('删除失败,请稍后重试');
- }
- }
- // 处理派单操作
- handleAssignment(taskId: string): void {
- // 标记任务为处理中
- const task = this.urgentTasks().find(t => t.id === taskId);
- if (task) {
- // 初始化处理状态
- this.taskProcessingState.update(state => ({
- ...state,
- [task.id]: { inProgress: true, progress: 0 }
- }));
- // 模拟处理进度
- let progress = 0;
- const interval = setInterval(() => {
- progress += 10;
-
- this.taskProcessingState.update(state => ({
- ...state,
- [task.id]: { inProgress: progress < 100, progress }
- }));
- if (progress >= 100) {
- clearInterval(interval);
-
- // 处理完成后从列表中移除该任务
- this.urgentTasks.set(
- this.urgentTasks().filter(t => t.id !== task.id)
- );
-
- // 清除处理状态
- this.taskProcessingState.update(state => {
- const newState = { ...state };
- delete newState[task.id];
- return newState;
- });
- }
- }, 300);
- }
- // 更新统计数据
- this.stats.pendingAssignments.set(this.stats.pendingAssignments() - 1);
- }
- // 显示任务表单
- async showTaskForm(): Promise<void> {
- // 重置表单数据
- this.newTask = {
- title: '',
- description: '',
- projectId: '',
- spaceId: '',
- stage: '订单分配',
- region: '',
- priority: 'high',
- assigneeId: '',
- deadline: new Date()
- };
-
- // 重置相关状态
- this.deadlineError = '';
- this.isSubmitDisabled = false;
-
- // 计算并设置默认预设时长
- this.setDefaultPreset();
-
- // 加载下拉列表数据
- try {
- const [projects, members] = await Promise.all([
- this.urgentTaskService.getProjects(),
- this.urgentTaskService.getTeamMembers()
- ]);
-
- this.projectList.set(projects);
- this.teamMembers.set(members);
- this.spaceList.set([]); // 初始为空,等待选择项目后加载
- } catch (error) {
- console.error('加载下拉列表数据失败:', error);
- }
-
- // 显示表单
- this.isTaskFormVisible.set(true);
-
- // 添加iOS风格的面板显示动画
- setTimeout(() => {
- document.querySelector('.ios-panel')?.classList.add('ios-panel-visible');
- }, 10);
- }
-
- // 项目选择变化时加载空间列表
- async onProjectChange(projectId: string): Promise<void> {
- if (!projectId) {
- this.spaceList.set([]);
- return;
- }
-
- try {
- const spaces = await this.urgentTaskService.getProjectSpaces(projectId);
- this.spaceList.set(spaces);
- } catch (error) {
- console.error('加载空间列表失败:', error);
- this.spaceList.set([]);
- }
- }
-
- // 设置默认预设时长
- private setDefaultPreset(): void {
- const now = new Date();
- const todayEnd = new Date(now);
- todayEnd.setHours(23, 59, 59, 999);
-
- // 检查3小时后是否超过当天24:00
- const threeHoursLater = new Date(now.getTime() + 3 * 60 * 60 * 1000);
-
- if (threeHoursLater <= todayEnd) {
- // 3小时后未超过当天24:00,默认选中3小时内
- this.selectedPreset = '3';
- this.updatePresetDeadline(3);
- } else {
- // 3小时后超过当天24:00,默认选中当天24:00前
- this.selectedPreset = 'today';
- this.deadlineInput = todayEnd.toISOString().slice(0, 16);
- this.newTask.deadline = todayEnd;
- }
- }
-
- // 处理预设时长选择
- handlePresetSelection(preset: string): void {
- this.selectedPreset = preset;
- this.deadlineError = '';
-
- if (preset === 'custom') {
- // 打开自定义时间选择器
- this.openCustomTimePicker();
- } else if (preset === 'today') {
- // 设置为当天24:00前
- const now = new Date();
- const todayEnd = new Date(now);
- todayEnd.setHours(23, 59, 59, 999);
-
- this.deadlineInput = todayEnd.toISOString().slice(0, 16);
- this.newTask.deadline = todayEnd;
- } else {
- // 计算预设时长的截止时间
- const hours = parseInt(preset);
- this.updatePresetDeadline(hours);
- }
- }
-
- // 更新预设时长的截止时间
- private updatePresetDeadline(hours: number): void {
- const now = new Date();
- const deadline = new Date(now.getTime() + hours * 60 * 60 * 1000);
-
- this.deadlineInput = deadline.toISOString().slice(0, 16);
- this.newTask.deadline = deadline;
- }
-
- // 打开自定义时间选择器
- openCustomTimePicker(): void {
- // 重置自定义时间
- this.customDate = new Date();
- const now = new Date();
- this.customTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
-
- // 显示自定义时间弹窗
- this.isCustomTimeVisible = true;
-
- // 添加iOS风格的弹窗动画
- setTimeout(() => {
- document.querySelector('.custom-time-modal')?.classList.add('modal-visible');
- }, 10);
- }
-
- // 关闭自定义时间选择器
- closeCustomTimePicker(): void {
- // 添加iOS风格的弹窗关闭动画
- const modal = document.querySelector('.custom-time-modal');
- if (modal) {
- modal.classList.remove('modal-visible');
- setTimeout(() => {
- this.isCustomTimeVisible = false;
- }, 300);
- } else {
- this.isCustomTimeVisible = false;
- }
- }
-
- // 处理自定义时间选择
- handleCustomTimeSelection(): void {
- const [hours, minutes] = this.customTime.split(':').map(Number);
- const selectedDateTime = new Date(this.customDate);
- selectedDateTime.setHours(hours, minutes, 0, 0);
-
- // 验证选择的时间是否有效
- if (this.validateDeadline(selectedDateTime)) {
- this.deadlineInput = selectedDateTime.toISOString().slice(0, 16);
- this.newTask.deadline = selectedDateTime;
- this.closeCustomTimePicker();
- }
- }
-
- // 验证截止时间是否有效
- validateDeadline(deadline: Date): boolean {
- const now = new Date();
-
- if (deadline < now) {
- this.deadlineError = '截止时间不能早于当前时间,请重新选择';
- this.isSubmitDisabled = true;
- return false;
- }
-
- this.deadlineError = '';
- this.isSubmitDisabled = false;
- return true;
- }
-
- // 获取显示的截止时间文本
- getDisplayDeadline(): string {
- if (!this.deadlineInput) return '';
-
- try {
- const date = new Date(this.deadlineInput);
- return date.toLocaleString('zh-CN', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit'
- });
- } catch (error) {
- return '';
- }
- }
-
- // 隐藏任务表单
- hideTaskForm(): void {
- // 添加iOS风格的面板隐藏动画
- const panel = document.querySelector('.ios-panel');
- if (panel) {
- panel.classList.remove('ios-panel-visible');
- setTimeout(() => {
- this.isTaskFormVisible.set(false);
- }, 300);
- } else {
- this.isTaskFormVisible.set(false);
- }
- }
-
- // 处理添加任务表单提交
- async handleAddTaskSubmit(): Promise<void> {
- // 验证表单数据
- if (!this.newTask.title.trim() || !this.newTask.projectName.trim() || !this.deadlineInput || this.isSubmitDisabled) {
- // 在实际应用中,这里应该显示错误提示
- window?.fmode?.alert('请填写必填字段(任务标题、项目名称、截止时间)');
- return;
- }
-
- try {
- // 创建紧急事项
- const task = await this.urgentTaskService.createUrgentTask({
- title: this.newTask.title,
- description: this.newTask.description,
- projectId: this.newTask.projectId,
- spaceId: this.newTask.spaceId || undefined,
- stage: this.newTask.stage,
- region: this.newTask.region,
- priority: this.newTask.priority,
- assigneeId: this.newTask.assigneeId || undefined,
- deadline: new Date(this.deadlineInput)
- });
-
- // 记录活动日志
- try {
- const user = this.currentUser();
- const projectName = this.projectList().find(p => p.id === this.newTask.projectId)?.get('title') || '未知项目';
-
- await this.activityLogService.logActivity({
- actorId: user?.id || 'unknown',
- actorName: user?.get('name') || '客服',
- actorRole: user?.get('roleName') || 'customer_service',
- actionType: 'create',
- module: 'urgent_task',
- entityType: 'UrgentTask',
- entityId: task.id,
- entityName: this.newTask.title,
- description: '创建了紧急事项',
- metadata: {
- priority: this.newTask.priority,
- projectName: projectName,
- stage: this.newTask.stage,
- region: this.newTask.region,
- deadline: this.deadlineInput
- }
- });
- } catch (logError) {
- console.error('记录活动日志失败:', logError);
- }
-
- // 重新加载任务列表
- await this.loadUrgentTasks();
-
- console.log('✅ 紧急事项创建成功');
-
- // 隐藏表单
- this.hideTaskForm();
- } catch (error) {
- console.error('❌ 创建紧急事项失败:', error);
- alert('创建失败,请稍后重试');
- }
- }
-
- // 添加新的紧急事项
- addUrgentTask(): void {
- // 调用显示表单方法
- this.showTaskForm();
- }
- // 项目总数图标点击处理
- handleTotalProjectsClick(): void {
- console.log('导航到项目列表 - 显示所有项目');
- this.router.navigate(['/customer-service/project-list'], {
- queryParams: { filter: 'all' }
- });
- }
- // 新咨询数图标点击处理
- handleNewConsultationsClick(): void {
- this.navigateToDetail('consultations');
- }
- // 待分配数图标点击处理
- handlePendingAssignmentsClick(): void {
- console.log('导航到项目列表 - 显示待分配项目');
- this.router.navigate(['/customer-service/project-list'], {
- queryParams: { filter: 'pending' }
- });
- }
- // 异常项目图标点击处理
- handleExceptionProjectsClick(): void {
- this.navigateToDetail('exceptions');
- }
- handleAfterSalesClick(): void {
- this.router.navigate(['/customer-service/after-sales']);
- }
- // 导航到详情页
- private navigateToDetail(type: 'consultations' | 'assignments' | 'exceptions'): void {
- const routeMap = {
- consultations: '/customer-service/consultation-list',
- assignments: '/customer-service/assignment-list',
- exceptions: '/customer-service/exception-list'
- };
-
- console.log('导航到:', routeMap[type]);
- console.log('当前路由:', this.router.url);
-
- // 添加iOS风格页面过渡动画
- document.body.classList.add('ios-page-transition');
- setTimeout(() => {
- this.router.navigateByUrl(routeMap[type])
- .then(navResult => {
- console.log('导航结果:', navResult);
- if (!navResult) {
- console.error('导航失败,检查路由配置');
- }
- })
- .catch(err => {
- console.error('导航错误:', err);
- });
-
- setTimeout(() => {
- document.body.classList.remove('ios-page-transition');
- }, 300);
- }, 100);
- }
- // 格式化日期
- formatDate(date: Date | string): string {
- if (!date) return '';
- try {
- return new Date(date).toLocaleString('zh-CN', {
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit'
- });
- } catch (error) {
- console.error('日期格式化错误:', error);
- return '';
- }
- }
- // 添加安全获取客户名称的方法
- getCustomerName(update: Project | CustomerFeedback): string {
- if ('customerName' in update && update.customerName) {
- return update.customerName;
- } else if ('projectId' in update) {
- // 查找相关项目获取客户名称
- return '客户反馈';
- }
- return '未知客户';
- }
- // 优化的日期格式化方法
- getFormattedDate(update: Project | CustomerFeedback): string {
- if (!update) return '';
-
- if ('createdAt' in update && update.createdAt) {
- return this.formatDate(update.createdAt);
- } else if ('updatedAt' in update && update.updatedAt) {
- return this.formatDate(update.updatedAt);
- } else if ('deadline' in update && update.deadline) {
- return this.formatDate(update.deadline);
- }
- return '';
- }
- // 添加获取状态的安全方法
- getUpdateStatus(update: Project | CustomerFeedback): string {
- if ('status' in update && update.status) {
- return update.status;
- }
- return '已更新';
- }
- // 检查是否是项目更新
- isProjectUpdate(update: Project | CustomerFeedback): update is Project {
- return 'name' in update && 'status' in update;
- }
- // 检查是否有内容字段
- hasContent(update: Project | CustomerFeedback): boolean {
- return 'content' in update;
- }
- // 获取更新内容
- getUpdateContent(update: Project | CustomerFeedback): string {
- if ('content' in update) {
- return (update as CustomerFeedback).content;
- }
- return '';
- }
- // 处理搜索输入事件
- onSearchInput(event: Event): void {
- const target = event.target as HTMLInputElement;
- if (target) {
- this.searchTerm.set(target.value);
- }
- }
- // 添加getTaskStatus方法的正确实现
- getTaskStatus(task: Task): string {
- if (!task) return '未知状态';
- if (task.isCompleted) return '已完成';
- if (task.isOverdue) return '已逾期';
- return '进行中';
- }
- // 添加getUpdateStatusClass方法的正确实现
- getUpdateStatusClass(update: Project | CustomerFeedback): string {
- if ('name' in update) {
- // 项目
- switch (update.status) {
- case '进行中': return 'status-active';
- case '已完成': return 'status-completed';
- case '已暂停': return 'status-paused';
- default: return 'status-pending';
- }
- } else {
- // 反馈
- switch (update.status) {
- case '已解决': return 'status-completed';
- case '处理中': return 'status-active';
- default: return 'status-pending';
- }
- }
- }
- // 新增:加载待跟进尾款项目(从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(pendingProjects);
- console.log(`✅ 待跟进尾款项目加载完成: ${pendingProjects.length} 个项目`);
- } catch (error) {
- console.error('❌ 待跟进尾款项目加载失败:', error);
- // 不抛出错误,允许其他数据继续加载
- }
- }
- // 新增:格式化日期时间
- formatDateTime(date: Date): string {
- const now = new Date();
- const diffMs = now.getTime() - date.getTime();
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
- const diffMinutes = Math.floor(diffMs / (1000 * 60));
- if (diffMinutes < 60) {
- return `${diffMinutes}分钟前`;
- } else if (diffHours < 24) {
- return `${diffHours}小时前`;
- } else {
- return date.toLocaleDateString('zh-CN', {
- month: 'short',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit'
- });
- }
- }
- // 新增:获取支付状态文本
- getPaymentStatusText(status: string): string {
- switch (status) {
- case 'pending_followup': return '待跟进';
- case 'following_up': return '跟进中';
- case 'payment_completed': return '已支付';
- default: return '未知状态';
- }
- }
- // 新增:开始跟进尾款
- followUpFinalPayment(projectId: string): void {
- console.log(`开始跟进项目 ${projectId} 的尾款`);
- // 这里可以添加实际的跟进逻辑,比如发送消息、创建任务等
- // 导航到项目详情页或打开跟进对话框
- this.router.navigate(['/customer-service/project-detail', projectId]);
- }
- // 新增:查看项目详情
- viewProjectDetail(projectId: string): void {
- this.router.navigate(['/customer-service/project-detail', projectId]);
- }
- // 新增:一键发送大图
- sendLargeImages(projectId: string): void {
- const projects = this.pendingFinalPaymentProjects();
- const project = projects.find(p => p.projectId === projectId);
-
- if (!project) return;
-
- console.log(`正在为项目 ${projectId} 发送大图到企业微信...`);
-
- // 模拟发送过程
- setTimeout(() => {
- const updatedProjects = projects.map(p => {
- if (p.projectId === projectId) {
- return { ...p, largeImagesSent: true };
- }
- return p;
- });
- this.pendingFinalPaymentProjects.set(updatedProjects);
-
- console.log(`✅ 项目 ${projectId} 大图已成功发送到企业微信服务群`);
- console.log(`📱 已同步发送支付成功与大图交付通知`);
-
- window?.fmode?.alert(`🎉 大图发送成功!
- ✅ 已完成操作:
- • 大图已发送至企业微信服务群
- • 已通知客户支付成功
- • 已确认大图交付完成
- 项目:${project.projectName}
- 客户:${project.customerName}`);
- }, 2000);
- }
- }
|