diff --git a/server/HoneyBox/src/HoneyBox.Admin.Business/Controllers/StatisticsController.cs b/server/HoneyBox/src/HoneyBox.Admin.Business/Controllers/StatisticsController.cs index 0bc4a579..872941ad 100644 --- a/server/HoneyBox/src/HoneyBox.Admin.Business/Controllers/StatisticsController.cs +++ b/server/HoneyBox/src/HoneyBox.Admin.Business/Controllers/StatisticsController.cs @@ -117,4 +117,38 @@ public class StatisticsController : BusinessControllerBase } }); } + + /// + /// 获取所有盒子的汇总统计数据 + /// + /// 盒子ID + /// 盒子名称 + /// 状态 + /// 盒子类型 + /// 开始时间 + /// 结束时间 + /// 汇总统计数据 + [HttpGet("summary-statistics")] + [BusinessPermission("statistics:view")] + public async Task GetSummaryStatistics( + [FromQuery] int? goodsId, + [FromQuery] string? title, + [FromQuery] int? status, + [FromQuery] int? type, + [FromQuery] DateTime? startTime, + [FromQuery] DateTime? endTime) + { + var request = new BoxSummaryStatisticsRequest + { + GoodsId = goodsId, + Title = title, + Status = status, + Type = type, + StartTime = startTime, + EndTime = endTime + }; + + var result = await _statisticsService.GetSummaryStatisticsAsync(request); + return Ok(new { code = 0, msg = "获取成功", data = result }); + } } diff --git a/server/HoneyBox/src/HoneyBox.Admin.Business/Models/Statistics/BoxStatisticsModels.cs b/server/HoneyBox/src/HoneyBox.Admin.Business/Models/Statistics/BoxStatisticsModels.cs index 2da48804..55958a28 100644 --- a/server/HoneyBox/src/HoneyBox.Admin.Business/Models/Statistics/BoxStatisticsModels.cs +++ b/server/HoneyBox/src/HoneyBox.Admin.Business/Models/Statistics/BoxStatisticsModels.cs @@ -233,4 +233,55 @@ public class BoxProfitSummary /// 总发货金额 /// public decimal TotalFhMoney { get; set; } + + /// + /// 总抽奖次数 + /// + public int TotalCjCount { get; set; } + + /// + /// 利润率 + /// + public decimal ProfitRate { get; set; } + + /// + /// 是否亏损 + /// + public bool IsNegative { get; set; } +} + +/// +/// 盒子汇总统计请求 +/// +public class BoxSummaryStatisticsRequest +{ + /// + /// 盒子ID + /// + public int? GoodsId { get; set; } + + /// + /// 盒子名称 + /// + public string? Title { get; set; } + + /// + /// 状态:0-下架,1-上架 + /// + public int? Status { get; set; } + + /// + /// 盒子类型 + /// + public int? Type { get; set; } + + /// + /// 开始时间 + /// + public DateTime? StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } } diff --git a/server/HoneyBox/src/HoneyBox.Admin.Business/Services/Interfaces/IStatisticsService.cs b/server/HoneyBox/src/HoneyBox.Admin.Business/Services/Interfaces/IStatisticsService.cs index dfd166f0..b9b0c819 100644 --- a/server/HoneyBox/src/HoneyBox.Admin.Business/Services/Interfaces/IStatisticsService.cs +++ b/server/HoneyBox/src/HoneyBox.Admin.Business/Services/Interfaces/IStatisticsService.cs @@ -45,4 +45,11 @@ public interface IStatisticsService /// 请求参数 /// 盒子利润统计列表 Task> GetBoxProfitListAsync(BoxProfitListRequest request); + + /// + /// 获取所有盒子的汇总统计数据 + /// + /// 请求参数 + /// 汇总统计数据 + Task GetSummaryStatisticsAsync(BoxSummaryStatisticsRequest request); } diff --git a/server/HoneyBox/src/HoneyBox.Admin.Business/Services/StatisticsService.cs b/server/HoneyBox/src/HoneyBox.Admin.Business/Services/StatisticsService.cs index c921c3ab..83613551 100644 --- a/server/HoneyBox/src/HoneyBox.Admin.Business/Services/StatisticsService.cs +++ b/server/HoneyBox/src/HoneyBox.Admin.Business/Services/StatisticsService.cs @@ -583,4 +583,152 @@ public class StatisticsService : IStatisticsService return PagedResult.Create(goods, total, request.Page, request.PageSize); } + + /// + /// 获取所有盒子的汇总统计数据 + /// + public async Task GetSummaryStatisticsAsync(BoxSummaryStatisticsRequest request) + { + // 构建盒子查询条件 + var goodsQuery = _dbContext.Goods + .AsNoTracking() + .Where(g => g.DeletedAt == null); + + if (request.GoodsId.HasValue && request.GoodsId > 0) + { + goodsQuery = goodsQuery.Where(g => g.Id == request.GoodsId); + } + + if (!string.IsNullOrWhiteSpace(request.Title)) + { + goodsQuery = goodsQuery.Where(g => g.Title.Contains(request.Title)); + } + + if (request.Status.HasValue) + { + goodsQuery = goodsQuery.Where(g => g.Status == request.Status); + } + + if (request.Type.HasValue && request.Type > 0) + { + goodsQuery = goodsQuery.Where(g => g.Type == request.Type); + } + + // 获取符合条件的盒子ID列表 + var goodsIds = await goodsQuery.Select(g => g.Id).ToListAsync(); + + if (!goodsIds.Any()) + { + return new BoxProfitSummary + { + TotalIncome = 0, + TotalCost = 0, + TotalProfit = 0, + TotalReMoney = 0, + TotalFhMoney = 0, + TotalCjCount = 0, + ProfitRate = 0, + IsNegative = false + }; + } + + // 获取测试用户ID列表 + var testUserIds = await _dbContext.Users + .Where(u => u.IsTest > 0 || u.Status == 2) + .Select(u => u.Id) + .ToListAsync(); + + if (!testUserIds.Any()) + { + testUserIds = new List { 0 }; + } + + // 构建时间范围条件(转换为Unix时间戳) + var hasTimeRange = request.StartTime.HasValue && request.EndTime.HasValue; + var startTimestamp = hasTimeRange ? (int)((DateTimeOffset)request.StartTime!.Value).ToUnixTimeSeconds() : 0; + var endTimestamp = hasTimeRange ? (int)((DateTimeOffset)request.EndTime!.Value).ToUnixTimeSeconds() : 0; + + // 查询1:获取消费金额(充值金额 + 余额消费) + var orderQuery = _dbContext.Orders + .Where(o => o.Status == 1) + .Where(o => o.Price > 0 || o.UseMoney > 0) + .Where(o => goodsIds.Contains(o.GoodsId)) + .Where(o => !testUserIds.Contains(o.UserId)); + + if (hasTimeRange) + { + orderQuery = orderQuery.Where(o => o.PayTime > startTimestamp && o.PayTime < endTimestamp); + } + + var priceSum = await orderQuery.SumAsync(o => (decimal?)o.Price) ?? 0; + var useMoneySum = await orderQuery.SumAsync(o => (decimal?)o.UseMoney) ?? 0; + var totalIncome = priceSum + useMoneySum; + + // 查询2:获取出货成本(奖品价值) + var scMoneyQuery = _dbContext.OrderItems + .Where(oi => oi.GoodsId.HasValue && goodsIds.Contains(oi.GoodsId.Value)) + .Where(oi => !testUserIds.Contains(oi.UserId)); + + if (hasTimeRange) + { + scMoneyQuery = scMoneyQuery.Where(oi => oi.CreatedAt > request.StartTime && oi.CreatedAt < request.EndTime); + } + + var totalCost = await scMoneyQuery.SumAsync(oi => (decimal?)oi.GoodslistMoney) ?? 0; + + // 查询3:获取兑换成本(已回收的奖品价值,status=1) + var reMoneyQuery = _dbContext.OrderItems + .Where(oi => oi.GoodsId.HasValue && goodsIds.Contains(oi.GoodsId.Value)) + .Where(oi => oi.Status == 1) + .Where(oi => !testUserIds.Contains(oi.UserId)); + + if (hasTimeRange) + { + reMoneyQuery = reMoneyQuery.Where(oi => oi.CreatedAt > request.StartTime && oi.CreatedAt < request.EndTime); + } + + var totalReMoney = await reMoneyQuery.SumAsync(oi => (decimal?)oi.GoodslistMoney) ?? 0; + + // 查询4:获取发货成本(已发货的奖品价值,status=2) + var fhMoneyQuery = _dbContext.OrderItems + .Where(oi => oi.GoodsId.HasValue && goodsIds.Contains(oi.GoodsId.Value)) + .Where(oi => oi.Status == 2) + .Where(oi => !testUserIds.Contains(oi.UserId)); + + if (hasTimeRange) + { + fhMoneyQuery = fhMoneyQuery.Where(oi => oi.CreatedAt > request.StartTime && oi.CreatedAt < request.EndTime); + } + + var totalFhMoney = await fhMoneyQuery.SumAsync(oi => (decimal?)oi.GoodslistMoney) ?? 0; + + // 查询5:获取抽奖次数 + var cjCountQuery = _dbContext.OrderItems + .Where(oi => oi.GoodsId.HasValue && goodsIds.Contains(oi.GoodsId.Value)) + .Where(oi => !testUserIds.Contains(oi.UserId)) + .Where(oi => oi.ParentGoodsListId == 0); + + if (hasTimeRange) + { + cjCountQuery = cjCountQuery.Where(oi => oi.CreatedAt > request.StartTime && oi.CreatedAt < request.EndTime); + } + + var totalCjCount = await cjCountQuery.CountAsync(); + + // 计算总利润和利润率 + var totalProfit = totalIncome - (totalCost - totalReMoney); + var profitRate = totalIncome > 0 ? Math.Round((totalProfit / totalIncome) * 100, 2) : 0; + + return new BoxProfitSummary + { + TotalIncome = totalIncome, + TotalCost = totalCost, + TotalProfit = totalProfit, + TotalReMoney = totalReMoney, + TotalFhMoney = totalFhMoney, + TotalCjCount = totalCjCount, + ProfitRate = profitRate, + IsNegative = totalProfit < 0 + }; + } } diff --git a/server/HoneyBox/src/HoneyBox.Admin.Business/Services/UserBusinessService.cs b/server/HoneyBox/src/HoneyBox.Admin.Business/Services/UserBusinessService.cs index 293edc24..d8779d51 100644 --- a/server/HoneyBox/src/HoneyBox.Admin.Business/Services/UserBusinessService.cs +++ b/server/HoneyBox/src/HoneyBox.Admin.Business/Services/UserBusinessService.cs @@ -1,8 +1,9 @@ -using HoneyBox.Admin.Business.Models; +using HoneyBox.Admin.Business.Models; using HoneyBox.Admin.Business.Models.User; using HoneyBox.Admin.Business.Services.Interfaces; using HoneyBox.Model.Data; using HoneyBox.Model.Entities; + using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -1083,14 +1084,14 @@ public class UserBusinessService : IUserBusinessService // 尝试解析为用户ID if (int.TryParse(keyword, out var keywordUserId)) { - query = query.Where(u => - u.Id == keywordUserId || + query = query.Where(u => + u.Id == keywordUserId || (u.Mobile != null && u.Mobile.Contains(keyword)) || u.Nickname.Contains(keyword)); } else { - query = query.Where(u => + query = query.Where(u => (u.Mobile != null && u.Mobile.Contains(keyword)) || u.Nickname.Contains(keyword)); } @@ -1099,6 +1100,7 @@ public class UserBusinessService : IUserBusinessService if (request.UserId.HasValue) { query = query.Where(u => u.Id == request.UserId.Value); + query = query.Where(u => u.Uid == request.UserId.ToString()); } if (!string.IsNullOrWhiteSpace(request.Mobile)) diff --git a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/api/business/statistics.ts b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/api/business/statistics.ts index d5a5e2d4..42e8b6aa 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/api/business/statistics.ts +++ b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/api/business/statistics.ts @@ -280,6 +280,12 @@ export interface BoxProfitSummary { totalReMoney: number /** 总发货金额 */ totalFhMoney: number + /** 总抽奖次数 */ + totalCjCount: number + /** 利润率 */ + profitRate: number + /** 是否亏损 */ + isNegative: boolean } /** @@ -320,3 +326,34 @@ export function getBoxProfitList(params: BoxProfitListParams): Promise> { + return request({ + url: `${STATISTICS_BASE_URL}/summary-statistics`, + method: 'get', + params + }) +} diff --git a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/business/statistics/box-profit.vue b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/business/statistics/box-profit.vue index 299f3828..8932765a 100644 --- a/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/business/statistics/box-profit.vue +++ b/server/HoneyBox/src/HoneyBox.Admin/admin-web/src/views/business/statistics/box-profit.vue @@ -38,6 +38,57 @@ + + + + + 汇总统计 + 刷新汇总 + + + + + + 总抽奖次数 + {{ summary.totalCjCount.toLocaleString() }} + + + + + 总收入 + ¥{{ summary.totalIncome.toFixed(2) }} + + + + + 总出货成本 + ¥{{ summary.totalCost.toFixed(2) }} + + + + + 总兑换金额 + ¥{{ summary.totalReMoney.toFixed(2) }} + + + + + 总发货金额 + ¥{{ summary.totalFhMoney.toFixed(2) }} + + + + + 总利润 + + ¥{{ summary.totalProfit.toFixed(2) }} + ({{ summary.profitRate.toFixed(2) }}%) + + + + + + @@ -127,7 +178,7 @@ import { ref, reactive, onMounted } from 'vue' import { Search, Refresh } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' -import { getBoxProfitList, getBoxStatistics, type BoxProfitItem } from '@/api/business/statistics' +import { getBoxProfitList, getBoxStatistics, getSummaryStatistics, type BoxProfitItem, type BoxProfitSummary } from '@/api/business/statistics' // 盒子类型选项 const boxTypes = [ @@ -156,6 +207,19 @@ const dateRange = ref<[string, string] | null>(null) const tableData = ref([]) const loading = ref(false) +// 汇总统计 +const summaryLoading = ref(false) +const summary = reactive({ + totalIncome: 0, + totalCost: 0, + totalProfit: 0, + totalReMoney: 0, + totalFhMoney: 0, + totalCjCount: 0, + profitRate: 0, + isNegative: false +}) + // 分页 const pagination = reactive({ page: 1, @@ -163,6 +227,32 @@ const pagination = reactive({ total: 0 }) +// 加载汇总统计 +async function loadSummary() { + summaryLoading.value = true + try { + const params: any = {} + if (searchForm.goodsId) params.goodsId = searchForm.goodsId + if (searchForm.title) params.title = searchForm.title + if (searchForm.status !== undefined) params.status = searchForm.status + if (searchForm.type) params.type = searchForm.type + if (dateRange.value) { + params.startTime = dateRange.value[0] + params.endTime = dateRange.value[1] + } + + const res = await getSummaryStatistics(params) as any + if (res.code === 0 && res.data) { + Object.assign(summary, res.data) + } + } catch (error) { + console.error('加载汇总统计失败:', error) + ElMessage.error('加载汇总统计失败') + } finally { + summaryLoading.value = false + } +} + // 加载列表数据 async function loadData() { loading.value = true @@ -225,6 +315,7 @@ async function loadBoxStats(row: BoxProfitItem) { function handleSearch() { pagination.page = 1 loadData() + loadSummary() } // 重置 @@ -236,6 +327,7 @@ function handleReset() { dateRange.value = null pagination.page = 1 loadData() + loadSummary() } // 分页大小变化 @@ -253,6 +345,7 @@ function handleCurrentChange(page: number) { onMounted(() => { loadData() + loadSummary() }) @@ -265,6 +358,38 @@ onMounted(() => { margin-bottom: 16px; } +.summary-card { + margin-bottom: 16px; +} + +.summary-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.stat-item { + text-align: center; + padding: 12px 0; +} + +.stat-label { + font-size: 14px; + color: #909399; + margin-bottom: 8px; +} + +.stat-value { + font-size: 20px; + font-weight: 600; + color: #303133; +} + +.profit-rate { + font-size: 14px; + font-weight: normal; +} + .table-card { margin-bottom: 16px; }