From dbc9750fb7b2adcaf347d831a5fffa609682ee27 Mon Sep 17 00:00:00 2001 From: 李曜臣 Date: Fri, 20 Mar 2026 16:54:05 +0800 Subject: [PATCH] 成员模块实现 --- label-template-template-1773988794039 (1).json | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/RbacRole/RbacRoleGetOutputDto.cs | 4 ++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberAssignedLocationDto.cs | 11 +++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberCreateInputVo.cs | 27 +++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs | 30 ++++++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListOutputDto.cs | 26 ++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetOutputDto.cs | 23 +++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberUpdateInputVo.cs | 24 ++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ITeamMemberAppService.cs | 18 ++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/RoleMenuDbEntity.cs | 18 ++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/UserLocationDbEntity.cs | 30 ++++++++++++++++++++++++++++++ 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs | 12 +++++++++++- 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs | 355 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 795 insertions(+), 1 deletion(-) create mode 100644 label-template-template-1773988794039 (1).json create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberAssignedLocationDto.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberCreateInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListOutputDto.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetOutputDto.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberUpdateInputVo.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ITeamMemberAppService.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/RoleMenuDbEntity.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/UserLocationDbEntity.cs create mode 100644 美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs diff --git a/label-template-template-1773988794039 (1).json b/label-template-template-1773988794039 (1).json new file mode 100644 index 0000000..bbbacba --- /dev/null +++ b/label-template-template-1773988794039 (1).json @@ -0,0 +1,218 @@ +{ + "id": "template-1773988794039", + "name": "未命名模板", + "labelType": "PRICE", + "unit": "inch", + "width": 4, + "height": 6, + "appliedLocation": "ALL", + "showRuler": true, + "showGrid": true, + "elements": [ + { + "id": "el-1773989351080-vqc03nr", + "type": "IMAGE", + "x": 32, + "y": 24, + "width": 60, + "height": 60, + "rotation": "horizontal", + "border": "none", + "config": { + "src": "", + "scaleMode": "contain" + } + }, + { + "id": "el-1773989452538-0ejrxoe", + "type": "TEXT_STATIC", + "x": 32, + "y": 104, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "文本", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "normal", + "textAlign": "left" + } + }, + { + "id": "el-1773989466493-ibbroio", + "type": "QRCODE", + "x": 32, + "y": 136, + "width": 80, + "height": 80, + "rotation": "horizontal", + "border": "none", + "config": { + "data": "https://example.com", + "errorLevel": "M" + } + }, + { + "id": "el-1773989469008-f1l39qj", + "type": "BARCODE", + "x": 0, + "y": 224, + "width": 160, + "height": 48, + "rotation": "horizontal", + "border": "none", + "config": { + "barcodeType": "CODE128", + "data": "123456789", + "showText": true, + "orientation": "horizontal" + } + }, + { + "id": "el-1773989473436-j7fdeh2", + "type": "BLANK", + "x": 32, + "y": 288, + "width": 48, + "height": 32, + "rotation": "horizontal", + "border": "none", + "config": {} + }, + { + "id": "el-1773989483341-ifwcyjj", + "type": "TEXT_PRICE", + "x": 152, + "y": 24, + "width": 80, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "0.00", + "prefix": "¥", + "decimal": 2, + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "bold", + "textAlign": "right" + } + }, + { + "id": "el-1773989498031-e4d61j8", + "type": "IMAGE", + "x": 192, + "y": 56, + "width": 60, + "height": 60, + "rotation": "horizontal", + "border": "none", + "config": { + "src": "", + "scaleMode": "contain" + } + }, + { + "id": "el-1773989505076-1lxccx7", + "type": "TEXT_PRODUCT", + "x": 200, + "y": 136, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "商品名", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "normal", + "textAlign": "left" + } + }, + { + "id": "el-1773989509805-ax3392v", + "type": "TEXT_STATIC", + "x": 192, + "y": 160, + "width": 120, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "文本", + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "normal", + "textAlign": "left" + } + }, + { + "id": "el-1773989512993-xt8bg7q", + "type": "QRCODE", + "x": 184, + "y": 184, + "width": 80, + "height": 80, + "rotation": "horizontal", + "border": "none", + "config": { + "data": "https://example.com", + "errorLevel": "M" + } + }, + { + "id": "el-1773989525383-eji8p2s", + "type": "BARCODE", + "x": 0, + "y": 288, + "width": 160, + "height": 48, + "rotation": "horizontal", + "border": "none", + "config": { + "barcodeType": "CODE128", + "data": "123456789", + "showText": true, + "orientation": "horizontal" + } + }, + { + "id": "el-1773989540159-dr2avdf", + "type": "NUTRITION", + "x": 184, + "y": 280, + "width": 200, + "height": 120, + "rotation": "horizontal", + "border": "none", + "config": { + "calories": 120, + "fat": "5g", + "protein": "3g", + "carbs": "10g", + "layout": "standard" + } + }, + { + "id": "el-1773989549679-mcxrdnw", + "type": "TEXT_PRICE", + "x": 24, + "y": 352, + "width": 80, + "height": 24, + "rotation": "horizontal", + "border": "none", + "config": { + "text": "0.00", + "prefix": "¥", + "decimal": 2, + "fontFamily": "Arial", + "fontSize": 14, + "fontWeight": "bold", + "textAlign": "right" + } + } + ] +} \ No newline at end of file diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/RbacRole/RbacRoleGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/RbacRole/RbacRoleGetOutputDto.cs index 877e1b0..c59c753 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/RbacRole/RbacRoleGetOutputDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/RbacRole/RbacRoleGetOutputDto.cs @@ -5,5 +5,9 @@ namespace FoodLabeling.Application.Contracts.Dtos.RbacRole; /// public class RbacRoleGetOutputDto : RbacRoleGetListOutputDto { + /// + /// 该角色已分配的菜单权限ID列表 + /// + public List MenuIds { get; set; } = new(); } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberAssignedLocationDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberAssignedLocationDto.cs new file mode 100644 index 0000000..062a805 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberAssignedLocationDto.cs @@ -0,0 +1,11 @@ +namespace FoodLabeling.Application.Contracts.Dtos.TeamMember; + +public class TeamMemberAssignedLocationDto +{ + public string Id { get; set; } = string.Empty; + + public string LocationCode { get; set; } = string.Empty; + + public string LocationName { get; set; } = string.Empty; +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberCreateInputVo.cs new file mode 100644 index 0000000..aedc9e6 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberCreateInputVo.cs @@ -0,0 +1,27 @@ +namespace FoodLabeling.Application.Contracts.Dtos.TeamMember; + +public class TeamMemberCreateInputVo +{ + public string FullName { get; set; } = string.Empty; + + /// + /// 登录账号(建议用邮箱或自定义用户名) + /// + public string UserName { get; set; } = string.Empty; + + public string Password { get; set; } = string.Empty; + + public string? Email { get; set; } + + public long? Phone { get; set; } + + public Guid? RoleId { get; set; } + + /// + /// 关联门店(至少1个) + /// + public List LocationIds { get; set; } = new(); + + public bool State { get; set; } = true; +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs new file mode 100644 index 0000000..55f2c29 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListInputVo.cs @@ -0,0 +1,30 @@ +using Volo.Abp.Application.Dtos; + +namespace FoodLabeling.Application.Contracts.Dtos.TeamMember; + +/// +/// 成员分页查询入参 +/// +public class TeamMemberGetListInputVo : PagedAndSortedResultRequestDto +{ + /// + /// 关键字(姓名/用户名/邮箱/电话) + /// + public string? Keyword { get; set; } + + /// + /// 角色ID(可选) + /// + public Guid? RoleId { get; set; } + + /// + /// 门店ID(可选) + /// + public string? LocationId { get; set; } + + /// + /// 启用状态(可选) + /// + public bool? State { get; set; } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListOutputDto.cs new file mode 100644 index 0000000..90a383c --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetListOutputDto.cs @@ -0,0 +1,26 @@ +namespace FoodLabeling.Application.Contracts.Dtos.TeamMember; + +public class TeamMemberGetListOutputDto +{ + public Guid Id { get; set; } + + public string FullName { get; set; } = string.Empty; + + public string UserName { get; set; } = string.Empty; + + public string? Email { get; set; } + + public long? Phone { get; set; } + + public bool State { get; set; } + + /// + /// 角色(当前仅返回第一个) + /// + public Guid? RoleId { get; set; } + + public string? RoleName { get; set; } + + public List AssignedLocations { get; set; } = new(); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetOutputDto.cs new file mode 100644 index 0000000..9daa71a --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberGetOutputDto.cs @@ -0,0 +1,23 @@ +namespace FoodLabeling.Application.Contracts.Dtos.TeamMember; + +public class TeamMemberGetOutputDto +{ + public Guid Id { get; set; } + + public string FullName { get; set; } = string.Empty; + + public string UserName { get; set; } = string.Empty; + + public string? Email { get; set; } + + public long? Phone { get; set; } + + public bool State { get; set; } + + public Guid? RoleId { get; set; } + + public List LocationIds { get; set; } = new(); + + public List AssignedLocations { get; set; } = new(); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberUpdateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberUpdateInputVo.cs new file mode 100644 index 0000000..f0e1e7d --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/TeamMember/TeamMemberUpdateInputVo.cs @@ -0,0 +1,24 @@ +namespace FoodLabeling.Application.Contracts.Dtos.TeamMember; + +public class TeamMemberUpdateInputVo +{ + public string FullName { get; set; } = string.Empty; + + public string UserName { get; set; } = string.Empty; + + /// + /// 为空表示不改密码 + /// + public string? Password { get; set; } + + public string? Email { get; set; } + + public long? Phone { get; set; } + + public Guid? RoleId { get; set; } + + public List LocationIds { get; set; } = new(); + + public bool State { get; set; } = true; +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ITeamMemberAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ITeamMemberAppService.cs new file mode 100644 index 0000000..72f4dfe --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/ITeamMemberAppService.cs @@ -0,0 +1,18 @@ +using FoodLabeling.Application.Contracts.Dtos.Common; +using FoodLabeling.Application.Contracts.Dtos.TeamMember; + +namespace FoodLabeling.Application.Contracts.IServices; + +public interface ITeamMemberAppService +{ + Task> GetListAsync(TeamMemberGetListInputVo input); + + Task GetAsync(Guid id); + + Task CreateAsync(TeamMemberCreateInputVo input); + + Task UpdateAsync(Guid id, TeamMemberUpdateInputVo input); + + Task DeleteAsync(Guid id); +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/RoleMenuDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/RoleMenuDbEntity.cs new file mode 100644 index 0000000..30e81d7 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/RoleMenuDbEntity.cs @@ -0,0 +1,18 @@ +using SqlSugar; + +namespace FoodLabeling.Application.Services.DbModels; + +/// +/// rolemenu 表映射(兼容字符串类型 RoleId/MenuId) +/// +[SugarTable("rolemenu")] +public class RoleMenuDbEntity +{ + [SugarColumn(IsPrimaryKey = true)] + public string Id { get; set; } = string.Empty; + + public string RoleId { get; set; } = string.Empty; + + public string MenuId { get; set; } = string.Empty; +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/UserLocationDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/UserLocationDbEntity.cs new file mode 100644 index 0000000..32a7db0 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/UserLocationDbEntity.cs @@ -0,0 +1,30 @@ +using SqlSugar; + +namespace FoodLabeling.Application.Services.DbModels; + +/// +/// userlocation 表映射(成员-门店关联) +/// +[SugarTable("userlocation")] +public class UserLocationDbEntity +{ + [SugarColumn(IsPrimaryKey = true)] + public string Id { get; set; } = string.Empty; + + public bool IsDeleted { get; set; } + + public DateTime CreationTime { get; set; } + + public string? CreatorId { get; set; } + + public string? LastModifierId { get; set; } + + public DateTime? LastModificationTime { get; set; } + + public string UserId { get; set; } = string.Empty; + + public string LocationId { get; set; } = string.Empty; + + public string? ConcurrencyStamp { get; set; } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs index 7a3c28a..012867b 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacRoleAppService.cs @@ -1,6 +1,7 @@ using FoodLabeling.Application.Contracts.Dtos.RbacRole; using FoodLabeling.Application.Contracts.Dtos.Common; using FoodLabeling.Application.Contracts.IServices; +using FoodLabeling.Application.Services.DbModels; using Microsoft.AspNetCore.Mvc; using SqlSugar; using Volo.Abp; @@ -17,17 +18,20 @@ namespace FoodLabeling.Application.Services; /// public class RbacRoleAppService : ApplicationService, IRbacRoleAppService { + private readonly ISqlSugarDbContext _dbContext; private readonly ISqlSugarRepository _roleRepository; private readonly ISqlSugarRepository _roleMenuRepository; private readonly ISqlSugarRepository _roleDeptRepository; private readonly ISqlSugarRepository _userRoleRepository; public RbacRoleAppService( + ISqlSugarDbContext dbContext, ISqlSugarRepository roleRepository, ISqlSugarRepository roleMenuRepository, ISqlSugarRepository roleDeptRepository, ISqlSugarRepository userRoleRepository) { + _dbContext = dbContext; _roleRepository = roleRepository; _roleMenuRepository = roleMenuRepository; _roleDeptRepository = roleDeptRepository; @@ -90,6 +94,11 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService throw new UserFriendlyException("角色不存在"); } + var menuIds = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.RoleId == id.ToString()) + .Select(x => x.MenuId) + .ToListAsync(); + return new RbacRoleGetOutputDto { Id = entity.Id, @@ -98,7 +107,8 @@ public class RbacRoleAppService : ApplicationService, IRbacRoleAppService Remark = entity.Remark, DataScope = (int)entity.DataScope, State = entity.State, - OrderNum = entity.OrderNum + OrderNum = entity.OrderNum, + MenuIds = menuIds }; } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs new file mode 100644 index 0000000..4cfe8bd --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/TeamMemberAppService.cs @@ -0,0 +1,355 @@ +using FoodLabeling.Application.Contracts.Dtos.Common; +using FoodLabeling.Application.Contracts.Dtos.TeamMember; +using FoodLabeling.Application.Contracts.IServices; +using FoodLabeling.Application.Services.DbModels; +using FoodLabeling.Domain.Entities; +using SqlSugar; +using Volo.Abp; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Guids; +using Yi.Framework.Rbac.Domain.Entities; +using Yi.Framework.Rbac.Domain.Managers; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace FoodLabeling.Application.Services; + +/// +/// 成员(Team Member/Manager)服务,对外仅在 food-labeling-us 暴露 +/// +public class TeamMemberAppService : ApplicationService, ITeamMemberAppService +{ + private readonly ISqlSugarRepository _userRepository; + private readonly UserManager _userManager; + private readonly ISqlSugarDbContext _dbContext; + private readonly IGuidGenerator _guidGenerator; + + public TeamMemberAppService( + ISqlSugarRepository userRepository, + UserManager userManager, + ISqlSugarDbContext dbContext, + IGuidGenerator guidGenerator) + { + _userRepository = userRepository; + _userManager = userManager; + _dbContext = dbContext; + _guidGenerator = guidGenerator; + } + + /// + /// 成员分页列表(含角色与已分配门店) + /// + public async Task> GetListAsync(TeamMemberGetListInputVo input) + { + var pageIndex = input.SkipCount / input.MaxResultCount + 1; + var pageSize = input.MaxResultCount; + var keyword = input.Keyword?.Trim(); + + RefAsync total = 0; + + // 先按 user 表筛选分页,再批量补齐角色与门店 + var users = await _userRepository._DbQueryable + .Where(u => !u.IsDeleted) + .WhereIF(!string.IsNullOrWhiteSpace(keyword), + u => (u.Name != null && u.Name.Contains(keyword!)) || + u.UserName.Contains(keyword!) || + (u.Email != null && u.Email.Contains(keyword!)) || + (u.Phone != null && u.Phone.ToString()!.Contains(keyword!))) + .WhereIF(input.State != null, u => u.State == input.State) + .OrderByIF(!string.IsNullOrWhiteSpace(input.Sorting), input.Sorting!) + .OrderByDescending(u => u.CreationTime) + .ToPageListAsync(input.SkipCount, input.MaxResultCount, total); + + var userIds = users.Select(x => x.Id).ToList(); + var userIdStrings = userIds.Select(x => x.ToString()).ToList(); + + // user-role: 仅取第一个角色(原型表格展示单角色) + var userRolePairs = await _dbContext.SqlSugarClient.Queryable((ur, r) => ur.RoleId == r.Id) + .Where(ur => userIds.Contains(ur.UserId)) + .Select((ur, r) => new { ur.UserId, r.Id, r.RoleName }) + .ToListAsync(); + + var roleMap = userRolePairs + .GroupBy(x => x.UserId) + .ToDictionary(g => g.Key, g => g.FirstOrDefault()); + + // user-location + var userLocations = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted) + .WhereIF(!string.IsNullOrWhiteSpace(input.LocationId), x => x.LocationId == input.LocationId) + .Where(x => userIdStrings.Contains(x.UserId)) + .ToListAsync(); + + // 如果按 LocationId 过滤,需要反向过滤掉无关联的 user + if (!string.IsNullOrWhiteSpace(input.LocationId)) + { + var allowedUserIds = userLocations.Select(x => x.UserId).ToHashSet(); + users = users.Where(u => allowedUserIds.Contains(u.Id.ToString())).ToList(); + } + + var locationIds = userLocations.Select(x => x.LocationId).Distinct().ToList(); + var locations = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted) + .WhereIF(locationIds.Count > 0, x => locationIds.Contains(x.Id.ToString())) + .Select(x => new { x.Id, x.LocationCode, x.LocationName }) + .ToListAsync(); + var locationMap = locations.ToDictionary(x => x.Id.ToString(), x => x); + + var assignedMap = userLocations + .GroupBy(x => x.UserId) + .ToDictionary( + g => g.Key, + g => g.Select(x => + { + if (locationMap.TryGetValue(x.LocationId, out var loc)) + { + return new TeamMemberAssignedLocationDto + { + Id = loc.Id.ToString(), + LocationCode = loc.LocationCode, + LocationName = loc.LocationName + }; + } + return null; + }).Where(x => x != null).Cast().ToList()); + + var items = users.Select(u => + { + roleMap.TryGetValue(u.Id, out var role); + assignedMap.TryGetValue(u.Id.ToString(), out var assigned); + + return new TeamMemberGetListOutputDto + { + Id = u.Id, + FullName = u.Name ?? string.Empty, + UserName = u.UserName, + Email = u.Email, + Phone = u.Phone, + State = u.State, + RoleId = role?.Id, + RoleName = role?.RoleName, + AssignedLocations = assigned ?? new List() + }; + }).ToList(); + + var totalCount = (long)total; + return new PagedResultWithPageDto + { + PageIndex = pageIndex, + PageSize = pageSize, + TotalCount = totalCount, + TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize), + Items = items + }; + } + + /// + /// 成员详情(带门店ID列表) + /// + public async Task GetAsync(Guid id) + { + var user = await _userRepository.GetByIdAsync(id); + if (user is null || user.IsDeleted) + { + throw new UserFriendlyException("成员不存在"); + } + + var userIdString = id.ToString(); + var links = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted && x.UserId == userIdString) + .ToListAsync(); + + var locationIds = links.Select(x => x.LocationId).Distinct().ToList(); + var locations = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted) + .WhereIF(locationIds.Count > 0, x => locationIds.Contains(x.Id.ToString())) + .Select(x => new { x.Id, x.LocationCode, x.LocationName }) + .ToListAsync(); + + var assigned = locations.Select(x => new TeamMemberAssignedLocationDto + { + Id = x.Id.ToString(), + LocationCode = x.LocationCode, + LocationName = x.LocationName + }).ToList(); + + var role = await _dbContext.SqlSugarClient.Queryable().FirstAsync(x => x.UserId == id); + + return new TeamMemberGetOutputDto + { + Id = user.Id, + FullName = user.Name ?? string.Empty, + UserName = user.UserName, + Email = user.Email, + Phone = user.Phone, + State = user.State, + RoleId = role?.RoleId, + LocationIds = locationIds, + AssignedLocations = assigned + }; + } + + /// + /// 新增成员(同步设置角色与门店) + /// + public async Task CreateAsync(TeamMemberCreateInputVo input) + { + if (input.LocationIds is null || input.LocationIds.Count == 0) + { + throw new UserFriendlyException("成员必须至少分配一个门店"); + } + + var user = new UserAggregateRoot(input.UserName.Trim(), input.Password, input.Phone, input.FullName.Trim()) + { + Name = input.FullName.Trim(), + Email = input.Email?.Trim(), + State = input.State + }; + + EntityHelper.TrySetId(user, _guidGenerator.Create); + user.BuildPassword(); + + await _userManager.CreateAsync(user); + + if (input.RoleId != null) + { + await _userManager.GiveUserSetRoleAsync(new List { user.Id }, new List { input.RoleId.Value }); + } + + await UpsertUserLocationsAsync(user.Id, input.LocationIds); + + return await GetAsync(user.Id); + } + + /// + /// 编辑成员(同步设置角色与门店) + /// + public async Task UpdateAsync(Guid id, TeamMemberUpdateInputVo input) + { + if (input.LocationIds is null || input.LocationIds.Count == 0) + { + throw new UserFriendlyException("成员必须至少分配一个门店"); + } + + var user = await _userRepository.GetByIdAsync(id); + if (user is null || user.IsDeleted) + { + throw new UserFriendlyException("成员不存在"); + } + + user.Name = input.FullName.Trim(); + user.UserName = input.UserName.Trim(); + user.Email = input.Email?.Trim(); + user.Phone = input.Phone; + user.State = input.State; + + if (!string.IsNullOrWhiteSpace(input.Password)) + { + user.EncryPassword.Password = input.Password; + user.BuildPassword(); + } + + await _userRepository.UpdateAsync(user); + + // 角色:覆盖式设置(只保留一个) + if (input.RoleId != null) + { + await _userManager.GiveUserSetRoleAsync(new List { id }, new List { input.RoleId.Value }); + } + else + { + await _userManager.GiveUserSetRoleAsync(new List { id }, new List()); + } + + await UpsertUserLocationsAsync(id, input.LocationIds); + + return await GetAsync(id); + } + + /// + /// 删除成员(逻辑删除 user;并逻辑删除关联表) + /// + public async Task DeleteAsync(Guid id) + { + var user = await _userRepository.GetByIdAsync(id); + if (user is null || user.IsDeleted) + { + return; + } + + user.IsDeleted = true; + await _userRepository.UpdateAsync(user); + + var userIdString = id.ToString(); + var currentUserId = CurrentUser?.Id?.ToString(); + await _dbContext.SqlSugarClient.Updateable() + .SetColumns(x => new UserLocationDbEntity + { + IsDeleted = true, + LastModificationTime = DateTime.Now, + LastModifierId = currentUserId + }) + .Where(x => x.UserId == userIdString && !x.IsDeleted) + .ExecuteCommandAsync(); + } + + private async Task UpsertUserLocationsAsync(Guid userId, List locationIds) + { + var now = DateTime.Now; + var userIdString = userId.ToString(); + var wanted = locationIds.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).Distinct().ToList(); + var currentUserId = CurrentUser?.Id?.ToString(); + + // 校验门店存在且未删除 + var validCount = await _dbContext.SqlSugarClient.Queryable() + .Where(x => !x.IsDeleted) + .Where(x => wanted.Contains(x.Id.ToString())) + .CountAsync(); + if (validCount != wanted.Count) + { + throw new UserFriendlyException("存在无效门店,请刷新后重试"); + } + + var existing = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.UserId == userIdString) + .ToListAsync(); + + var existingActive = existing.Where(x => !x.IsDeleted).ToList(); + var existingActiveSet = existingActive.Select(x => x.LocationId).ToHashSet(); + + // 需要删除的(逻辑删除) + var toDelete = existingActive.Where(x => !wanted.Contains(x.LocationId)).ToList(); + if (toDelete.Count > 0) + { + var ids = toDelete.Select(x => x.Id).ToList(); + await _dbContext.SqlSugarClient.Updateable() + .SetColumns(x => new UserLocationDbEntity + { + IsDeleted = true, + LastModificationTime = now, + LastModifierId = currentUserId + }) + .Where(x => ids.Contains(x.Id)) + .ExecuteCommandAsync(); + } + + // 需要新增的 + var toInsert = wanted.Where(x => !existingActiveSet.Contains(x)).ToList(); + if (toInsert.Count > 0) + { + var rows = toInsert.Select(locationId => new UserLocationDbEntity + { + Id = _guidGenerator.Create().ToString(), + IsDeleted = false, + CreationTime = now, + CreatorId = currentUserId, + UserId = userIdString, + LocationId = locationId, + ConcurrencyStamp = string.Empty + }).ToList(); + + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); + } + } +} + -- libgit2 0.21.4