Compare commits

...

6 Commits

14 changed files with 1495 additions and 3 deletions

1
.idea/.idea.AGSS/.idea/.name generated Normal file
View File

@ -0,0 +1 @@
AGSS

View File

@ -144,7 +144,7 @@ public class AdminRoleControllers:ControllerBase
[HttpPost]
public async Task<IActionResult> SetMenu([FromBody]MenuRequest request)
public async Task<IActionResult> SetMenu([FromBody]UserMenuRequest request)
{
if (string.IsNullOrWhiteSpace(request.Id) || string.IsNullOrWhiteSpace(request.MenuName))
{

View File

@ -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;
}
/// <summary>
/// 新增父级菜单
/// </summary>
[HttpPost("create-parent")]
public async Task<ReturnTemplate> CreateParentMenu([FromBody] MenuRequest request)
{
return await _menuService.CreateParentMenu(request);
}
/// <summary>
/// 编辑父级菜单
/// </summary>
[HttpPut("update-parent")]
public async Task<ReturnTemplate> UpdateParentMenu([FromBody] MenuRequest request)
{
return await _menuService.UpdateParentMenu(request);
}
/// <summary>
/// 新增子级菜单
/// </summary>
[HttpPost("create-child")]
public async Task<ReturnTemplate> CreateChildMenu([FromBody] MenuRequest request)
{
return await _menuService.CreateChildMenu(request);
}
/// <summary>
/// 编辑子级菜单
/// </summary>
[HttpPut("update-child")]
public async Task<ReturnTemplate> UpdateChildMenu([FromBody] MenuRequest request)
{
return await _menuService.UpdateChildMenu(request);
}
/// <summary>
/// 查询菜单全量返回(树形结构)
/// </summary>
[HttpGet("all")]
public async Task<ReturnTemplate> GetAllMenus()
{
return await _menuService.GetAllMenus();
}
/// <summary>
/// 删除菜单(递归删除)
/// </summary>
[HttpDelete("delete/{uuid}")]
public async Task<ReturnTemplate> DeleteMenu(string uuid)
{
return await _menuService.DeleteMenu(uuid);
}
}

View File

