|
@@ -1,7 +1,7 @@
|
|
|
import { Injectable } from '@angular/core';
|
|
|
-import { HttpClient } from '@angular/common/http';
|
|
|
-import { Observable, of } from 'rxjs';
|
|
|
-import { map } from 'rxjs/operators';
|
|
|
+import { Observable, from, forkJoin, of } from 'rxjs';
|
|
|
+import { map, catchError } from 'rxjs/operators';
|
|
|
+import { AdminDataService } from '../services/admin-data.service';
|
|
|
|
|
|
// 定义仪表盘统计数据接口
|
|
|
export interface DashboardStats {
|
|
@@ -19,97 +19,158 @@ export interface DashboardStats {
|
|
|
providedIn: 'root'
|
|
|
})
|
|
|
export class AdminDashboardService {
|
|
|
- constructor(private http: HttpClient) {}
|
|
|
+ constructor(private adminData: AdminDataService) {}
|
|
|
|
|
|
// 获取仪表盘统计数据
|
|
|
getDashboardStats(): Observable<DashboardStats> {
|
|
|
- // 在实际应用中,这里会调用后端API
|
|
|
- // return this.http.get<DashboardStats>('/api/admin/dashboard/stats')
|
|
|
- // .pipe(map(response => response));
|
|
|
-
|
|
|
- // 模拟API响应
|
|
|
- return of({
|
|
|
- totalProjects: 128,
|
|
|
- activeProjects: 86,
|
|
|
- completedProjects: 42,
|
|
|
- totalDesigners: 24,
|
|
|
- totalCustomers: 356,
|
|
|
- totalRevenue: 1258000,
|
|
|
- projectTrend: [
|
|
|
- { name: '1月', value: 18 },
|
|
|
- { name: '2月', value: 25 },
|
|
|
- { name: '3月', value: 32 },
|
|
|
- { name: '4月', value: 28 },
|
|
|
- { name: '5月', value: 42 },
|
|
|
- { name: '6月', value: 38 }
|
|
|
- ],
|
|
|
- revenueData: [
|
|
|
- { name: '第一季度', value: 350000 },
|
|
|
- { name: '第二季度', value: 420000 },
|
|
|
- { name: '第三季度', value: 488000 }
|
|
|
- ]
|
|
|
- });
|
|
|
+ return forkJoin({
|
|
|
+ totalProjects: from(this.adminData.count('Project')),
|
|
|
+ activeProjects: from(this.adminData.count('Project', q => q.equalTo('status', '进行中'))),
|
|
|
+ completedProjects: from(this.adminData.count('Project', q => q.equalTo('status', '已完成'))),
|
|
|
+ totalDesigners: from(this.adminData.count('Profile')),
|
|
|
+ totalCustomers: from(this.adminData.count('ContactInfo')),
|
|
|
+ totalRevenue: from(this.calculateTotalRevenue())
|
|
|
+ }).pipe(
|
|
|
+ map(stats => ({
|
|
|
+ ...stats,
|
|
|
+ projectTrend: [],
|
|
|
+ revenueData: []
|
|
|
+ })),
|
|
|
+ catchError(error => {
|
|
|
+ console.error('加载仪表盘统计失败:', error);
|
|
|
+ return of({
|
|
|
+ totalProjects: 0,
|
|
|
+ activeProjects: 0,
|
|
|
+ completedProjects: 0,
|
|
|
+ totalDesigners: 0,
|
|
|
+ totalCustomers: 0,
|
|
|
+ totalRevenue: 0,
|
|
|
+ projectTrend: [],
|
|
|
+ revenueData: []
|
|
|
+ });
|
|
|
+ })
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算总收入
|
|
|
+ private async calculateTotalRevenue(): Promise<number> {
|
|
|
+ try {
|
|
|
+ const settlements = await this.adminData.findAll('ProjectSettlement', {
|
|
|
+ additionalQuery: q => q.equalTo('status', '已结算')
|
|
|
+ });
|
|
|
+ return settlements.reduce((sum, s) => sum + (s.get('amount') || 0), 0);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('计算总收入失败:', error);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // 获取最近活动记录
|
|
|
+ // 获取最近活动记录 - 从ProjectChange和Project表获取
|
|
|
getRecentActivities(): Observable<any[]> {
|
|
|
- // 模拟数据
|
|
|
- return of([
|
|
|
- {
|
|
|
- id: '1',
|
|
|
- user: '系统',
|
|
|
- action: '创建了新项目',
|
|
|
- target: '现代简约风格三居室设计',
|
|
|
- targetType: 'project',
|
|
|
- time: '今天 10:30'
|
|
|
- },
|
|
|
- {
|
|
|
- id: '2',
|
|
|
- user: '张设计师',
|
|
|
- action: '完成了任务',
|
|
|
- target: '设计初稿',
|
|
|
- targetType: 'task',
|
|
|
- time: '今天 09:15'
|
|
|
- },
|
|
|
- {
|
|
|
- id: '3',
|
|
|
- user: '客服小李',
|
|
|
- action: '新增了客户',
|
|
|
- target: '王先生',
|
|
|
- targetType: 'customer',
|
|
|
- time: '昨天 16:45'
|
|
|
- },
|
|
|
- {
|
|
|
- id: '4',
|
|
|
- user: '系统',
|
|
|
- action: '完成了项目',
|
|
|
- target: '北欧风格两居室设计',
|
|
|
- targetType: 'project',
|
|
|
- time: '昨天 15:30'
|
|
|
- }
|
|
|
- ]);
|
|
|
+ return from(this.loadRecentActivities()).pipe(
|
|
|
+ catchError(error => {
|
|
|
+ console.error('加载最近活动失败:', error);
|
|
|
+ return of([]);
|
|
|
+ })
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private async loadRecentActivities(): Promise<any[]> {
|
|
|
+ const activities: any[] = [];
|
|
|
+
|
|
|
+ // 获取最近创建的项目
|
|
|
+ const recentProjects = await this.adminData.findAll('Project', {
|
|
|
+ limit: 10,
|
|
|
+ descending: 'createdAt',
|
|
|
+ include: ['customer']
|
|
|
+ });
|
|
|
+
|
|
|
+ recentProjects.forEach(project => {
|
|
|
+ activities.push({
|
|
|
+ id: project.id,
|
|
|
+ type: 'project_created',
|
|
|
+ title: project.get('title'),
|
|
|
+ customer: project.get('customer')?.get('name') || '未知客户',
|
|
|
+ time: project.get('createdAt')
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 按时间排序
|
|
|
+ activities.sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime());
|
|
|
+ return activities.slice(0, 20);
|
|
|
}
|
|
|
|
|
|
// 获取项目状态分布
|
|
|
getProjectStatusDistribution(): Observable<any[]> {
|
|
|
- // 模拟数据
|
|
|
- return of([
|
|
|
- { name: '进行中', value: 86, color: '#165DFF' },
|
|
|
- { name: '已完成', value: 42, color: '#00B42A' },
|
|
|
- { name: '已暂停', value: 15, color: '#FF7D00' },
|
|
|
- { name: '已延期', value: 5, color: '#F53F3F' }
|
|
|
- ]);
|
|
|
+ return from(this.loadProjectStatusDistribution()).pipe(
|
|
|
+ catchError(error => {
|
|
|
+ console.error('加载项目状态分布失败:', error);
|
|
|
+ return of([]);
|
|
|
+ })
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private async loadProjectStatusDistribution(): Promise<any[]> {
|
|
|
+ const statusList = ['待分配', '进行中', '已完成', '已暂停', '已延期', '已取消'];
|
|
|
+ const results = await Promise.all(
|
|
|
+ statusList.map(async status => ({
|
|
|
+ name: status,
|
|
|
+ value: await this.adminData.count('Project', q => q.equalTo('status', status)),
|
|
|
+ color: this.getStatusColor(status)
|
|
|
+ }))
|
|
|
+ );
|
|
|
+ return results.filter(r => r.value > 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private getStatusColor(status: string): string {
|
|
|
+ const colorMap: Record<string, string> = {
|
|
|
+ '待分配': '#FFAA00',
|
|
|
+ '进行中': '#165DFF',
|
|
|
+ '已完成': '#00B42A',
|
|
|
+ '已暂停': '#FF7D00',
|
|
|
+ '已延期': '#F53F3F',
|
|
|
+ '已取消': '#86909C'
|
|
|
+ };
|
|
|
+ return colorMap[status] || '#165DFF';
|
|
|
}
|
|
|
|
|
|
// 获取设计师工作量统计
|
|
|
getDesignerWorkloadStats(): Observable<any[]> {
|
|
|
- // 模拟数据
|
|
|
- return of([
|
|
|
- { name: '张设计师', completed: 18, inProgress: 8 },
|
|
|
- { name: '李设计师', completed: 15, inProgress: 6 },
|
|
|
- { name: '王设计师', completed: 12, inProgress: 5 },
|
|
|
- { name: '赵设计师', completed: 10, inProgress: 4 },
|
|
|
- { name: '陈设计师', completed: 9, inProgress: 3 }
|
|
|
- ]);
|
|
|
+ return from(this.loadDesignerWorkloadStats()).pipe(
|
|
|
+ catchError(error => {
|
|
|
+ console.error('加载设计师工作量统计失败:', error);
|
|
|
+ return of([]);
|
|
|
+ })
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private async loadDesignerWorkloadStats(): Promise<any[]> {
|
|
|
+ // 获取所有设计师
|
|
|
+ const designers = await this.adminData.findAll('Profile', {
|
|
|
+ additionalQuery: q => q.equalTo('roleName', '组员'),
|
|
|
+ limit: 10
|
|
|
+ });
|
|
|
+
|
|
|
+ const stats = await Promise.all(
|
|
|
+ designers.map(async designer => {
|
|
|
+ const completed = await this.adminData.count('Project', q => {
|
|
|
+ q.equalTo('assignee', designer.toPointer());
|
|
|
+ q.equalTo('status', '已完成');
|
|
|
+ });
|
|
|
+
|
|
|
+ const inProgress = await this.adminData.count('Project', q => {
|
|
|
+ q.equalTo('assignee', designer.toPointer());
|
|
|
+ q.equalTo('status', '进行中');
|
|
|
+ });
|
|
|
+
|
|
|
+ return {
|
|
|
+ name: designer.get('name'),
|
|
|
+ completed,
|
|
|
+ inProgress
|
|
|
+ };
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ return stats.sort((a, b) => b.completed - a.completed);
|
|
|
}
|
|
|
}
|