diff --git a/.idea/.idea.AGSS/.idea/.name b/.idea/.idea.AGSS/.idea/.name new file mode 100644 index 0000000..cc471a1 --- /dev/null +++ b/.idea/.idea.AGSS/.idea/.name @@ -0,0 +1 @@ +AGSS \ No newline at end of file diff --git a/AGSS/Controllers/Admin/AdminRoleControllers.cs b/AGSS/Controllers/Admin/AdminRoleControllers.cs index 09bcf08..9b898ab 100644 --- a/AGSS/Controllers/Admin/AdminRoleControllers.cs +++ b/AGSS/Controllers/Admin/AdminRoleControllers.cs @@ -144,7 +144,7 @@ public class AdminRoleControllers:ControllerBase [HttpPost] - public async Task SetMenu([FromBody]MenuRequest request) + public async Task SetMenu([FromBody]UserMenuRequest request) { if (string.IsNullOrWhiteSpace(request.Id) || string.IsNullOrWhiteSpace(request.MenuName)) { diff --git a/AGSS/Controllers/Menu/MenuController.cs b/AGSS/Controllers/Menu/MenuController.cs new file mode 100644 index 0000000..21a3d31 --- /dev/null +++ b/AGSS/Controllers/Menu/MenuController.cs @@ -0,0 +1,72 @@ +using AGSS.Models.DTOs; +using AGSS.Models.Template; +using AGSS.Services; +using Microsoft.AspNetCore.Mvc; + +namespace AGSS.Controllers.Menu; + +[ApiController] +[Route("api/[controller]")] +public class MenuController : ControllerBase +{ + private readonly MenuService _menuService; + + public MenuController(MenuService menuService) + { + _menuService = menuService; + } + + /// + /// 新增父级菜单 + /// + [HttpPost("create-parent")] + public async Task CreateParentMenu([FromBody] MenuRequest request) + { + return await _menuService.CreateParentMenu(request); + } + + /// + /// 编辑父级菜单 + /// + [HttpPut("update-parent")] + public async Task UpdateParentMenu([FromBody] MenuRequest request) + { + return await _menuService.UpdateParentMenu(request); + } + + /// + /// 新增子级菜单 + /// + [HttpPost("create-child")] + public async Task CreateChildMenu([FromBody] MenuRequest request) + { + return await _menuService.CreateChildMenu(request); + } + + /// + /// 编辑子级菜单 + /// + [HttpPut("update-child")] + public async Task UpdateChildMenu([FromBody] MenuRequest request) + { + return await _menuService.UpdateChildMenu(request); + } + + /// + /// 查询菜单全量返回(树形结构) + /// + [HttpGet("all")] + public async Task GetAllMenus() + { + return await _menuService.GetAllMenus(); + } + + /// + /// 删除菜单(递归删除) + /// + [HttpDelete("delete/{uuid}")] + public async Task DeleteMenu(string uuid) + { + return await _menuService.DeleteMenu(uuid); + } +} \ No newline at end of file diff --git a/AGSS/DbSet/UserSet.cs b/AGSS/DbSet/UserSet.cs index a2dbb4d..a37519d 100644 --- a/AGSS/DbSet/UserSet.cs +++ b/AGSS/DbSet/UserSet.cs @@ -10,6 +10,7 @@ namespace AGSS.DbSet public override DbSet Users { get; set; } public override DbSet Roles { get; set; } public DbSet Dictionaries { get; set; } + public DbSet Menus { get; set; } public ApplicationDbContext(DbContextOptions options) : base(options) @@ -25,6 +26,14 @@ namespace AGSS.DbSet modelBuilder.Entity() .HasKey(d => d.Uuid); // 假设 Id 是 DictionaryModel 的主键字段 + modelBuilder.Entity() + .HasKey(m => m.Uuid); + + modelBuilder.Entity() + .HasOne() + .WithMany() + .HasForeignKey(m => m.ParentId) + .OnDelete(DeleteBehavior.Restrict); // 在这里添加额外的配置,如果需要的话 // 例如: diff --git a/AGSS/Migrations/20250714031454_AddMenuTable.Designer.cs b/AGSS/Migrations/20250714031454_AddMenuTable.Designer.cs new file mode 100644 index 0000000..85a00f4 --- /dev/null +++ b/AGSS/Migrations/20250714031454_AddMenuTable.Designer.cs @@ -0,0 +1,425 @@ +// +using System; +using AGSS.DbSet; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace AGSS.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250714031454_AddMenuTable")] + partial class AddMenuTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AGSS.Models.Entities.DictionaryModel", b => + { + b.Property("Uuid") + .HasColumnType("text"); + + b.Property("CreateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("CreateUserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LabelEn") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentValue") + .IsRequired() + .HasColumnType("text"); + + b.Property("Remark") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Uuid"); + + b.ToTable("Dictionaries"); + }); + + modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b => + { + b.Property("Uuid") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Adaptability") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Component") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Icon") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MenuCode") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ParentId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Query") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Sort") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(1) + .HasColumnType("character varying(1)"); + + b.Property("UpdateTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Uuid"); + + b.HasIndex("ParentId"); + + b.ToTable("Menus"); + }); + + modelBuilder.Entity("AGSS.Models.Entities.RoleModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("AGSS.Models.Entities.UserModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Birthday") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Config") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("JobCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("JobName") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("MenuCode") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("MenuName") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Sex") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b => + { + b.HasOne("AGSS.Models.Entities.MenuModel", null) + .WithMany() + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("AGSS.Models.Entities.RoleModel", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("AGSS.Models.Entities.RoleModel", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/AGSS/Migrations/20250714031454_AddMenuTable.cs b/AGSS/Migrations/20250714031454_AddMenuTable.cs new file mode 100644 index 0000000..2179b32 --- /dev/null +++ b/AGSS/Migrations/20250714031454_AddMenuTable.cs @@ -0,0 +1,56 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AGSS.Migrations +{ + /// + public partial class AddMenuTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Menus", + columns: table => new + { + Uuid = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + ParentId = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Path = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Label = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Icon = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + MenuCode = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + Adaptability = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Component = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Sort = table.Column(type: "integer", nullable: false), + Status = table.Column(type: "character varying(1)", maxLength: 1, nullable: false), + Query = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + CreateTime = table.Column(type: "timestamp with time zone", nullable: false), + UpdateTime = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Menus", x => x.Uuid); + table.ForeignKey( + name: "FK_Menus_Menus_ParentId", + column: x => x.ParentId, + principalTable: "Menus", + principalColumn: "Uuid", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Menus_ParentId", + table: "Menus", + column: "ParentId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Menus"); + } + } +} diff --git a/AGSS/Migrations/20250714035418_AddMenuTableV2.Designer.cs b/AGSS/Migrations/20250714035418_AddMenuTableV2.Designer.cs new file mode 100644 index 0000000..9db0985 --- /dev/null +++ b/AGSS/Migrations/20250714035418_AddMenuTableV2.Designer.cs @@ -0,0 +1,425 @@ +// +using System; +using AGSS.DbSet; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace AGSS.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250714035418_AddMenuTableV2")] + partial class AddMenuTableV2 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AGSS.Models.Entities.DictionaryModel", b => + { + b.Property("Uuid") + .HasColumnType("text"); + + b.Property("CreateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("CreateUserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("LabelEn") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentValue") + .IsRequired() + .HasColumnType("text"); + + b.Property("Remark") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Uuid"); + + b.ToTable("Dictionaries"); + }); + + modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b => + { + b.Property("Uuid") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Adaptability") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Component") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Icon") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MenuCode") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ParentId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Query") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Sort") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(1) + .HasColumnType("character varying(1)"); + + b.Property("UpdateTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Uuid"); + + b.HasIndex("ParentId"); + + b.ToTable("Menus"); + }); + + modelBuilder.Entity("AGSS.Models.Entities.RoleModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("AGSS.Models.Entities.UserModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Birthday") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Config") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("JobCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("JobName") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("MenuCode") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("MenuName") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("Sex") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b => + { + b.HasOne("AGSS.Models.Entities.MenuModel", null) + .WithMany() + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("AGSS.Models.Entities.RoleModel", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("AGSS.Models.Entities.RoleModel", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("AGSS.Models.Entities.UserModel", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/AGSS/Migrations/20250714035418_AddMenuTableV2.cs b/AGSS/Migrations/20250714035418_AddMenuTableV2.cs new file mode 100644 index 0000000..3b0d80c --- /dev/null +++ b/AGSS/Migrations/20250714035418_AddMenuTableV2.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AGSS.Migrations +{ + /// + public partial class AddMenuTableV2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/AGSS/Migrations/ApplicationDbContextModelSnapshot.cs b/AGSS/Migrations/ApplicationDbContextModelSnapshot.cs index 57eb2fc..9cdf659 100644 --- a/AGSS/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/AGSS/Migrations/ApplicationDbContextModelSnapshot.cs @@ -67,6 +67,70 @@ namespace AGSS.Migrations b.ToTable("Dictionaries"); }); + modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b => + { + b.Property("Uuid") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Adaptability") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Component") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Icon") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MenuCode") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ParentId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Query") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Sort") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(1) + .HasColumnType("character varying(1)"); + + b.Property("UpdateTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Uuid"); + + b.HasIndex("ParentId"); + + b.ToTable("Menus"); + }); + modelBuilder.Entity("AGSS.Models.Entities.RoleModel", b => { b.Property("Id") @@ -294,6 +358,14 @@ namespace AGSS.Migrations b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b => + { + b.HasOne("AGSS.Models.Entities.MenuModel", null) + .WithMany() + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("AGSS.Models.Entities.RoleModel", null) diff --git a/AGSS/Models/DTOs/MenuDto.cs b/AGSS/Models/DTOs/MenuDto.cs new file mode 100644 index 0000000..d6facb0 --- /dev/null +++ b/AGSS/Models/DTOs/MenuDto.cs @@ -0,0 +1,39 @@ +namespace AGSS.Models.DTOs; + +public class MenuRequest +{ + public string? Uuid { get; set; } + public string? ParentId { get; set; } + public string Path { get; set; } = string.Empty; + public string Label { get; set; } = string.Empty; + public string Icon { get; set; } = string.Empty; + public string? MenuCode { get; set; } + public string Adaptability { get; set; } = "pc"; + public string Component { get; set; } = string.Empty; + public int Sort { get; set; } + public string Status { get; set; } = "1"; + public string? Query { get; set; } +} + +public class MenuResponse +{ + public string Uuid { get; set; } = string.Empty; + public string? ParentId { get; set; } + public string Path { get; set; } = string.Empty; + public string Label { get; set; } = string.Empty; + public string Icon { get; set; } = string.Empty; + public string? MenuCode { get; set; } + public string Adaptability { get; set; } = string.Empty; + public string Component { get; set; } = string.Empty; + public int Sort { get; set; } + public string Status { get; set; } = string.Empty; + public string? Query { get; set; } + public DateTime CreateTime { get; set; } + public DateTime? UpdateTime { get; set; } + public List Children { get; set; } = new(); +} + +public class DeleteMenuRequest +{ + public string Uuid { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/AGSS/Models/DTOs/MenuRequest.cs b/AGSS/Models/DTOs/MenuRequest.cs index b8a76c2..9d5a8b9 100644 --- a/AGSS/Models/DTOs/MenuRequest.cs +++ b/AGSS/Models/DTOs/MenuRequest.cs @@ -1,8 +1,8 @@ namespace AGSS.Models.DTOs; -public struct MenuRequest +public struct UserMenuRequest { public string Id { get; set; } public string MenuName { get; set; } public string? MenuCode { get; set; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/AGSS/Models/Entities/MenuModel.cs b/AGSS/Models/Entities/MenuModel.cs new file mode 100644 index 0000000..850de39 --- /dev/null +++ b/AGSS/Models/Entities/MenuModel.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.DataAnnotations; + +namespace AGSS.Models.Entities; + +public class MenuModel +{ + [Key] + [MaxLength(50)] + public string Uuid { get; set; } = string.Empty; + + [MaxLength(50)] + public string? ParentId { get; set; } + + [MaxLength(200)] + public string Path { get; set; } = string.Empty; + + [MaxLength(100)] + public string Label { get; set; } = string.Empty; + + [MaxLength(100)] + public string Icon { get; set; } = string.Empty; + + [MaxLength(500)] + public string? MenuCode { get; set; } + + [MaxLength(20)] + public string Adaptability { get; set; } = "pc"; + + [MaxLength(200)] + public string Component { get; set; } = string.Empty; + + public int Sort { get; set; } + + [MaxLength(1)] + public string Status { get; set; } = "1"; + + [MaxLength(1000)] + public string? Query { get; set; } + + public DateTime CreateTime { get; set; } = DateTime.Now; + + public DateTime? UpdateTime { get; set; } +} \ No newline at end of file diff --git a/AGSS/Program.cs b/AGSS/Program.cs index 3e306db..f177555 100644 --- a/AGSS/Program.cs +++ b/AGSS/Program.cs @@ -55,6 +55,7 @@ builder.Services.AddIdentityCore(options => builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); diff --git a/AGSS/Services/MenuService.cs b/AGSS/Services/MenuService.cs new file mode 100644 index 0000000..03d77e3 --- /dev/null +++ b/AGSS/Services/MenuService.cs @@ -0,0 +1,327 @@ +using AGSS.DbSet; +using AGSS.Models.DTOs; +using AGSS.Models.Entities; +using AGSS.Models.Template; +using Microsoft.EntityFrameworkCore; + +namespace AGSS.Services; + +public class MenuService +{ + private readonly ApplicationDbContext _context; + + public MenuService(ApplicationDbContext context) + { + _context = context; + } + + /// + /// 新增父级菜单 + /// + public async Task CreateParentMenu(MenuRequest request) + { + try + { + var menu = new MenuModel + { + Uuid = Guid.NewGuid().ToString(), + ParentId = null, + Path = request.Path, + Label = request.Label, + Icon = request.Icon, + MenuCode = request.MenuCode, + Adaptability = request.Adaptability, + Component = request.Component, + Sort = request.Sort, + Status = request.Status, + Query = request.Query, + CreateTime = DateTime.Now + }; + + _context.Menus.Add(menu); + await _context.SaveChangesAsync(); + + return new ReturnTemplate(200, "父级菜单创建成功", menu); + } + catch (Exception ex) + { + return new ReturnTemplate(500, $"创建父级菜单失败: {ex.Message}", null); + } + } + + /// + /// 编辑父级菜单 + /// + public async Task UpdateParentMenu(MenuRequest request) + { + try + { + if (string.IsNullOrEmpty(request.Uuid)) + { + return new ReturnTemplate(400, "UUID不能为空", null); + } + + var menu = await _context.Menus.FirstOrDefaultAsync(m => m.Uuid == request.Uuid); + if (menu == null) + { + return new ReturnTemplate(404, "菜单不存在", null); + } + + // 确保是父级菜单 + if (!string.IsNullOrEmpty(menu.ParentId)) + { + return new ReturnTemplate(400, "只能编辑父级菜单", null); + } + + menu.Path = request.Path; + menu.Label = request.Label; + menu.Icon = request.Icon; + menu.MenuCode = request.MenuCode; + menu.Adaptability = request.Adaptability; + menu.Component = request.Component; + menu.Sort = request.Sort; + menu.Status = request.Status; + menu.Query = request.Query; + menu.UpdateTime = DateTime.Now; + + await _context.SaveChangesAsync(); + + return new ReturnTemplate(200, "父级菜单更新成功", menu); + } + catch (Exception ex) + { + return new ReturnTemplate(500, $"更新父级菜单失败: {ex.Message}", null); + } + } + + /// + /// 新增子级菜单 + /// + public async Task CreateChildMenu(MenuRequest request) + { + try + { + if (string.IsNullOrEmpty(request.ParentId)) + { + return new ReturnTemplate(400, "父级ID不能为空", null); + } + + // 验证父级菜单是否存在 + var parentMenu = await _context.Menus.FirstOrDefaultAsync(m => m.Uuid == request.ParentId); + if (parentMenu == null) + { + return new ReturnTemplate(404, "父级菜单不存在", null); + } + + var menu = new MenuModel + { + Uuid = Guid.NewGuid().ToString(), + ParentId = request.ParentId, + Path = request.Path, + Label = request.Label, + Icon = request.Icon, + MenuCode = request.MenuCode, + Adaptability = request.Adaptability, + Component = request.Component, + Sort = request.Sort, + Status = request.Status, + Query = request.Query, + CreateTime = DateTime.Now + }; + + _context.Menus.Add(menu); + await _context.SaveChangesAsync(); + + return new ReturnTemplate(200, "子级菜单创建成功", menu); + } + catch (Exception ex) + { + return new ReturnTemplate(500, $"创建子级菜单失败: {ex.Message}", null); + } + } + + /// + /// 编辑子级菜单 + /// + public async Task UpdateChildMenu(MenuRequest request) + { + try + { + if (string.IsNullOrEmpty(request.Uuid)) + { + return new ReturnTemplate(400, "UUID不能为空", null); + } + + var menu = await _context.Menus.FirstOrDefaultAsync(m => m.Uuid == request.Uuid); + if (menu == null) + { + return new ReturnTemplate(404, "菜单不存在", null); + } + + // 确保是子级菜单 + if (string.IsNullOrEmpty(menu.ParentId)) + { + return new ReturnTemplate(400, "只能编辑子级菜单", null); + } + + menu.Path = request.Path; + menu.Label = request.Label; + menu.Icon = request.Icon; + menu.MenuCode = request.MenuCode; + menu.Adaptability = request.Adaptability; + menu.Component = request.Component; + menu.Sort = request.Sort; + menu.Status = request.Status; + menu.Query = request.Query; + menu.UpdateTime = DateTime.Now; + + await _context.SaveChangesAsync(); + + return new ReturnTemplate(200, "子级菜单更新成功", menu); + } + catch (Exception ex) + { + return new ReturnTemplate(500, $"更新子级菜单失败: {ex.Message}", null); + } + } + + /// + /// 查询菜单全量返回(树形结构) + /// + public async Task GetAllMenus() + { + try + { + var allMenus = await _context.Menus + .OrderBy(m => m.Sort) + .ToListAsync(); + + var menuTree = BuildMenuTree(allMenus); + + return new ReturnTemplate(200, "成功", menuTree); + } + catch (Exception ex) + { + return new ReturnTemplate(500, $"查询菜单失败: {ex.Message}", null); + } + } + + /// + /// 删除菜单(递归删除) + /// + public async Task DeleteMenu(string uuid) + { + try + { + if (string.IsNullOrEmpty(uuid)) + { + return new ReturnTemplate(400, "UUID不能为空", null); + } + + var menuToDelete = await _context.Menus.FirstOrDefaultAsync(m => m.Uuid == uuid); + if (menuToDelete == null) + { + return new ReturnTemplate(404, "菜单不存在", null); + } + + // 递归删除所有子菜单 + await DeleteMenuRecursive(uuid); + + return new ReturnTemplate(200, "菜单删除成功", null); + } + catch (Exception ex) + { + return new ReturnTemplate(500, $"删除菜单失败: {ex.Message}", null); + } + } + + /// + /// 递归删除菜单及其子菜单 + /// + private async Task DeleteMenuRecursive(string parentUuid) + { + var children = await _context.Menus.Where(m => m.ParentId == parentUuid).ToListAsync(); + + foreach (var child in children) + { + await DeleteMenuRecursive(child.Uuid); + } + + var menu = await _context.Menus.FirstOrDefaultAsync(m => m.Uuid == parentUuid); + if (menu != null) + { + _context.Menus.Remove(menu); + } + } + + /// + /// 构建菜单树形结构 + /// + private List BuildMenuTree(List allMenus) + { + var menuDict = allMenus.ToDictionary(m => m.Uuid); + var rootMenus = new List(); + + foreach (var menu in allMenus) + { + var menuResponse = new MenuResponse + { + Uuid = menu.Uuid, + ParentId = menu.ParentId, + Path = menu.Path, + Label = menu.Label, + Icon = menu.Icon, + MenuCode = menu.MenuCode, + Adaptability = menu.Adaptability, + Component = menu.Component, + Sort = menu.Sort, + Status = menu.Status, + Query = menu.Query, + CreateTime = menu.CreateTime, + UpdateTime = menu.UpdateTime + }; + + if (string.IsNullOrEmpty(menu.ParentId)) + { + // 根菜单 + rootMenus.Add(menuResponse); + } + else + { + // 子菜单 + if (menuDict.TryGetValue(menu.ParentId, out var parentMenu)) + { + var parentResponse = FindMenuResponse(rootMenus, menu.ParentId); + if (parentResponse != null) + { + parentResponse.Children.Add(menuResponse); + } + } + } + } + + return rootMenus; + } + + /// + /// 在菜单树中查找指定UUID的菜单响应对象 + /// + private MenuResponse? FindMenuResponse(List menus, string uuid) + { + foreach (var menu in menus) + { + if (menu.Uuid == uuid) + { + return menu; + } + + var found = FindMenuResponse(menu.Children, uuid); + if (found != null) + { + return found; + } + } + + return null; + } +} \ No newline at end of file