@ -10,6 +10,7 @@ namespace AGSS.DbSet
public override DbSet<UserModel> Users { get; set; }
public override DbSet<RoleModel> Roles { get; set; }
public DbSet<DictionaryModel> Dictionaries { get; set; }
public DbSet<MenuModel> Menus { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
@ -25,6 +26,14 @@ namespace AGSS.DbSet
modelBuilder.Entity<DictionaryModel>()
.HasKey(d => d.Uuid); // 假设 Id 是 DictionaryModel 的主键字段
modelBuilder.Entity<MenuModel>()
.HasKey(m => m.Uuid);
modelBuilder.Entity<MenuModel>()
.HasOne<MenuModel>()
.WithMany()
.HasForeignKey(m => m.ParentId)
.OnDelete(DeleteBehavior.Restrict);
// 在这里添加额外的配置,如果需要的话
// 例如:

View File

@ -0,0 +1,425 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<string>("Uuid")
.HasColumnType("text");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreateUserId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Label")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LabelEn")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParentId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParentValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Remark")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tag")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Uuid");
b.ToTable("Dictionaries");
});
modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b =>
{
b.Property<string>("Uuid")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Adaptability")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Component")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Icon")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("Label")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("MenuCode")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("ParentId")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Path")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("Query")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<int>("Sort")
.HasColumnType("integer");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(1)
.HasColumnType("character varying(1)");
b.Property<DateTime?>("UpdateTime")
.HasColumnType("timestamp with time zone");
b.HasKey("Uuid");
b.HasIndex("ParentId");
b.ToTable("Menus");
});
modelBuilder.Entity("AGSS.Models.Entities.RoleModel", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("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<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("Birthday")
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Config")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("Description")
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<string>("JobCode")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<string>("JobName")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("MenuCode")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("MenuName")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<string>("Sex")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("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<string>", b =>
{
b.HasOne("AGSS.Models.Entities.RoleModel", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("AGSS.Models.Entities.UserModel", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("AGSS.Models.Entities.UserModel", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", b =>
{
b.HasOne("AGSS.Models.Entities.UserModel", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AGSS.Migrations
{
/// <inheritdoc />
public partial class AddMenuTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Menus",
columns: table => new
{
Uuid = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
ParentId = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
Path = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
Label = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
Icon = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
MenuCode = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
Adaptability = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
Component = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
Sort = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<string>(type: "character varying(1)", maxLength: 1, nullable: false),
Query = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
CreateTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdateTime = table.Column<DateTime>(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");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Menus");
}
}
}

View File

@ -0,0 +1,425 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<string>("Uuid")
.HasColumnType("text");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreateUserId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Label")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LabelEn")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParentId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ParentValue")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Remark")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tag")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Uuid");
b.ToTable("Dictionaries");
});
modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b =>
{
b.Property<string>("Uuid")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Adaptability")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Component")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Icon")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("Label")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("MenuCode")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("ParentId")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Path")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("Query")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<int>("Sort")
.HasColumnType("integer");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(1)
.HasColumnType("character varying(1)");
b.Property<DateTime?>("UpdateTime")
.HasColumnType("timestamp with time zone");
b.HasKey("Uuid");
b.HasIndex("ParentId");
b.ToTable("Menus");
});
modelBuilder.Entity("AGSS.Models.Entities.RoleModel", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("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<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("Birthday")
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Config")
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("Description")
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<string>("JobCode")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<string>("JobName")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("MenuCode")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("MenuName")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<string>("Sex")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("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<string>", b =>
{
b.HasOne("AGSS.Models.Entities.RoleModel", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("AGSS.Models.Entities.UserModel", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("AGSS.Models.Entities.UserModel", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", b =>
{
b.HasOne("AGSS.Models.Entities.UserModel", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AGSS.Migrations
{
/// <inheritdoc />
public partial class AddMenuTableV2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@ -67,6 +67,70 @@ namespace AGSS.Migrations
b.ToTable("Dictionaries");
});
modelBuilder.Entity("AGSS.Models.Entities.MenuModel", b =>
{
b.Property<string>("Uuid")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Adaptability")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Component")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime>("CreateTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Icon")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("Label")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<string>("MenuCode")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<string>("ParentId")
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Path")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("Query")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<int>("Sort")
.HasColumnType("integer");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(1)
.HasColumnType("character varying(1)");
b.Property<DateTime?>("UpdateTime")
.HasColumnType("timestamp with time zone");
b.HasKey("Uuid");
b.HasIndex("ParentId");
b.ToTable("Menus");
});
modelBuilder.Entity("AGSS.Models.Entities.RoleModel", b =>
{
b.Property<string>("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<string>", b =>
{
b.HasOne("AGSS.Models.Entities.RoleModel", null)

View File

@ -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<MenuResponse> Children { get; set; } = new();
}
public class DeleteMenuRequest
{
public string Uuid { get; set; } = string.Empty;
}

View File

@ -1,6 +1,6 @@
namespace AGSS.Models.DTOs;
public struct MenuRequest
public struct UserMenuRequest
{
public string Id { get; set; }
public string MenuName { get; set; }

View File

@ -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; }
}

View File

@ -55,6 +55,7 @@ builder.Services.AddIdentityCore<UserModel>(options =>
builder.Services.AddScoped<Jwt>();
builder.Services.AddScoped<UserService>();
builder.Services.AddScoped<MenuService>();

View File

@ -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;
}
/// <summary>
/// 新增父级菜单
/// </summary>
public async Task<ReturnTemplate> 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);
}
}
/// <summary>
/// 编辑父级菜单
/// </summary>
public async Task<ReturnTemplate> 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);
}
}
/// <summary>
/// 新增子级菜单
/// </summary>
public async Task<ReturnTemplate> 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);
}
}
/// <summary>
/// 编辑子级菜单
/// </summary>
public async Task<ReturnTemplate> 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);
}
}
/// <summary>
/// 查询菜单全量返回(树形结构)
/// </summary>
public async Task<ReturnTemplate> 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);
}
}
/// <summary>
/// 删除菜单(递归删除)
/// </summary>
public async Task<ReturnTemplate> 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);
}
}
/// <summary>
/// 递归删除菜单及其子菜单
/// </summary>
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);
}
}
/// <summary>
/// 构建菜单树形结构
/// </summary>
private List<MenuResponse> BuildMenuTree(List<MenuModel> allMenus)
{
var menuDict = allMenus.ToDictionary(m => m.Uuid);
var rootMenus = new List<MenuResponse>();
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;
}
/// <summary>
/// 在菜单树中查找指定UUID的菜单响应对象
/// </summary>
private MenuResponse? FindMenuResponse(List<MenuResponse> 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;
}
}