diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/AuthSession/CurrentUserBriefDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/AuthSession/CurrentUserBriefDto.cs new file mode 100644 index 0000000..4567f6d --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/AuthSession/CurrentUserBriefDto.cs @@ -0,0 +1,17 @@ +namespace FoodLabeling.Application.Contracts.Dtos.AuthSession; + +/// +/// 当前登录用户简要信息(不含敏感字段) +/// +public class CurrentUserBriefDto +{ + public Guid Id { get; set; } + + public string UserName { get; set; } = string.Empty; + + public string? Nick { get; set; } + + public string? Email { get; set; } + + public string? Icon { get; set; } +} diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/AuthSession/CurrentUserMenuNodeDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/AuthSession/CurrentUserMenuNodeDto.cs new file mode 100644 index 0000000..b7935f7 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/AuthSession/CurrentUserMenuNodeDto.cs @@ -0,0 +1,43 @@ +namespace FoodLabeling.Application.Contracts.Dtos.AuthSession; + +/// +/// 当前用户可见菜单树节点(与权限分配一致) +/// +public class CurrentUserMenuNodeDto +{ + public string Id { get; set; } = string.Empty; + + public string ParentId { get; set; } = "0"; + + public string MenuName { get; set; } = string.Empty; + + public string? RouterName { get; set; } + + public string? Router { get; set; } + + public string? PermissionCode { get; set; } + + public int MenuType { get; set; } + + public int MenuSource { get; set; } + + public int OrderNum { get; set; } + + public bool State { get; set; } + + public string? MenuIcon { get; set; } + + public string? Component { get; set; } + + public bool IsLink { get; set; } + + public bool IsCache { get; set; } + + public bool IsShow { get; set; } + + public string? Query { get; set; } + + public string? Remark { get; set; } + + public List Children { get; set; } = new(); +} diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/AuthSession/CurrentUserMenuPermissionsOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/AuthSession/CurrentUserMenuPermissionsOutputDto.cs new file mode 100644 index 0000000..5ea17eb --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/AuthSession/CurrentUserMenuPermissionsOutputDto.cs @@ -0,0 +1,15 @@ +namespace FoodLabeling.Application.Contracts.Dtos.AuthSession; + +/// +/// 当前登录用户的菜单与权限码(用于前端动态路由/按钮权限) +/// +public class CurrentUserMenuPermissionsOutputDto +{ + public CurrentUserBriefDto User { get; set; } = new(); + + public List RoleCodes { get; set; } = new(); + + public List PermissionCodes { get; set; } = new(); + + public List Menus { get; set; } = new(); +} diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelCreateInputVo.cs index 5965aec..bd1107f 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelCreateInputVo.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/Label/LabelCreateInputVo.cs @@ -2,7 +2,7 @@ namespace FoodLabeling.Application.Contracts.Dtos.Label; public class LabelCreateInputVo { - public string LabelCode { get; set; } = string.Empty; + public string? LabelCode { get; set; } public string LabelName { get; set; } = string.Empty; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryCreateInputVo.cs index 986bca2..efdd7fd 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryCreateInputVo.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryCreateInputVo.cs @@ -6,10 +6,33 @@ public class LabelCategoryCreateInputVo public string CategoryName { get; set; } = string.Empty; + /// + /// 按钮展示文案(为空则默认使用 CategoryName) + /// + public string? DisplayText { get; set; } + + /// + /// COLOR 模式存色值、IMAGE 模式存图片 URL、TEXT 可为分类小图或空(与 buttonAppearance 配合) + /// public string? CategoryPhotoUrl { get; set; } public bool State { get; set; } = true; + /// + /// 按钮外观:TEXT / COLOR / IMAGE(展示值见 categoryPhotoUrl) + /// + public string ButtonAppearance { get; set; } = "TEXT"; + + /// + /// 门店可用范围:ALL / SPECIFIED + /// + public string AvailabilityType { get; set; } = "ALL"; + + /// + /// 指定门店 Id 列表(当 AvailabilityType=SPECIFIED 时必填) + /// + public List? LocationIds { get; set; } + public int OrderNum { get; set; } } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListOutputDto.cs index db2aa7c..c8effdf 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListOutputDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetListOutputDto.cs @@ -8,10 +8,16 @@ public class LabelCategoryGetListOutputDto public string CategoryName { get; set; } = string.Empty; + public string? DisplayText { get; set; } + public string? CategoryPhotoUrl { get; set; } public bool State { get; set; } + public string ButtonAppearance { get; set; } = "TEXT"; + + public string AvailabilityType { get; set; } = "ALL"; + public int OrderNum { get; set; } public long NoOfLabels { get; set; } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetOutputDto.cs index 242b820..c4a740b 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetOutputDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelCategory/LabelCategoryGetOutputDto.cs @@ -8,10 +8,18 @@ public class LabelCategoryGetOutputDto public string CategoryName { get; set; } = string.Empty; + public string? DisplayText { get; set; } + public string? CategoryPhotoUrl { get; set; } public bool State { get; set; } + public string ButtonAppearance { get; set; } = "TEXT"; + + public string AvailabilityType { get; set; } = "ALL"; + + public List LocationIds { get; set; } = new(); + public int OrderNum { get; set; } } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelTemplate/LabelTemplateElementDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelTemplate/LabelTemplateElementDto.cs index 5b6cf03..643a49d 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelTemplate/LabelTemplateElementDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/LabelTemplate/LabelTemplateElementDto.cs @@ -3,7 +3,7 @@ using System.Text.Json.Serialization; namespace FoodLabeling.Application.Contracts.Dtos.LabelTemplate; /// -/// 模板元素(对齐你给的 editor JSON:id/type/x/y/width/height/rotation/border/config) +/// 模板元素(对齐 editor JSON:id/type/typeAdd/elementName/x/y/width/height/rotation/border/config 等) /// public class LabelTemplateElementDto { diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryCreateInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryCreateInputVo.cs index 0dd9931..9f45ada 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryCreateInputVo.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryCreateInputVo.cs @@ -9,10 +9,33 @@ public class ProductCategoryCreateInputVo public string CategoryName { get; set; } = string.Empty; + /// + /// 按钮展示文案(为空则默认使用 CategoryName) + /// + public string? DisplayText { get; set; } + + /// + /// COLOR 模式存色值、IMAGE 模式存图片 URL、TEXT 可为分类小图或空(与 buttonAppearance 配合) + /// public string? CategoryPhotoUrl { get; set; } + /// + /// 按钮外观:TEXT / COLOR / IMAGE(展示值见 categoryPhotoUrl) + /// + public string ButtonAppearance { get; set; } = "TEXT"; + public bool State { get; set; } = true; + /// + /// 门店可用范围:ALL / SPECIFIED + /// + public string AvailabilityType { get; set; } = "ALL"; + + /// + /// 指定门店 Id 列表(当 AvailabilityType=SPECIFIED 时必填) + /// + public List? LocationIds { get; set; } + public int OrderNum { get; set; } = 0; } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetListOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetListOutputDto.cs index ced7d81..51048ce 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetListOutputDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetListOutputDto.cs @@ -11,10 +11,16 @@ public class ProductCategoryGetListOutputDto public string CategoryName { get; set; } = string.Empty; + public string? DisplayText { get; set; } + public string? CategoryPhotoUrl { get; set; } + public string ButtonAppearance { get; set; } = "TEXT"; + public bool State { get; set; } + public string AvailabilityType { get; set; } = "ALL"; + public int OrderNum { get; set; } public DateTime LastEdited { get; set; } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetOutputDto.cs index 3c673a5..2018bc2 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetOutputDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/ProductCategory/ProductCategoryGetOutputDto.cs @@ -11,10 +11,19 @@ public class ProductCategoryGetOutputDto public string CategoryName { get; set; } = string.Empty; + public string? DisplayText { get; set; } + + /// COLOR 色值 / IMAGE 图片 URL / TEXT 可选图 public string? CategoryPhotoUrl { get; set; } + public string ButtonAppearance { get; set; } = "TEXT"; + public bool State { get; set; } + public string AvailabilityType { get; set; } = "ALL"; + + public List LocationIds { get; set; } = new(); + public int OrderNum { get; set; } } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/RbacMenu/RbacMenuGetListOutputDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/RbacMenu/RbacMenuGetListOutputDto.cs index 4a59a70..9bebabf 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/RbacMenu/RbacMenuGetListOutputDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/RbacMenu/RbacMenuGetListOutputDto.cs @@ -11,6 +11,10 @@ public class RbacMenuGetListOutputDto public string MenuName { get; set; } = string.Empty; + public string? RouterName { get; set; } + + public string? Router { get; set; } + public string? PermissionCode { get; set; } public int MenuType { get; set; } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelCategoryTreeNodeDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelCategoryTreeNodeDto.cs index 4e2ce5d..dc93a0b 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelCategoryTreeNodeDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelCategoryTreeNodeDto.cs @@ -11,6 +11,9 @@ public class UsAppLabelCategoryTreeNodeDto public string? CategoryPhotoUrl { get; set; } + /// 按钮外观:TEXT / COLOR / IMAGE(COLOR/IMAGE 的展示值在 categoryPhotoUrl) + public string ButtonAppearance { get; set; } = "TEXT"; + public int OrderNum { get; set; } public List ProductCategories { get; set; } = new(); diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintInputVo.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintInputVo.cs index 0977cdf..24d9679 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintInputVo.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppLabelPrintInputVo.cs @@ -49,11 +49,6 @@ public class UsAppLabelPrintInputVo public JsonElement? PrintInputJson { get; set; } /// - /// 客户端幂等请求 Id(可选);重复相同值时由服务端决定是否直接返回首次结果(见接口文档)。 - /// - public string? ClientRequestId { get; set; } - - /// /// 打印机Id(可选,若业务需要追踪) /// public string? PrinterId { get; set; } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppProductCategoryNodeDto.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppProductCategoryNodeDto.cs index fd93dae..ddf43ea 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppProductCategoryNodeDto.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/Dtos/UsAppLabeling/UsAppProductCategoryNodeDto.cs @@ -14,6 +14,18 @@ public class UsAppProductCategoryNodeDto /// 分类显示名;空为「无」 public string Name { get; set; } = string.Empty; + /// 按钮展示文案;为空时客户端可回退使用 Name + public string? DisplayText { get; set; } + + /// 按钮外观:TEXT / COLOR / IMAGE(COLOR/IMAGE 的展示值在 categoryPhotoUrl) + public string ButtonAppearance { get; set; } = "TEXT"; + + /// 门店可用范围:ALL / SPECIFIED(本树已按当前门店过滤) + public string AvailabilityType { get; set; } = "ALL"; + + /// 排序号(来自 fl_product_category;未归类为较大值以排在后) + public int OrderNum { get; set; } + public int ItemCount { get; set; } public List Products { get; set; } = new(); diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IAuthSessionAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IAuthSessionAppService.cs new file mode 100644 index 0000000..a033cd9 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IAuthSessionAppService.cs @@ -0,0 +1,33 @@ +using FoodLabeling.Application.Contracts.Dtos.AuthSession; +using Volo.Abp.Application.Services; + +namespace FoodLabeling.Application.Contracts.IServices; + +/// +/// 当前登录会话:菜单权限与退出(美国版 Web 管理端) +/// +public interface IAuthSessionAppService : IApplicationService +{ + /// + /// 获取当前登录用户的角色编码、权限码与可见菜单树 + /// + /// + /// 与框架 UserManager.GetInfoAsync 一致;用户名为 admin 时返回全部未删除菜单(与 AccountService.GetVue3Router 行为对齐)。 + /// + /// 用户简要信息、权限码与菜单树 + /// 成功 + /// 未登录或令牌无效 + /// 服务器错误 + Task GetMyMenusAsync(); + + /// + /// 退出登录:清除服务端用户信息缓存(JWT 仍由前端丢弃) + /// + /// + /// 与框架 AccountService.PostLogout 一致;未登录时返回 false。 + /// + /// 是否执行了缓存清理(已登录为 true) + /// 成功 + /// 服务器错误 + Task LogoutAsync(); +} diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs index ccde16d..781adcd 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application.Contracts/IServices/IUsAppLabelingAppService.cs @@ -1,6 +1,5 @@ using FoodLabeling.Application.Contracts.Dtos.Common; using FoodLabeling.Application.Contracts.Dtos.UsAppLabeling; -using FoodLabeling.Application.Contracts.Dtos.Common; using Volo.Abp.Application.Services; namespace FoodLabeling.Application.Contracts.IServices; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/AuthSessionAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/AuthSessionAppService.cs new file mode 100644 index 0000000..7507726 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/AuthSessionAppService.cs @@ -0,0 +1,213 @@ +using FoodLabeling.Application.Contracts.Dtos.AuthSession; +using FoodLabeling.Application.Contracts.IServices; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.Application.Services; +using Volo.Abp.Caching; +using FoodLabeling.Application.Services.DbModels; +using Yi.Framework.Rbac.Domain.Entities; +using Yi.Framework.Rbac.Domain.Shared.Caches; +using Yi.Framework.Rbac.Domain.Shared.Consts; +using Yi.Framework.SqlSugarCore.Abstractions; + +namespace FoodLabeling.Application.Services; + +/// +/// 当前登录会话:菜单权限与退出 +/// +[Authorize] +public class AuthSessionAppService : ApplicationService, IAuthSessionAppService +{ + private readonly IDistributedCache _userCache; + private readonly ISqlSugarDbContext _dbContext; + private readonly ISqlSugarRepository _userRepository; + + public AuthSessionAppService( + ISqlSugarDbContext dbContext, + ISqlSugarRepository userRepository, + IDistributedCache userCache) + { + _dbContext = dbContext; + _userRepository = userRepository; + _userCache = userCache; + } + + /// + public virtual async Task GetMyMenusAsync() + { + if (!CurrentUser.Id.HasValue) + { + throw new UserFriendlyException("用户未登录"); + } + + // 避免走 UserManager.GetInfoAsync -> UserRepository.GetUserAllInfoAsync 的导航加载 + // 这里直接按 UserRole/RoleMenu/Menu 表关联查询当前用户可见菜单与权限码 + var userId = CurrentUser.Id.Value; + var user = await _userRepository.GetByIdAsync(userId); + if (user is null || user.IsDeleted) + { + throw new UserFriendlyException("用户不存在"); + } + + List menus; + if (UserConst.Admin.Equals(user.UserName)) + { + // MenuAggregateRoot(ParentId 为 Guid) 无法兼容 menu.ParentId=0/字符串:这里统一用 MenuDbEntity + menus = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.IsDeleted == false) + .ToListAsync(); + } + else + { + var roleIds = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.UserId == userId) + .Select(x => x.RoleId) + .ToListAsync(); + + var roleIdStrs = roleIds.Select(x => x.ToString()).Distinct().ToList(); + if (roleIdStrs.Count == 0) + { + menus = new List(); + } + else + { + var menuIds = await _dbContext.SqlSugarClient.Queryable() + .Where(x => roleIdStrs.Contains(x.RoleId)) + .Select(x => x.MenuId) + .Distinct() + .ToListAsync(); + + menus = menuIds.Count == 0 + ? new List() + : await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.IsDeleted == false && menuIds.Contains(x.Id)) + .ToListAsync(); + } + } + + var menuNodes = menus + .Select(MapToNode) + .OrderByDescending(x => x.OrderNum) + .ThenBy(x => x.MenuName) + .ToList(); + + // 注意:查询 RoleAggregateRoot 会触发 YiRbacDbContext 的 IDataPermission 过滤, + // 其表达式包含 roleInfo.Select(...).Contains(...),在当前 SqlSugar 版本下会报“不支持 Select”。 + // 这里直接使用 JWT 中的角色码(CurrentUser.Roles)返回,避免触发过滤器。 + var roleCodes = CurrentUser.Roles?.ToList() ?? new List(); + + var permissionCodes = menuNodes + .Where(x => !string.IsNullOrWhiteSpace(x.PermissionCode)) + .Select(x => x.PermissionCode!.Trim()) + .Distinct() + .OrderBy(x => x) + .ToList(); + + return new CurrentUserMenuPermissionsOutputDto + { + User = new CurrentUserBriefDto + { + Id = user.Id, + UserName = user.UserName, + Nick = user.Nick, + Email = user.Email, + Icon = user.Icon + }, + RoleCodes = roleCodes.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).Distinct().OrderBy(x => x).ToList(), + PermissionCodes = permissionCodes, + Menus = BuildMenuTree(menuNodes) + }; + } + + /// + [HttpPost] + public virtual async Task LogoutAsync() + { + if (!CurrentUser.Id.HasValue) + { + return false; + } + + await _userCache.RemoveAsync(new UserInfoCacheKey(CurrentUser.Id.Value)); + return true; + } + + private static List BuildMenuTree(List flat) + { + var nodes = flat + .GroupBy(x => x.Id) + .Select(g => g.First()) + .ToList(); + var byId = nodes.ToDictionary(n => n.Id, n => n); + + foreach (var n in nodes) + { + n.Children = new List(); + } + + var roots = new List(); + foreach (var n in nodes) + { + var pid = string.IsNullOrWhiteSpace(n.ParentId) ? "0" : n.ParentId.Trim(); + if (pid == "0" || pid == "00000000-0000-0000-0000-000000000000") + { + roots.Add(n); + continue; + } + + if (byId.TryGetValue(pid, out var parent)) + { + parent.Children.Add(n); + } + else + { + roots.Add(n); + } + } + + SortMenuTree(roots); + return roots; + } + + private static CurrentUserMenuNodeDto MapToNode(MenuDbEntity m) + { + return new CurrentUserMenuNodeDto + { + Id = m.Id, + ParentId = string.IsNullOrWhiteSpace(m.ParentId) ? "0" : m.ParentId.Trim(), + MenuName = m.MenuName ?? string.Empty, + RouterName = m.RouterName, + Router = m.Router, + PermissionCode = m.PermissionCode, + MenuType = m.MenuType, + MenuSource = m.MenuSource, + OrderNum = m.OrderNum, + State = m.State, + MenuIcon = m.MenuIcon, + Component = m.Component, + IsLink = m.IsLink, + IsCache = m.IsCache, + IsShow = m.IsShow, + Query = m.Query, + Remark = m.Remark + }; + } + + private static void SortMenuTree(List level) + { + level.Sort((a, b) => + { + var o = b.OrderNum.CompareTo(a.OrderNum); + return o != 0 ? o : string.Compare(a.MenuName, b.MenuName, StringComparison.Ordinal); + }); + + foreach (var n in level) + { + if (n.Children.Count > 0) + { + SortMenuTree(n.Children); + } + } + } +} diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryDbEntity.cs index d70b724..b89ab82 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryDbEntity.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryDbEntity.cs @@ -24,10 +24,28 @@ public class FlLabelCategoryDbEntity public string CategoryName { get; set; } = string.Empty; + /// + /// 按钮展示文案(为空则默认使用 CategoryName) + /// + public string? DisplayText { get; set; } + + /// + /// 分类图/展示值:TEXT 可为图或空;COLOR 存色值(如 #409EFF);IMAGE 存图片 URL(与 ButtonAppearance 配合) + /// public string? CategoryPhotoUrl { get; set; } public int OrderNum { get; set; } public bool State { get; set; } + + /// + /// 按钮外观:TEXT / COLOR / IMAGE(展示数据见 CategoryPhotoUrl) + /// + public string ButtonAppearance { get; set; } = "TEXT"; + + /// + /// 门店可用范围:ALL / SPECIFIED + /// + public string AvailabilityType { get; set; } = "ALL"; } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryLocationDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryLocationDbEntity.cs new file mode 100644 index 0000000..ae9b896 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelCategoryLocationDbEntity.cs @@ -0,0 +1,22 @@ +using SqlSugar; + +namespace FoodLabeling.Application.Services.DbModels; + +/// +/// 标签分类可用门店关联(对应表:fl_label_category_location) +/// +[SugarTable("fl_label_category_location")] +public class FlLabelCategoryLocationDbEntity +{ + [SugarColumn(IsPrimaryKey = true)] + public string Id { get; set; } = string.Empty; + + public string CategoryId { get; set; } = string.Empty; + + public string LocationId { get; set; } = string.Empty; + + public DateTime CreationTime { get; set; } + + public string? CreatorId { get; set; } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelDbEntity.cs index 3199335..23f6f20 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelDbEntity.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlLabelDbEntity.cs @@ -20,7 +20,7 @@ public class FlLabelDbEntity public string ConcurrencyStamp { get; set; } = string.Empty; - public string LabelCode { get; set; } = string.Empty; + public string? LabelCode { get; set; } public string LabelName { get; set; } = string.Empty; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlProductCategoryDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlProductCategoryDbEntity.cs index 1f51a75..2852756 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlProductCategoryDbEntity.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlProductCategoryDbEntity.cs @@ -24,10 +24,28 @@ public class FlProductCategoryDbEntity public string CategoryName { get; set; } = string.Empty; + /// + /// 按钮展示文案(为空则默认使用 CategoryName) + /// + public string? DisplayText { get; set; } + + /// + /// 分类图/展示值:TEXT 可为图或空;COLOR 存色值;IMAGE 存图片 URL(与 ButtonAppearance 配合) + /// public string? CategoryPhotoUrl { get; set; } + /// + /// 按钮外观:TEXT / COLOR / IMAGE(展示数据见 CategoryPhotoUrl) + /// + public string ButtonAppearance { get; set; } = "TEXT"; + public bool State { get; set; } + /// + /// 门店可用范围:ALL / SPECIFIED + /// + public string AvailabilityType { get; set; } = "ALL"; + public int OrderNum { get; set; } } diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlProductCategoryLocationDbEntity.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlProductCategoryLocationDbEntity.cs new file mode 100644 index 0000000..c6a6795 --- /dev/null +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/DbModels/FlProductCategoryLocationDbEntity.cs @@ -0,0 +1,22 @@ +using SqlSugar; + +namespace FoodLabeling.Application.Services.DbModels; + +/// +/// 产品类别可用门店关联(对应表:fl_product_category_location) +/// +[SugarTable("fl_product_category_location")] +public class FlProductCategoryLocationDbEntity +{ + [SugarColumn(IsPrimaryKey = true)] + public string Id { get; set; } = string.Empty; + + public string CategoryId { get; set; } = string.Empty; + + public string LocationId { get; set; } = string.Empty; + + public DateTime CreationTime { get; set; } + + public string? CreatorId { get; set; } +} + diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelAppService.cs index f17c480..64fbea7 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelAppService.cs @@ -261,7 +261,7 @@ public class LabelAppService : ApplicationService, ILabelAppService return new LabelGetOutputDto { - Id = label.LabelCode, + Id = label.LabelCode ?? string.Empty, LabelName = label.LabelName, LocationId = label.LocationId ?? string.Empty, LocationName = location?.LocationName ?? location?.LocationCode ?? "无", diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs index 52cab93..827d7cc 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/LabelCategoryAppService.cs @@ -30,12 +30,34 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ var query = _dbContext.SqlSugarClient.Queryable() .Where(x => !x.IsDeleted) .WhereIF(!string.IsNullOrWhiteSpace(keyword), - x => x.CategoryCode.Contains(keyword!) || x.CategoryName.Contains(keyword!)) + x => x.CategoryCode.Contains(keyword!) || x.CategoryName.Contains(keyword!) || + (x.DisplayText != null && x.DisplayText.Contains(keyword!))) .WhereIF(input.State != null, x => x.State == input.State); + // Sorting 仅允许白名单字段,避免 Unknown column/注入风险 if (!string.IsNullOrWhiteSpace(input.Sorting)) { - query = query.OrderBy(input.Sorting); + var sorting = input.Sorting.Trim(); + if (sorting.Equals("OrderNum desc", StringComparison.OrdinalIgnoreCase)) + { + query = query.OrderByDescending(x => x.OrderNum); + } + else if (sorting.Equals("OrderNum asc", StringComparison.OrdinalIgnoreCase)) + { + query = query.OrderBy(x => x.OrderNum); + } + else if (sorting.Equals("CreationTime desc", StringComparison.OrdinalIgnoreCase)) + { + query = query.OrderByDescending(x => x.CreationTime); + } + else if (sorting.Equals("CreationTime asc", StringComparison.OrdinalIgnoreCase)) + { + query = query.OrderBy(x => x.CreationTime); + } + else + { + query = query.OrderByDescending(x => x.OrderNum).OrderByDescending(x => x.CreationTime); + } } else { @@ -58,8 +80,11 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ Id = x.Id, CategoryCode = x.CategoryCode, CategoryName = x.CategoryName, + DisplayText = x.DisplayText, CategoryPhotoUrl = x.CategoryPhotoUrl, State = x.State, + ButtonAppearance = x.ButtonAppearance, + AvailabilityType = x.AvailabilityType, OrderNum = x.OrderNum, NoOfLabels = countMap.TryGetValue(x.Id, out var count) ? count : 0, LastEdited = x.LastModificationTime ?? x.CreationTime @@ -77,7 +102,17 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ throw new UserFriendlyException("标签分类不存在"); } - return MapToGetOutput(entity); + var dto = MapToGetOutput(entity); + if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase)) + { + var locationIds = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.CategoryId == entity.Id) + .Select(x => x.LocationId) + .ToListAsync(); + dto.LocationIds = locationIds?.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).Distinct().ToList() ?? new(); + } + + return dto; } public async Task CreateAsync(LabelCategoryCreateInputVo input) @@ -89,6 +124,13 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ throw new UserFriendlyException("分类编码和名称不能为空"); } + var displayText = input.DisplayText?.Trim(); + var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); + var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); + ValidateButtonAppearance(appearance); + var locationIds = NormalizeLocationIds(input.LocationIds); + ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); + var duplicated = await _dbContext.SqlSugarClient.Queryable() .AnyAsync(x => !x.IsDeleted && (x.CategoryCode == code || x.CategoryName == name)); if (duplicated) @@ -96,17 +138,23 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ throw new UserFriendlyException("分类编码或名称已存在"); } + var now = DateTime.Now; + var currentUserId = CurrentUser?.Id?.ToString(); var entity = new FlLabelCategoryDbEntity { Id = _guidGenerator.Create().ToString(), CategoryCode = code, CategoryName = name, + DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(), State = input.State, + ButtonAppearance = appearance, + AvailabilityType = availabilityType, OrderNum = input.OrderNum }; await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); + await SaveCategoryLocationsAsync(entity.Id, availabilityType, locationIds, currentUserId, now); return await GetAsync(entity.Id); } @@ -126,6 +174,13 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ throw new UserFriendlyException("分类编码和名称不能为空"); } + var displayText = input.DisplayText?.Trim(); + var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); + var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); + ValidateButtonAppearance(appearance); + var locationIds = NormalizeLocationIds(input.LocationIds); + ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); + var duplicated = await _dbContext.SqlSugarClient.Queryable() .AnyAsync(x => !x.IsDeleted && x.Id != id && (x.CategoryCode == code || x.CategoryName == name)); if (duplicated) @@ -135,13 +190,17 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ entity.CategoryCode = code; entity.CategoryName = name; + entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; entity.CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(); entity.State = input.State; + entity.ButtonAppearance = appearance; + entity.AvailabilityType = availabilityType; entity.OrderNum = input.OrderNum; entity.LastModificationTime = DateTime.Now; entity.LastModifierId = CurrentUser?.Id?.ToString(); await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); + await SaveCategoryLocationsAsync(entity.Id, availabilityType, locationIds, entity.LastModifierId, entity.LastModificationTime ?? DateTime.Now); return await GetAsync(id); } @@ -174,12 +233,78 @@ public class LabelCategoryAppService : ApplicationService, ILabelCategoryAppServ Id = x.Id, CategoryCode = x.CategoryCode, CategoryName = x.CategoryName, + DisplayText = x.DisplayText, CategoryPhotoUrl = x.CategoryPhotoUrl, State = x.State, + ButtonAppearance = x.ButtonAppearance, + AvailabilityType = x.AvailabilityType, OrderNum = x.OrderNum }; } + private static void ValidateAvailabilityTypeAndLocations(string availabilityType, List locationIds) + { + if (availabilityType != "ALL" && availabilityType != "SPECIFIED") + { + throw new UserFriendlyException("门店可用范围不合法(ALL/SPECIFIED)"); + } + + if (availabilityType == "SPECIFIED" && locationIds.Count == 0) + { + throw new UserFriendlyException("指定门店范围时必须至少选择一个门店"); + } + } + + private static List NormalizeLocationIds(List? locationIds) + { + return locationIds? + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .Distinct() + .ToList() ?? new(); + } + + private static void ValidateButtonAppearance(string appearance) + { + if (appearance != "TEXT" && appearance != "COLOR" && appearance != "IMAGE") + { + throw new UserFriendlyException("按钮外观不合法(TEXT/COLOR/IMAGE)"); + } + } + + private async Task SaveCategoryLocationsAsync( + string categoryId, + string availabilityType, + List locationIds, + string? currentUserId, + DateTime now) + { + await _dbContext.SqlSugarClient.Deleteable() + .Where(x => x.CategoryId == categoryId) + .ExecuteCommandAsync(); + + if (availabilityType != "SPECIFIED") + { + return; + } + + if (locationIds.Count == 0) + { + return; + } + + var rows = locationIds.Select(locId => new FlLabelCategoryLocationDbEntity + { + Id = _guidGenerator.Create().ToString(), + CategoryId = categoryId, + LocationId = locId, + CreationTime = now, + CreatorId = currentUserId + }).ToList(); + + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); + } + private static PagedResultWithPageDto BuildPagedResult(int skipCount, int maxResultCount, int total, List items) { var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs index 3a6fdf6..9af1412 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/ProductCategoryAppService.cs @@ -35,7 +35,8 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp var query = _dbContext.SqlSugarClient.Queryable() .Where(x => !x.IsDeleted) .WhereIF(!string.IsNullOrWhiteSpace(keyword), - x => x.CategoryCode.Contains(keyword!) || x.CategoryName.Contains(keyword!)) + x => x.CategoryCode.Contains(keyword!) || x.CategoryName.Contains(keyword!) || + (x.DisplayText != null && x.DisplayText.Contains(keyword!))) .WhereIF(input.State != null, x => x.State == input.State); // Sorting 仅允许白名单字段,避免不同数据库列命名导致 Unknown column @@ -77,8 +78,11 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp Id = x.Id, CategoryCode = x.CategoryCode, CategoryName = x.CategoryName, + DisplayText = x.DisplayText, CategoryPhotoUrl = x.CategoryPhotoUrl, + ButtonAppearance = x.ButtonAppearance, State = x.State, + AvailabilityType = x.AvailabilityType, OrderNum = x.OrderNum, LastEdited = x.LastModificationTime ?? x.CreationTime }).ToList(); @@ -98,7 +102,17 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp throw new UserFriendlyException("类别不存在"); } - return MapToGetOutput(entity); + var dto = MapToGetOutput(entity); + if (string.Equals(entity.AvailabilityType, "SPECIFIED", StringComparison.OrdinalIgnoreCase)) + { + var locationIds = await _dbContext.SqlSugarClient.Queryable() + .Where(x => x.CategoryId == entity.Id) + .Select(x => x.LocationId) + .ToListAsync(); + dto.LocationIds = locationIds?.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).Distinct().ToList() ?? new(); + } + + return dto; } /// @@ -113,6 +127,13 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp throw new UserFriendlyException("类别编码和名称不能为空"); } + var displayText = input.DisplayText?.Trim(); + var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); + var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); + ValidateButtonAppearance(appearance); + var locationIds = NormalizeLocationIds(input.LocationIds); + ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); + var duplicated = await _dbContext.SqlSugarClient.Queryable() .AnyAsync(x => !x.IsDeleted && (x.CategoryCode == code || x.CategoryName == name)); if (duplicated) @@ -133,12 +154,16 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp ConcurrencyStamp = _guidGenerator.Create().ToString("N"), CategoryCode = code, CategoryName = name, + DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText, CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(), + ButtonAppearance = appearance, State = input.State, + AvailabilityType = availabilityType, OrderNum = input.OrderNum }; await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync(); + await SaveCategoryLocationsAsync(entity.Id, availabilityType, locationIds, currentUserId, now); return await GetAsync(entity.Id); } @@ -161,6 +186,13 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp throw new UserFriendlyException("类别编码和名称不能为空"); } + var displayText = input.DisplayText?.Trim(); + var appearance = (input.ButtonAppearance ?? "TEXT").Trim().ToUpperInvariant(); + var availabilityType = (input.AvailabilityType ?? "ALL").Trim().ToUpperInvariant(); + ValidateButtonAppearance(appearance); + var locationIds = NormalizeLocationIds(input.LocationIds); + ValidateAvailabilityTypeAndLocations(availabilityType, locationIds); + var duplicated = await _dbContext.SqlSugarClient.Queryable() .AnyAsync(x => !x.IsDeleted && x.Id != id && (x.CategoryCode == code || x.CategoryName == name)); if (duplicated) @@ -170,13 +202,17 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp entity.CategoryCode = code; entity.CategoryName = name; + entity.DisplayText = string.IsNullOrWhiteSpace(displayText) ? null : displayText; entity.CategoryPhotoUrl = input.CategoryPhotoUrl?.Trim(); + entity.ButtonAppearance = appearance; entity.State = input.State; + entity.AvailabilityType = availabilityType; entity.OrderNum = input.OrderNum; entity.LastModificationTime = DateTime.Now; entity.LastModifierId = CurrentUser?.Id?.ToString(); await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync(); + await SaveCategoryLocationsAsync(entity.Id, availabilityType, locationIds, entity.LastModifierId, entity.LastModificationTime ?? DateTime.Now); return await GetAsync(id); } @@ -213,12 +249,78 @@ public class ProductCategoryAppService : ApplicationService, IProductCategoryApp Id = x.Id, CategoryCode = x.CategoryCode, CategoryName = x.CategoryName, + DisplayText = x.DisplayText, CategoryPhotoUrl = x.CategoryPhotoUrl, + ButtonAppearance = x.ButtonAppearance, State = x.State, + AvailabilityType = x.AvailabilityType, OrderNum = x.OrderNum }; } + private static void ValidateAvailabilityTypeAndLocations(string availabilityType, List locationIds) + { + if (availabilityType != "ALL" && availabilityType != "SPECIFIED") + { + throw new UserFriendlyException("门店可用范围不合法(ALL/SPECIFIED)"); + } + + if (availabilityType == "SPECIFIED" && locationIds.Count == 0) + { + throw new UserFriendlyException("指定门店范围时必须至少选择一个门店"); + } + } + + private static List NormalizeLocationIds(List? locationIds) + { + return locationIds? + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .Distinct() + .ToList() ?? new(); + } + + private static void ValidateButtonAppearance(string appearance) + { + if (appearance != "TEXT" && appearance != "COLOR" && appearance != "IMAGE") + { + throw new UserFriendlyException("按钮外观不合法(TEXT/COLOR/IMAGE)"); + } + } + + private async Task SaveCategoryLocationsAsync( + string categoryId, + string availabilityType, + List locationIds, + string? currentUserId, + DateTime now) + { + await _dbContext.SqlSugarClient.Deleteable() + .Where(x => x.CategoryId == categoryId) + .ExecuteCommandAsync(); + + if (availabilityType != "SPECIFIED") + { + return; + } + + if (locationIds.Count == 0) + { + return; + } + + var rows = locationIds.Select(locId => new FlProductCategoryLocationDbEntity + { + Id = _guidGenerator.Create().ToString(), + CategoryId = categoryId, + LocationId = locId, + CreationTime = now, + CreatorId = currentUserId + }).ToList(); + + await _dbContext.SqlSugarClient.Insertable(rows).ExecuteCommandAsync(); + } + private static PagedResultWithPageDto BuildPagedResult(int skipCount, int maxResultCount, int total, List items) { var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount; diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacMenuAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacMenuAppService.cs index e23abcd..66868ba 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacMenuAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/RbacMenuAppService.cs @@ -38,6 +38,8 @@ public class RbacMenuAppService : ApplicationService, IRbacMenuAppService Id = x.Id, ParentId = x.ParentId, MenuName = x.MenuName ?? string.Empty, + RouterName = x.RouterName, + Router = x.Router, PermissionCode = x.PermissionCode, MenuType = x.MenuType, MenuSource = x.MenuSource, @@ -62,6 +64,8 @@ public class RbacMenuAppService : ApplicationService, IRbacMenuAppService Id = entity.Id, ParentId = entity.ParentId, MenuName = entity.MenuName ?? string.Empty, + RouterName = entity.RouterName, + Router = entity.Router, PermissionCode = entity.PermissionCode, MenuType = entity.MenuType, MenuSource = entity.MenuSource, diff --git a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs index 1a89a0d..bb294a3 100644 --- a/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs +++ b/美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/FoodLabeling.Application/Services/UsAppLabelingAppService.cs @@ -50,8 +50,11 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ /// 获取当前门店下四级嵌套数据 /// /// - /// L1 标签分类 fl_label_category;L2 产品分类 fl_product.CategoryId join fl_product_category; + /// L1 标签分类 fl_label_category(含 buttonAppearance;COLOR/IMAGE 展示值在 categoryPhotoUrl);仅对当前门店可用:ALL 或 SPECIFIED 且在 fl_label_category_location; + /// L2 产品分类 fl_product.CategoryId join fl_product_category(同上,展示值在 categoryPhotoUrl); /// L3 产品;L4 与该门店、该标签分类、该产品关联的标签实例(fl_label + fl_label_type)。 + /// L2 仅包含对当前门店可用的类别:AvailabilityType=ALL,或 SPECIFIED 且在 fl_product_category_location 存在该门店记录; + /// 未归类或分类行未关联到 fl_product_category 时仍归入「无」节点。 /// [Authorize] public virtual async Task> GetLabelingTreeAsync(UsAppLabelingTreeInputVo input) @@ -83,10 +86,15 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ LabelCategoryId = c.Id, LabelCategoryName = c.CategoryName, LabelCategoryPhotoUrl = c.CategoryPhotoUrl, + LabelCategoryButtonAppearance = c.ButtonAppearance, LabelCategoryOrderNum = c.OrderNum, ProductCategoryId = p.CategoryId, ProductCategoryName = pc.CategoryName, ProductCategoryPhotoUrl = pc.CategoryPhotoUrl, + ProductCategoryDisplayText = pc.DisplayText, + ProductCategoryButtonAppearance = pc.ButtonAppearance, + ProductCategoryAvailabilityType = pc.AvailabilityType, + ProductCategoryOrderNum = pc.OrderNum, ProductId = p.Id, ProductName = p.ProductName, ProductCode = p.ProductCode, @@ -112,17 +120,22 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ x.LabelCategoryId, x.LabelCategoryName, x.LabelCategoryPhotoUrl, + x.LabelCategoryButtonAppearance, x.LabelCategoryOrderNum }).OrderBy(g => g.Key.LabelCategoryOrderNum).ThenBy(g => g.Key.LabelCategoryName); var result = new List(); foreach (var g1 in byL1) { + var l1Appearance = string.IsNullOrWhiteSpace(g1.Key.LabelCategoryButtonAppearance) + ? "TEXT" + : g1.Key.LabelCategoryButtonAppearance.Trim().ToUpperInvariant(); var l1 = new UsAppLabelCategoryTreeNodeDto { Id = g1.Key.LabelCategoryId, CategoryName = g1.Key.LabelCategoryName ?? string.Empty, CategoryPhotoUrl = g1.Key.LabelCategoryPhotoUrl, + ButtonAppearance = l1Appearance, OrderNum = g1.Key.LabelCategoryOrderNum, ProductCategories = new List() }; @@ -136,7 +149,11 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ { CategoryId = (string?)null, CategoryName = "无", - CategoryPhotoUrl = (string?)null + CategoryPhotoUrl = (string?)null, + DisplayText = (string?)null, + ButtonAppearance = (string?)null, + AvailabilityType = (string?)null, + CategoryOrderNum = int.MaxValue }; } @@ -146,19 +163,34 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ { CategoryId = (string?)categoryId, CategoryName = categoryName, - CategoryPhotoUrl = categoryPhotoUrl + CategoryPhotoUrl = categoryPhotoUrl, + DisplayText = NormalizeNullableUrl(x.ProductCategoryDisplayText), + ButtonAppearance = NormalizeNullableId(x.ProductCategoryButtonAppearance), + AvailabilityType = NormalizeNullableId(x.ProductCategoryAvailabilityType), + CategoryOrderNum = x.ProductCategoryOrderNum }; }) - .OrderBy(g => g.Key.CategoryName); + .OrderBy(g => g.Key.CategoryOrderNum) + .ThenBy(g => g.Key.CategoryName); foreach (var g2 in byL2) { var productsGrouped = g2.GroupBy(x => x.ProductId).OrderBy(pg => pg.First().ProductName); + var appearance = string.IsNullOrWhiteSpace(g2.Key.ButtonAppearance) + ? "TEXT" + : g2.Key.ButtonAppearance.Trim().ToUpperInvariant(); + var availability = string.IsNullOrWhiteSpace(g2.Key.AvailabilityType) + ? "ALL" + : g2.Key.AvailabilityType.Trim().ToUpperInvariant(); var l2 = new UsAppProductCategoryNodeDto { CategoryId = g2.Key.CategoryId, CategoryPhotoUrl = g2.Key.CategoryPhotoUrl, Name = g2.Key.CategoryName, + DisplayText = g2.Key.DisplayText, + ButtonAppearance = appearance, + AvailabilityType = availability, + OrderNum = g2.Key.CategoryOrderNum == int.MaxValue ? 0 : g2.Key.CategoryOrderNum, ItemCount = productsGrouped.Count(), Products = new List() }; @@ -424,7 +456,7 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ } var previewProductId = await ResolvePreviewProductIdAsync(labelRow.Id, input.ProductId); - var normalizedPrintInput = input.PrintInputJson?.ToDictionary(x => x.Key, x => (object?)x.Value); + var normalizedPrintInput = ParsePrintInputJsonToDictionary(input.PrintInputJson); // 解析模板 elements(与预览一致的渲染数据) var resolvedTemplate = await _labelAppService.PreviewAsync(new LabelPreviewResolveInputVo @@ -852,14 +884,29 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ .Where((lp, l, p, c, t, tpl, pc) => l.LocationId == locationId) .Where((lp, l, p, c, t, tpl, pc) => !l.IsDeleted && l.State) .Where((lp, l, p, c, t, tpl, pc) => !p.IsDeleted && p.State) - .Where((lp, l, p, c, t, tpl, pc) => !c.IsDeleted && c.State) + .Where((lp, l, p, c, t, tpl, pc) => + !c.IsDeleted && c.State && + (c.AvailabilityType == "ALL" || + (c.AvailabilityType == "SPECIFIED" && + SqlFunc.Subqueryable() + .Where(loc => loc.CategoryId == c.Id && loc.LocationId == locationId) + .Any()))) .Where((lp, l, p, c, t, tpl, pc) => !t.IsDeleted && t.State) .Where((lp, l, p, c, t, tpl, pc) => !tpl.IsDeleted) + .Where((lp, l, p, c, t, tpl, pc) => + pc.Id == null || + (!pc.IsDeleted && pc.State && + (pc.AvailabilityType == "ALL" || + (pc.AvailabilityType == "SPECIFIED" && + SqlFunc.Subqueryable() + .Where(loc => loc.CategoryId == pc.Id && loc.LocationId == locationId) + .Any())))) .WhereIF(!string.IsNullOrWhiteSpace(filterCategoryId), (lp, l, p, c, t, tpl, pc) => l.LabelCategoryId == filterCategoryId) .WhereIF(!string.IsNullOrWhiteSpace(keyword), (lp, l, p, c, t, tpl, pc) => (l.LabelName != null && l.LabelName.Contains(keyword!)) || (p.ProductName != null && p.ProductName.Contains(keyword!)) || (pc.CategoryName != null && pc.CategoryName.Contains(keyword!)) || + (pc.DisplayText != null && pc.DisplayText.Contains(keyword!)) || (c.CategoryName != null && c.CategoryName.Contains(keyword!)) || (t.TypeName != null && t.TypeName.Contains(keyword!)) || (l.LabelCode != null && l.LabelCode.Contains(keyword!))); @@ -875,6 +922,8 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ public string? LabelCategoryPhotoUrl { get; set; } + public string? LabelCategoryButtonAppearance { get; set; } + public int LabelCategoryOrderNum { get; set; } public string? ProductCategoryId { get; set; } @@ -883,6 +932,14 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ public string? ProductCategoryPhotoUrl { get; set; } + public string? ProductCategoryDisplayText { get; set; } + + public string? ProductCategoryButtonAppearance { get; set; } + + public string? ProductCategoryAvailabilityType { get; set; } + + public int ProductCategoryOrderNum { get; set; } + public string ProductId { get; set; } = string.Empty; public string? ProductName { get; set; } @@ -908,6 +965,32 @@ public class UsAppLabelingAppService : ApplicationService, IUsAppLabelingAppServ public string TemplateUnit { get; set; } = "inch"; } + /// + /// 将 App 入参中的 JsonElement(对象或 null)反序列化为 PreviewAsync 所需的扁平字典。 + /// + private static Dictionary? ParsePrintInputJsonToDictionary(JsonElement? printInputJson) + { + if (printInputJson is null) + { + return null; + } + + var je = printInputJson.Value; + if (je.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined) + { + return null; + } + + try + { + return JsonSerializer.Deserialize>(je.GetRawText()); + } + catch + { + return null; + } + } + private static string NormalizeCategoryName(string? categoryName) { var s = categoryName?.Trim(); diff --git a/项目相关文档/本次新增与优化接口汇总.md b/项目相关文档/本次新增与优化接口汇总.md new file mode 100644 index 0000000..a2f79e6 --- /dev/null +++ b/项目相关文档/本次新增与优化接口汇总.md @@ -0,0 +1,262 @@ +# 本次接口变更汇总(标签 / 产品类别 / 模板组件 / App 树 / Web 会话) + +> 说明:本文只汇总本次迭代中相关内容: +> - 标签模块 Label Categories:对齐“新增类别”原型图(`buttonAppearance` + `categoryPhotoUrl` / 展示文案 / 门店范围) +> - 产品模块 Categories:对齐“新增产品类别”原型图(同上) +> - 模板组件(`fl_label_template_element`):新增字段 `TypeAdd`(并补齐 `ElementName`) +> - App `labeling-tree`:L1 标签分类返回 `buttonAppearance` +> - Web 管理端 `auth-session`:**当前用户菜单与权限**、**退出登录**(食品标签-美国版模块) +> - Web `rbac-menu` 列表/详情:补充返回 `routerName`、`router` +> +> 其余标签打印相关接口不在本文范围内。 + +--- + +## 1. 模板组件字段(`fl_label_template_element`) + +### 1.1 字段变更 + +- 新增字段:`TypeAdd`(元素附加类型,如 `label_Duration`) +- 新增字段:`ElementName`(元素名称,用于更稳定的显示/快照) + +### 1.2 接口影响范围 + +- 模板新增/编辑:保存 `elements[].typeAdd` / `elements[].elementName` +- 模板详情/预览:返回 `elements[].typeAdd` / `elements[].elementName` + +### 1.3 JSON 对齐(elements[]) + +| 前端字段 | 后端字段 | 说明 | +|---|---|---| +| `type` | `ElementType` | 元素类型 | +| `typeAdd` | `TypeAdd` | 元素附加类型 | +| `elementName` | `ElementName` | 元素名称 | + +--- + +## 2. 产品模块 Categories(Products → Categories) + +> 数据库侧你已完成:`fl_product_category` 新字段、`fl_product_category_location` 新表。 + +### 2.1 表结构要点 + +- `fl_product_category`(主表)关键字段: + - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) + - `ButtonAppearance`:`TEXT/COLOR/IMAGE`(前端按类型解析) + - `CategoryPhotoUrl`:与 `ButtonAppearance` 配合——`COLOR` 存颜色值、`IMAGE` 存图片 URL;`TEXT` 可空或不用 + - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` + - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) +- `fl_product_category_location`(关联表): + - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店 + +### 2.2 CRUD 接口(字段扩展) + +接口路径不变,仅扩展字段。 + +#### 2.2.1 列表 + +- **方法**:`GET` +- **路径**:`/api/app/product-category` +- **列表行新增返回**: + - `displayText` + - `buttonAppearance` + - `categoryPhotoUrl` + - `availabilityType` + +#### 2.2.2 详情 + +- **方法**:`GET` +- **路径**:`/api/app/product-category/{id}` +- **新增返回字段**: + - `displayText` + - `buttonAppearance`、`categoryPhotoUrl`(COLOR/IMAGE 的展示数据统一在此字段) + - `availabilityType` + - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) + +#### 2.2.3 新增 + +- **方法**:`POST` +- **路径**:`/api/app/product-category` +- **新增入参字段**: + - `displayText` + - `buttonAppearance`、`categoryPhotoUrl` + - `availabilityType` + - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) + +#### 2.2.4 编辑 + +- **方法**:`PUT` +- **路径**:`/api/app/product-category/{id}` +- **入参同新增** + +#### 2.2.5 删除 + +- **方法**:`DELETE` +- **路径**:`/api/app/product-category/{id}` +- **说明**:逻辑删除;若被产品引用会阻止删除(保持原行为) + +### 2.3 后端校验规则(本次新增) + +- `availabilityType` 仅允许 `ALL/SPECIFIED` + - `SPECIFIED` 时 `locationIds` 至少 1 个 +- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE`(接口层校验枚举;`categoryPhotoUrl` 是否必填由业务/前端约定,`COLOR/IMAGE` 时应写入该字段) + +--- + +## 3. 标签模块 Label Categories(Labels → Label Categories) + +> 数据库侧新增:`fl_label_category` 新字段、`fl_label_category_location` 新表。 + +### 3.1 表结构要点 + +- `fl_label_category`(主表)关键字段: + - `DisplayText`:按钮展示文案(为空可回退 `CategoryName`) + - `ButtonAppearance`:`TEXT/COLOR/IMAGE`(前端按类型解析) + - `CategoryPhotoUrl`:与 `ButtonAppearance` 配合——`COLOR` 存颜色值、`IMAGE` 存图片 URL;`TEXT` 可空或不用 + - **已删除列**(需在库上执行 `DROP COLUMN`):`ButtonTextColor`、`ButtonBgColor`、`ButtonImageUrl`、`ButtonStyleJson` + - `AvailabilityType`:`ALL/SPECIFIED`(门店可用范围) +- `fl_label_category_location`(关联表): + - `(CategoryId, LocationId)` 唯一约束;用于 `AvailabilityType=SPECIFIED` 指定门店(`LocationId` 对应 `location` 表主键) + +### 3.2 CRUD 接口(字段扩展) + +接口路径不变,仅扩展字段。 + +#### 3.2.1 列表 + +- **方法**:`GET` +- **路径**:`/api/app/label-category` +- **列表行新增返回**: + - `displayText` + - `buttonAppearance` + - `categoryPhotoUrl` + - `availabilityType` + +#### 3.2.2 详情 + +- **方法**:`GET` +- **路径**:`/api/app/label-category/{id}` +- **新增返回字段**: + - `displayText` + - `buttonAppearance`、`categoryPhotoUrl`(COLOR/IMAGE 的展示数据统一在此字段) + - `availabilityType` + - `locationIds`(当 `availabilityType=SPECIFIED` 返回门店 Id 列表,否则为空数组) + +#### 3.2.3 新增 + +- **方法**:`POST` +- **路径**:`/api/app/label-category` +- **新增入参字段**: + - `displayText` + - `buttonAppearance`、`categoryPhotoUrl` + - `availabilityType` + - `locationIds`(当 `availabilityType=SPECIFIED` 必填且至少 1 个) + +#### 3.2.4 编辑 + +- **方法**:`PUT` +- **路径**:`/api/app/label-category/{id}` +- **入参同新增** + +#### 3.2.5 删除 + +- **方法**:`DELETE` +- **路径**:`/api/app/label-category/{id}` +- **说明**:逻辑删除;若被标签引用会阻止删除(保持原行为) + +### 3.3 后端校验规则(本次新增) + +- `availabilityType` 仅允许 `ALL/SPECIFIED` + - `SPECIFIED` 时 `locationIds` 至少 1 个 +- `buttonAppearance` 仅允许 `TEXT/COLOR/IMAGE`(接口层校验枚举;`categoryPhotoUrl` 是否必填由业务/前端约定,`COLOR/IMAGE` 时应写入该字段) + +--- + +## 4. App 端 `GET /api/app/us-app-labeling/labeling-tree` + +- **L1(标签分类)节点**:除原有 `categoryName`、`categoryPhotoUrl`、`orderNum` 等外,**返回 `buttonAppearance`**(缺省或空时后端按 `TEXT` 规范化为大写)。 +- **L2(产品分类)节点**:仅 `buttonAppearance` + `categoryPhotoUrl` 承载外观数据(已不再返回 `buttonTextColor`、`buttonBgColor`、`buttonImageUrl`、`buttonStyleJson`)。 + +### 4.1 数据库迁移(两张主表) + +在确认历史数据已按需迁到 `CategoryPhotoUrl` 后,可执行(列不存在时需跳过或调整): + +```sql +ALTER TABLE `fl_label_category` + DROP COLUMN `ButtonTextColor`, + DROP COLUMN `ButtonBgColor`, + DROP COLUMN `ButtonImageUrl`, + DROP COLUMN `ButtonStyleJson`; + +ALTER TABLE `fl_product_category` + DROP COLUMN `ButtonTextColor`, + DROP COLUMN `ButtonBgColor`, + DROP COLUMN `ButtonImageUrl`, + DROP COLUMN `ButtonStyleJson`; +``` + +--- + +## 5. Web 管理端会话(`AuthSession` / 食品标签-美国版) + +> 实现:`IAuthSessionAppService` / `AuthSessionAppService`。需携带与后台一致的 **JWT**(`Authorization: Bearer {token}`)。具体 action 路径以部署环境 **Swagger / OpenAPI** 为准;下列为 ABP 常规约定(`RootPath = api/app`)。 + +### 5.1 获取当前登录用户菜单与权限 + +- **方法**:`GET` +- **路径**(约定):`/api/app/auth-session/my-menus` +- **鉴权**:需要登录 +- **用途**:前端动态路由、侧边栏、按钮级权限(`permissionCodes`) +- **返回体**(`CurrentUserMenuPermissionsOutputDto`,JSON 字段名为 camelCase): + +| 字段 | 类型 | 说明 | +|---|---|---| +| `user` | object | 当前用户简要信息(无密码) | +| `user.id` | guid | 用户 Id | +| `user.userName` | string | 登录名 | +| `user.nick` | string? | 昵称 | +| `user.email` | string? | 邮箱 | +| `user.icon` | string? | 头像 | +| `roleCodes` | string[] | 角色编码列表(已排序) | +| `permissionCodes` | string[] | 权限码列表(已排序;超级管理员常见为 `*:*:*`) | +| `menus` | array | **菜单树**(根节点列表,子节点在 `children`) | + +**菜单树节点**(`CurrentUserMenuNodeDto`)主要字段: + +| 字段 | 说明 | +|---|---| +| `id` / `parentId` | 菜单 Id、父 Id(根父级多为 `"0"`) | +| `menuName` | 菜单名称 | +| `routerName` / `router` | 路由名、路径 | +| `permissionCode` | 权限标识(按钮/接口控制用) | +| `menuType` / `menuSource` | 枚举整型值(与 `Menu` 表一致) | +| `orderNum` / `state` | 排序、是否启用 | +| `menuIcon` / `component` / `isLink` / `isCache` / `isShow` / `query` / `remark` | 与菜单表一致 | +| `children` | 子节点数组 | + +**业务说明**: + +- 数据来源与框架 `UserManager.GetInfoAsync` 一致:按用户角色合并菜单与权限码。 +- 用户名为 **`admin`** 时:与 `AccountService.GetVue3Router` 对齐,返回 **`Menu` 表中未逻辑删除** 的全部菜单再组树(`permissionCodes` 仍为超级管理员约定值)。 + +### 5.2 退出登录 + +- **方法**:`POST` +- **路径**(约定):`/api/app/auth-session/logout` +- **鉴权**:需要登录(未登录或无法解析用户时返回 `false`) +- **请求体**:无 +- **返回**:`boolean` + - `true`:已清除服务端 **用户信息分布式缓存**(与 `AccountService.PostLogout` 一致) + - `false`:当前请求未识别到用户 Id(例如未登录) +- **说明**:JWT 为无状态令牌,**前端仍需丢弃本地 Token**;退出接口主要清理服务端缓存侧用户信息。 + +--- + +## 6. 权限菜单 `rbac-menu` 列表/详情补充字段 + +- **路径**:`GET /api/app/rbac-menu`(列表)、`GET /api/app/rbac-menu/{id}`(详情) +- **新增返回**(与 `menu` 表字段一致,JSON 一般为 camelCase): + - `routerName`:路由名称 + - `router`:路由路径 +- **说明**:树接口 `GET /api/app/rbac-menu/tree`(若已使用)本身已包含完整菜单字段,无需重复改动。 +