diff --git a/antis-ncc-admin/public/index.html b/antis-ncc-admin/public/index.html index 631c754..85113bb 100644 --- a/antis-ncc-admin/public/index.html +++ b/antis-ncc-admin/public/index.html @@ -8,6 +8,10 @@ <%= webpackConfig.name %> + + + + diff --git a/antis-ncc-admin/src/styles/index.scss b/antis-ncc-admin/src/styles/index.scss index 4dd01b1..cdfb3cc 100644 --- a/antis-ncc-admin/src/styles/index.scss +++ b/antis-ncc-admin/src/styles/index.scss @@ -10,7 +10,7 @@ body { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; - font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif; } // label { diff --git a/antis-ncc-admin/src/views/extend/storeDashboard/index.vue b/antis-ncc-admin/src/views/extend/storeDashboard/index.vue index ae7c4a9..2097749 100644 --- a/antis-ncc-admin/src/views/extend/storeDashboard/index.vue +++ b/antis-ncc-admin/src/views/extend/storeDashboard/index.vue @@ -74,6 +74,30 @@ formatMoney(storeData.Performance.NetPerformance) : '0.00' }}
+15.8%
+
+
生美业绩
+
¥{{ storeData && storeData.Performance && + storeData.Performance.LifeBeautyPerformance ? + formatMoney(storeData.Performance.LifeBeautyPerformance) : '0.00' }}
+
+12.5%
+
+
+
医美业绩
+
¥{{ storeData && storeData.Performance && + storeData.Performance.MedicalBeautyPerformance ? + formatMoney(storeData.Performance.MedicalBeautyPerformance) : '0.00' }}
+
+8.3%
+
+
+
科美业绩
+
¥{{ storeData && storeData.Performance && + storeData.Performance.TechBeautyPerformance ? + formatMoney(storeData.Performance.TechBeautyPerformance) : '0.00' }}
+
+2.1%
+
@@ -182,7 +206,7 @@ @@ -235,7 +259,7 @@ @@ -619,7 +643,10 @@ export default { RefundAmount: response.data.RefundAmount || 0, RefundCount: response.data.RefundCount || 0, RemainingRightsAmount: response.data.RemainingRightsAmount || 0, - TargetPerformance: response.data.TargetPerformance || 0 + TargetPerformance: response.data.TargetPerformance || 0, + LifeBeautyPerformance: response.data.LifeBeautyPerformance || 0, + MedicalBeautyPerformance: response.data.MedicalBeautyPerformance || 0, + TechBeautyPerformance: response.data.TechBeautyPerformance || 0 }, Operation: { HeadCount: response.data.HeadCount || 0, @@ -1901,17 +1928,49 @@ export default { \ No newline at end of file + + + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberRemainingItemsExportOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberRemainingItemsExportOutput.cs new file mode 100644 index 0000000..c2b89f5 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberRemainingItemsExportOutput.cs @@ -0,0 +1,85 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqKhxx +{ + /// + /// 会员剩余品项导出输出 + /// + public class MemberRemainingItemsExportOutput + { + /// + /// 会员ID + /// + public string memberId { get; set; } + + /// + /// 会员姓名 + /// + public string memberName { get; set; } + + /// + /// 手机号 + /// + public string phone { get; set; } + + /// + /// 归属门店 + /// + public string storeName { get; set; } + + /// + /// 开单品项ID + /// + public string billingItemId { get; set; } + + /// + /// 品项ID + /// + public string itemId { get; set; } + + /// + /// 品项名称 + /// + public string itemName { get; set; } + + /// + /// 品项单价 + /// + public decimal itemPrice { get; set; } + + /// + /// 来源类型 + /// + public string sourceType { get; set; } + + /// + /// 总购买数量 + /// + public decimal totalPurchased { get; set; } + + /// + /// 已耗卡数量 + /// + public decimal consumedCount { get; set; } + + /// + /// 已退卡数量 + /// + public decimal refundedCount { get; set; } + + /// + /// 已储扣数量 + /// + public decimal deductCount { get; set; } + + /// + /// 剩余数量 + /// + public decimal remainingCount { get; set; } + + /// + /// 备注 + /// + public string remark { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/StoreDashboardStatisticsOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/StoreDashboardStatisticsOutput.cs index 4381c10..eb375cd 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/StoreDashboardStatisticsOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/StoreDashboardStatisticsOutput.cs @@ -94,6 +94,21 @@ namespace NCC.Extend.Entitys.Dto.LqStoreDashboard /// 人均项目数(项目数/人头数) /// public decimal AvgProjectPerHead { get; set; } + + /// + /// 生美业绩(消耗业绩) + /// + public decimal LifeBeautyPerformance { get; set; } + + /// + /// 医美业绩(消耗业绩) + /// + public decimal MedicalBeautyPerformance { get; set; } + + /// + /// 科美业绩(消耗业绩) + /// + public decimal TechBeautyPerformance { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs index 012c63a..68d1ed8 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using Mapster; using Microsoft.AspNetCore.Mvc; @@ -40,6 +41,9 @@ using SqlSugar; using Yitter.IdGenerator; using NCC.Extend.Entitys.lq_kd_deductinfo; using Microsoft.AspNetCore.Authorization; +using NPOI.XSSF.UserModel; +using NPOI.SS.UserModel; +using System.Reflection; namespace NCC.Extend.LqKhxx { @@ -1249,6 +1253,389 @@ namespace NCC.Extend.LqKhxx } #endregion + #region 导出所有会员剩余品项 + /// + /// 导出所有会员剩余品项 + /// + /// + /// 导出所有会员的剩余品项信息到Excel文件 + /// + /// 示例请求: + /// ```http + /// GET /api/Extend/LqKhxx/Actions/ExportAllMemberRemainingItems + /// ``` + /// + /// 返回数据说明: + /// - 返回Excel文件下载链接 + /// - Excel文件包含所有会员的剩余品项信息 + /// + /// Excel文件下载信息 + /// 成功返回Excel文件下载链接 + /// 服务器错误 + [HttpGet("Actions/ExportAllMemberRemainingItems")] + public async Task ExportAllMemberRemainingItems() + { + try + { + _logger.LogInformation("开始导出所有会员剩余品项"); + + // 1. 查询所有有效会员 + var members = await _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new { x.Id, x.Khmc, x.Sjh, x.Gsmd }) + .ToListAsync(); + + if (members == null || !members.Any()) + { + throw NCCException.Oh("没有找到会员数据"); + } + + // 2. 批量查询门店信息 + var storeIds = members.Where(x => !string.IsNullOrEmpty(x.Gsmd)).Select(x => x.Gsmd).Distinct().ToList(); + var storeDict = new Dictionary(); + if (storeIds.Any()) + { + var stores = await _db.Queryable() + .Where(x => storeIds.Contains(x.Id)) + .Select(x => new { x.Id, x.Dm }) + .ToListAsync(); + storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? ""); + } + + var memberIds = members.Select(x => x.Id).ToList(); + + // 3. 批量查询所有开单品项数据 + var baseItems = await _db.Queryable() + .Where(x => memberIds.Contains(x.MemberId)) + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new + { + x.Id, + x.MemberId, + x.Px, + x.Pxmc, + x.Pxjg, + x.SourceType, + x.ProjectNumber, + x.Remark + }) + .ToListAsync(); + + if (!baseItems.Any()) + { + throw NCCException.Oh("没有找到品项数据"); + } + + var billingItemIds = baseItems.Select(x => x.Id).ToList(); + + // 4. 批量查询消费数据 + var consumedData = await _db.Queryable() + .Where(x => billingItemIds.Contains(x.BillingItemId)) + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .GroupBy(x => x.BillingItemId) + .Select(x => new + { + BillingItemId = x.BillingItemId, + TotalConsumed = SqlFunc.AggregateSum(x.OriginalProjectNumber) + }) + .ToListAsync(); + + // 5. 批量查询退卡数据 + var refundedData = await _db.Queryable() + .Where(x => billingItemIds.Contains(x.BillingItemId)) + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .GroupBy(x => x.BillingItemId) + .Select(x => new + { + BillingItemId = x.BillingItemId, + TotalRefunded = SqlFunc.AggregateSum(x.ProjectNumber) + }) + .ToListAsync(); + + // 6. 批量查询储扣数据 + var deductData = await _db.Queryable() + .Where(x => billingItemIds.Contains(x.DeductId)) + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .GroupBy(x => x.DeductId) + .Select(x => new + { + BillingItemId = x.DeductId, + TotalDeduct = SqlFunc.AggregateSum(x.ProjectNumber) + }) + .ToListAsync(); + + // 7. 构建字典以提高查询效率 + var memberDict = members.ToDictionary(x => x.Id, x => x); + + // 8. 组装导出数据 + var exportData = new List(); + foreach (var item in baseItems) + { + if (!memberDict.ContainsKey(item.MemberId)) continue; + + var member = memberDict[item.MemberId]; + var consumed = consumedData.FirstOrDefault(c => c.BillingItemId == item.Id)?.TotalConsumed ?? 0m; + var refunded = refundedData.FirstOrDefault(r => r.BillingItemId == item.Id)?.TotalRefunded ?? 0m; + var deduct = deductData.FirstOrDefault(d => d.BillingItemId == item.Id)?.TotalDeduct ?? 0m; + var remaining = item.ProjectNumber - consumed - refunded - deduct; + + // 只导出剩余数量不等于0的品项 + if (remaining != 0) + { + exportData.Add(new MemberRemainingItemsExportOutput + { + memberId = item.MemberId, + memberName = member.Khmc ?? "", + phone = member.Sjh ?? "", + storeName = !string.IsNullOrEmpty(member.Gsmd) && storeDict.ContainsKey(member.Gsmd) ? storeDict[member.Gsmd] : "", + billingItemId = item.Id, + itemId = item.Px ?? "", + itemName = item.Pxmc ?? "", + itemPrice = item.Pxjg, + sourceType = item.SourceType ?? "", + totalPurchased = item.ProjectNumber, + consumedCount = consumed, + refundedCount = refunded, + deductCount = deduct, + remainingCount = remaining, + remark = item.Remark ?? "" + }); + } + } + + // 9. 配置Excel导出 + ExcelConfig excelconfig = new ExcelConfig(); + excelconfig.FileName = "所有会员剩余品项_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx"; + excelconfig.HeadFont = "微软雅黑"; + excelconfig.HeadPoint = 10; + excelconfig.IsAllSizeColumn = true; + excelconfig.ColumnModel = new List + { + new ExcelColumnModel { Column = "memberId", ExcelColumn = "会员ID" }, + new ExcelColumnModel { Column = "memberName", ExcelColumn = "会员姓名" }, + new ExcelColumnModel { Column = "phone", ExcelColumn = "手机号" }, + new ExcelColumnModel { Column = "storeName", ExcelColumn = "归属门店" }, + new ExcelColumnModel { Column = "billingItemId", ExcelColumn = "开单品项ID" }, + new ExcelColumnModel { Column = "itemId", ExcelColumn = "品项ID" }, + new ExcelColumnModel { Column = "itemName", ExcelColumn = "品项名称" }, + new ExcelColumnModel { Column = "itemPrice", ExcelColumn = "品项单价" }, + new ExcelColumnModel { Column = "sourceType", ExcelColumn = "来源类型" }, + new ExcelColumnModel { Column = "totalPurchased", ExcelColumn = "总购买数量" }, + new ExcelColumnModel { Column = "consumedCount", ExcelColumn = "已耗卡数量" }, + new ExcelColumnModel { Column = "refundedCount", ExcelColumn = "已退卡数量" }, + new ExcelColumnModel { Column = "deductCount", ExcelColumn = "已储扣数量" }, + new ExcelColumnModel { Column = "remainingCount", ExcelColumn = "剩余数量" }, + new ExcelColumnModel { Column = "remark", ExcelColumn = "备注" } + }; + + // 10. 查找项目根目录并创建ExportFiles文件夹 + var baseDir = AppContext.BaseDirectory; + var projectRoot = baseDir; + var dir = new DirectoryInfo(baseDir); + while (dir != null && dir.Parent != null) + { + try + { + if (dir.GetDirectories(".git").Any() || dir.GetFiles("*.sln").Any()) + { + projectRoot = dir.FullName; + break; + } + } + catch + { + // 忽略访问错误,继续向上查找 + } + dir = dir.Parent; + } + + // 如果没找到 .git 或 .sln 目录,再查找包含 .sln 文件的目录 + if (projectRoot == baseDir) + { + dir = new DirectoryInfo(baseDir); + while (dir != null && dir.Parent != null) + { + try + { + if (dir.GetFiles("*.sln").Any()) + { + projectRoot = dir.FullName; + break; + } + } + catch + { + // 忽略访问错误,继续向上查找 + } + dir = dir.Parent; + } + } + + // 在项目根目录下创建 ExportFiles 文件夹 + var exportFilesPath = Path.Combine(projectRoot, "ExportFiles"); + if (!Directory.Exists(exportFilesPath)) + { + Directory.CreateDirectory(exportFilesPath); + } + + var addPath = Path.Combine(exportFilesPath, excelconfig.FileName); + + // 11. 导出Excel文件(支持多sheet,每个sheet最多65535行数据) + ExportToExcelWithMultipleSheets(exportData, excelconfig, addPath); + + var fileName = _userManager.UserId + "|" + addPath + "|xlsx"; + var output = new + { + name = excelconfig.FileName, + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") + }; + + _logger.LogInformation("导出所有会员剩余品项完成,共{Count}条记录", exportData.Count); + + return output; + } + catch (Exception ex) + { + _logger.LogError(ex, "导出所有会员剩余品项失败"); + throw NCCException.Oh($"导出所有会员剩余品项失败:{ex.Message}"); + } + } + + /// + /// 导出Excel文件(支持多sheet,每个sheet最多65535行数据) + /// + private void ExportToExcelWithMultipleSheets(List exportData, ExcelConfig excelConfig, string filePath) + { + const int maxRowsPerSheet = 65535; // 每个sheet最多65535行数据(不包括表头) + var workbook = new XSSFWorkbook(); + var type = typeof(MemberRemainingItemsExportOutput); + var properties = type.GetProperties(); + + // 创建样式 + var headStyle = workbook.CreateCellStyle(); + var headFont = workbook.CreateFont(); + headFont.FontHeightInPoints = (short)excelConfig.HeadPoint; + headFont.FontName = excelConfig.HeadFont; + headFont.Boldweight = (short)700; // 粗体 + headStyle.SetFont(headFont); + headStyle.Alignment = HorizontalAlignment.Left; + + var dateStyle = workbook.CreateCellStyle(); + var format = workbook.CreateDataFormat(); + dateStyle.DataFormat = format.GetFormat("yyyy-MM-dd HH:mm:ss"); + + // 计算列宽 + var columnWidths = new int[excelConfig.ColumnModel.Count]; + for (int i = 0; i < excelConfig.ColumnModel.Count; i++) + { + var columnName = excelConfig.ColumnModel[i].ExcelColumn; + columnWidths[i] = Encoding.UTF8.GetBytes(columnName).Length + 2; + } + + int dataIndex = 0; + int sheetIndex = 0; + + while (dataIndex < exportData.Count) + { + // 创建新的sheet + var sheetName = sheetIndex == 0 ? "Sheet1" : $"Sheet{sheetIndex + 1}"; + var sheet = workbook.CreateSheet(sheetName); + + // 创建表头 + var headerRow = sheet.CreateRow(0); + for (int col = 0; col < excelConfig.ColumnModel.Count; col++) + { + var cell = headerRow.CreateCell(col); + cell.SetCellValue(excelConfig.ColumnModel[col].ExcelColumn); + cell.CellStyle = headStyle; + sheet.SetColumnWidth(col, (columnWidths[col] + 1) * 256); + } + + // 填充数据 + int rowIndex = 1; + while (dataIndex < exportData.Count && rowIndex <= maxRowsPerSheet) + { + var item = exportData[dataIndex]; + var dataRow = sheet.CreateRow(rowIndex); + + for (int col = 0; col < excelConfig.ColumnModel.Count; col++) + { + var column = excelConfig.ColumnModel[col]; + var property = properties.FirstOrDefault(p => p.Name == column.Column); + if (property != null) + { + var cell = dataRow.CreateCell(col); + var value = property.GetValue(item); + SetCellValue(cell, value, property.PropertyType, dateStyle); + } + } + + rowIndex++; + dataIndex++; + } + + sheetIndex++; + } + + // 保存文件 + using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write)) + { + workbook.Write(fileStream); + } + } + + /// + /// 设置单元格值 + /// + private void SetCellValue(ICell cell, object value, Type propertyType, ICellStyle dateStyle) + { + if (value == null) + { + cell.SetCellValue(""); + return; + } + + var typeName = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>) + ? propertyType.GetGenericArguments()[0].ToString() + : propertyType.ToString(); + + switch (typeName) + { + case "System.String": + cell.SetCellValue(value.ToString()); + break; + case "System.DateTime": + if (value is DateTime dateTime) + { + cell.SetCellValue(dateTime); + cell.CellStyle = dateStyle; + } + else + { + cell.SetCellValue(""); + } + break; + case "System.Boolean": + cell.SetCellValue((bool)value); + break; + case "System.Int16": + case "System.Int32": + case "System.Int64": + case "System.Byte": + cell.SetCellValue(Convert.ToDouble(value)); + break; + case "System.Decimal": + case "System.Double": + case "System.Single": + cell.SetCellValue(Convert.ToDouble(value)); + break; + default: + cell.SetCellValue(value.ToString()); + break; + } + } + #endregion + #region 获取会员类型枚举内容 /// /// 获取会员类型枚举内容 diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs index d6b7e1f..7e57e8e 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs @@ -200,6 +200,25 @@ namespace NCC.Extend // 15. 计算人均项目数(项目数/人头数) var avgProjectPerHead = headCount > 0 ? projectCount / (decimal)headCount : 0m; + // 16. 计算各分类消耗业绩(生美、医美、科美) + var categoryPerformanceSql = $@" + SELECT + COALESCE(SUM(CASE WHEN COALESCE(xmzl.qt2, '其他') = '生美' THEN CAST(COALESCE(xhpx.F_TotalPrice, xhpx.pxjg * xhpx.F_ProjectNumber, 0) AS DECIMAL(18,2)) ELSE 0 END), 0) as LifeBeautyPerformance, + COALESCE(SUM(CASE WHEN COALESCE(xmzl.qt2, '其他') = '医美' THEN CAST(COALESCE(xhpx.F_TotalPrice, xhpx.pxjg * xhpx.F_ProjectNumber, 0) AS DECIMAL(18,2)) ELSE 0 END), 0) as MedicalBeautyPerformance, + COALESCE(SUM(CASE WHEN COALESCE(xmzl.qt2, '其他') = '科美' THEN CAST(COALESCE(xhpx.F_TotalPrice, xhpx.pxjg * xhpx.F_ProjectNumber, 0) AS DECIMAL(18,2)) ELSE 0 END), 0) as TechBeautyPerformance + FROM lq_xh_pxmx xhpx + INNER JOIN lq_xh_hyhk xh ON xhpx.F_ConsumeInfoId = xh.F_Id AND xh.F_IsEffective = 1 + LEFT JOIN lq_xmzl xmzl ON xhpx.px = xmzl.F_Id AND xmzl.F_IsEffective = 1 + WHERE xhpx.F_IsEffective = 1 + AND xh.md = '{input.StoreId}' + AND xh.hksj >= '{startDate:yyyy-MM-dd 00:00:00}' + AND xh.hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'"; + var categoryPerformanceResult = await _db.Ado.SqlQueryAsync(categoryPerformanceSql); + var categoryPerformance = categoryPerformanceResult?.FirstOrDefault(); + var lifeBeautyPerformance = categoryPerformance != null ? Convert.ToDecimal(categoryPerformance.LifeBeautyPerformance ?? 0) : 0m; + var medicalBeautyPerformance = categoryPerformance != null ? Convert.ToDecimal(categoryPerformance.MedicalBeautyPerformance ?? 0) : 0m; + var techBeautyPerformance = categoryPerformance != null ? Convert.ToDecimal(categoryPerformance.TechBeautyPerformance ?? 0) : 0m; + var result = new StoreDashboardStatisticsOutput { BillingPerformance = billingAmount, @@ -219,7 +238,10 @@ namespace NCC.Extend ProjectCount = Convert.ToInt32(projectCount), AvgAmountPerPerson = avgAmountPerPerson, AvgAmountPerProject = avgAmountPerProject, - AvgProjectPerHead = avgProjectPerHead + AvgProjectPerHead = avgProjectPerHead, + LifeBeautyPerformance = lifeBeautyPerformance, + MedicalBeautyPerformance = medicalBeautyPerformance, + TechBeautyPerformance = techBeautyPerformance }; _logger.LogInformation("门店驾驶舱统计数据查询完成,门店ID:{StoreId},开单业绩:{BillingPerformance},消耗业绩:{ConsumePerformance},完成率:{CompletionRate}%,净业绩:{NetPerformance},开单次数:{BillingCount},消耗次数:{ConsumeCount},退卡次数:{RefundCount}", diff --git a/netcore/src/Modularity/System/NCC.System.Interfaces/Permission/IAuthorizeService.cs b/netcore/src/Modularity/System/NCC.System.Interfaces/Permission/IAuthorizeService.cs index 3dbd477..7d82e63 100644 --- a/netcore/src/Modularity/System/NCC.System.Interfaces/Permission/IAuthorizeService.cs +++ b/netcore/src/Modularity/System/NCC.System.Interfaces/Permission/IAuthorizeService.cs @@ -46,6 +46,14 @@ namespace NCC.System.Interfaces.Permission Task> GetCurrentUserResourceAuthorize(string userId, bool isAdmin); /// + /// 当前用户App模块权限 + /// + /// 用户ID + /// 是否超管 + /// + Task> GetCurrentUserAppModuleAuthorize(string userId, bool isAdmin); + + /// /// 获取权限项ids /// /// 角色id diff --git a/netcore/src/Modularity/System/NCC.System/Service/Permission/AuthorizeService.cs b/netcore/src/Modularity/System/NCC.System/Service/Permission/AuthorizeService.cs index ce64751..0ceaa74 100644 --- a/netcore/src/Modularity/System/NCC.System/Service/Permission/AuthorizeService.cs +++ b/netcore/src/Modularity/System/NCC.System/Service/Permission/AuthorizeService.cs @@ -864,6 +864,63 @@ namespace NCC.System.Service.Permission } /// + /// 当前用户App模块权限 + /// + /// 用户ID + /// 是否超管 + /// + [NonAction] + public async Task> GetCurrentUserAppModuleAuthorize(string userId, bool isAdmin) + { + var output = new List(); + if (!isAdmin) + { + // 获取用户角色 + var user = _userRepository.FirstOrDefault(u => u.Id == userId && u.DeleteMark == null); + if (user == null || string.IsNullOrEmpty(user.RoleId)) return output; + + var roleArray = user.RoleId.Split(','); + // 验证角色是否启用且未删除 + var roleId = await _roleRepository.Entities.In(r => r.Id, roleArray) + .Where(r => r.EnabledMark.Equals(1) && r.DeleteMark == null) + .Select(r => r.Id) + .ToListAsync(); + + if (roleId.Count == 0) return output; + + // 获取角色拥有的模块权限项ID + var items = await _authorizeRepository.Entities + .In(a => a.ObjectId, roleId) + .Where(a => a.ItemType == "module") + .GroupBy(it => new { it.ItemId }) + .Select(it => new { it.ItemId }) + .ToListAsync(); + + if (items.Count == 0) return output; + + // 获取模块信息,并过滤Category为"App"的模块 + output = await _moduleRepository.Entities + .In(m => m.Id, items.Select(it => it.ItemId).ToArray()) + .Where(a => a.EnabledMark.Equals(1) + && a.DeleteMark == null + && a.Category == "App") + .OrderBy(o => o.SortCode) + .ToListAsync(); + } + else + { + // 管理员返回所有App模块 + output = await _moduleRepository + .Where(a => a.EnabledMark.Equals(1) + && a.DeleteMark == null + && a.Category == "App") + .OrderBy(o => o.SortCode) + .ToListAsync(); + } + return output; + } + + /// /// 当前用户模块权限资源 /// /// 用户ID diff --git a/netcore/src/Modularity/System/NCC.System/Service/Permission/UsersCurrentService.cs b/netcore/src/Modularity/System/NCC.System/Service/Permission/UsersCurrentService.cs index a955f1e..15cc069 100644 --- a/netcore/src/Modularity/System/NCC.System/Service/Permission/UsersCurrentService.cs +++ b/netcore/src/Modularity/System/NCC.System/Service/Permission/UsersCurrentService.cs @@ -156,6 +156,46 @@ namespace NCC.System.Service.Permission } /// + /// 获取当前用户App权限 + /// + /// + /// 获取当前登录用户的App权限列表,根据用户的角色获取对应的App模块权限 + /// + /// 权限获取逻辑: + /// 1. 获取用户关联的角色ID列表 + /// 2. 验证角色是否启用且未删除 + /// 3. 根据角色ID从权限表中获取模块权限项 + /// 4. 过滤Category为"App"的模块 + /// 5. 返回树形结构的App权限列表 + /// + /// 注意事项: + /// - 管理员用户返回所有App权限 + /// - 普通用户只返回其角色拥有的App权限 + /// - 只返回启用且未删除的模块 + /// + /// App权限树形列表 + /// 成功返回App权限列表 + [HttpGet("AppAuthorize")] + public async Task GetAppAuthorize() + { + var userId = _userManager.UserId; + var isAdmin = _userManager.IsAdministrator; + + // 获取App模块权限列表 + var appModuleList = await _authorizeService.GetCurrentUserAppModuleAuthorize(userId, isAdmin); + + if (appModuleList.Count == 0) + { + return new List(); + } + + // 转换为树形结构 + var appModuleTree = appModuleList.Adapt>().ToTree("-1"); + + return appModuleTree; + } + + /// /// 获取系统日志 /// /// 参数