164 lines
5.8 KiB
C#
164 lines
5.8 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using MilliSim.Common.Dtos;
|
|
using MilliSim.Common.Entities;
|
|
using MilliSim.Common.Models;
|
|
using MilliSim.Data;
|
|
using MilliSim.Services.Infrastructure;
|
|
|
|
namespace MilliSim.Services;
|
|
|
|
public class DashboardService : IDashboardService
|
|
{
|
|
private readonly MilliSimDbContext _db;
|
|
private readonly ICurrentUserService _currentUser;
|
|
private readonly IFileStorageService _fileStorage;
|
|
|
|
public DashboardService(MilliSimDbContext db, ICurrentUserService currentUser, IFileStorageService fileStorage)
|
|
{
|
|
_db = db;
|
|
_currentUser = currentUser;
|
|
_fileStorage = fileStorage;
|
|
}
|
|
|
|
public async Task<ApiResponse<DashboardData>> GetDashboardDataAsync()
|
|
{
|
|
var userId = _currentUser.UserId;
|
|
|
|
// ===== Stats =====
|
|
var projectCount = await _db.Projects.CountAsync();
|
|
var customerCount = await _db.Users.CountAsync(u => u.Role == UserRole.Customer);
|
|
|
|
var todayStart = DateTime.Now.Date;
|
|
var todayEnd = todayStart.AddDays(1);
|
|
var todayVisits = await _db.OperationLogs
|
|
.CountAsync(l => l.CreatedAt >= todayStart && l.CreatedAt < todayEnd);
|
|
|
|
var activeAuthorizations = await _db.Authorizations
|
|
.CountAsync(a => a.Status == AuthorizationStatus.Active);
|
|
|
|
var unreadMessages = await _db.Notifications
|
|
.Where(n => n.UserId == null || n.UserId == userId)
|
|
.Where(n => !_db.NotificationReads.Any(r => r.NotificationId == n.Id && r.UserId == userId))
|
|
.CountAsync();
|
|
|
|
// ===== Trends: 近 7 天 =====
|
|
var startDate = DateTime.Now.Date.AddDays(-6);
|
|
var endDate = DateTime.Now.Date.AddDays(1);
|
|
|
|
var visits = await _db.OperationLogs
|
|
.Where(l => l.CreatedAt >= startDate && l.CreatedAt < endDate)
|
|
.GroupBy(l => l.CreatedAt.Date)
|
|
.Select(g => new { Date = g.Key, Count = g.Count() })
|
|
.ToListAsync();
|
|
|
|
var experiences = await _db.ExperienceLogs
|
|
.Where(l => l.CreatedAt >= startDate && l.CreatedAt < endDate)
|
|
.GroupBy(l => l.CreatedAt.Date)
|
|
.Select(g => new { Date = g.Key, Count = g.Count() })
|
|
.ToListAsync();
|
|
|
|
var trends = new List<TrendData>();
|
|
for (var i = 0; i < 7; i++)
|
|
{
|
|
var date = startDate.AddDays(i);
|
|
trends.Add(new TrendData
|
|
{
|
|
Date = date.ToString("MM-dd"),
|
|
Visits = visits.FirstOrDefault(v => v.Date == date)?.Count ?? 0,
|
|
Experiences = experiences.FirstOrDefault(e => e.Date == date)?.Count ?? 0
|
|
});
|
|
}
|
|
|
|
// 如果没有真实数据则返回模拟数据
|
|
if (trends.Sum(t => t.Visits + t.Experiences) == 0)
|
|
{
|
|
var random = new Random();
|
|
foreach (var t in trends)
|
|
{
|
|
t.Visits = random.Next(50, 200);
|
|
t.Experiences = random.Next(10, 80);
|
|
}
|
|
}
|
|
|
|
// ===== CategoryDistribution: 按分类统计项目数 =====
|
|
var categoryDistribution = await _db.Projects
|
|
.Join(_db.ProjectCategories,
|
|
p => p.CategoryId,
|
|
c => c.Id,
|
|
(p, c) => new { c.Name })
|
|
.GroupBy(x => x.Name)
|
|
.Select(g => new CategoryDistribution
|
|
{
|
|
Name = g.Key,
|
|
Count = g.Count()
|
|
})
|
|
.ToListAsync();
|
|
|
|
// ===== RecentActivities: 当前用户最近 10 条操作日志 =====
|
|
var recentLogEntities = await _db.OperationLogs
|
|
.Where(l => l.UserId == userId)
|
|
.OrderByDescending(l => l.CreatedAt)
|
|
.Take(10)
|
|
.ToListAsync();
|
|
|
|
var recentUserIds = recentLogEntities.Select(l => l.UserId).Distinct().ToList();
|
|
var recentNicknames = await _db.Users
|
|
.Where(u => recentUserIds.Contains(u.Id))
|
|
.ToDictionaryAsync(u => u.Id, u => u.Nickname);
|
|
|
|
var recentActivities = recentLogEntities.Select(l => new OperationLogDto
|
|
{
|
|
Id = l.Id,
|
|
UserId = l.UserId,
|
|
Username = l.Username,
|
|
Nickname = recentNicknames.GetValueOrDefault(l.UserId) ?? string.Empty,
|
|
Module = l.Module,
|
|
Operation = l.Operation,
|
|
Detail = l.Detail,
|
|
Ip = l.Ip,
|
|
CreatedAt = l.CreatedAt
|
|
}).ToList();
|
|
|
|
// ===== HotProjects: 按 ExperienceCount 倒序取 6 个(封面需 COS 签名) =====
|
|
var hotProjectEntities = await _db.Projects
|
|
.Include(p => p.Category)
|
|
.OrderByDescending(p => p.ExperienceCount)
|
|
.Take(6)
|
|
.ToListAsync();
|
|
|
|
var hotProjects = hotProjectEntities.Select(p => new ProjectDto
|
|
{
|
|
Id = p.Id,
|
|
Name = p.Name,
|
|
CategoryId = p.CategoryId,
|
|
CategoryName = p.Category != null ? p.Category.Name : string.Empty,
|
|
CoverImage = _fileStorage.GetSignedUrl(p.CoverImage),
|
|
Description = p.Description,
|
|
Status = p.Status,
|
|
IsFeatured = p.IsFeatured,
|
|
ViewCount = p.ViewCount,
|
|
ExperienceCount = p.ExperienceCount,
|
|
FavoriteCount = p.FavoriteCount,
|
|
CreatedAt = p.CreatedAt
|
|
}).ToList();
|
|
|
|
var data = new DashboardData
|
|
{
|
|
Stats = new DashboardStats
|
|
{
|
|
ProjectCount = projectCount,
|
|
CustomerCount = customerCount,
|
|
TodayVisits = todayVisits,
|
|
ActiveAuthorizations = activeAuthorizations,
|
|
UnreadMessages = unreadMessages
|
|
},
|
|
Trends = trends,
|
|
CategoryDistribution = categoryDistribution,
|
|
RecentActivities = recentActivities,
|
|
HotProjects = hotProjects
|
|
};
|
|
|
|
return ApiResponse<DashboardData>.Success(data);
|
|
}
|
|
}
|