Add cascading region selectors for village creation
This commit is contained in:
1 parent
6d2b042d46
commit
ae9e8e0c9d
1 file changed
+5
-4
@@ -8,10 +8,11 @@
|
||||
<PageTitle>行政区划</PageTitle>
|
||||
<section class="admin-page"><div class="admin-page__heading"><div><p class="eyebrow">基础资料 / 地域</p><h1>行政区划目录</h1><p>省、市、区县、镇街来自受控 AreaCity 数据版本;村/社区只可使用本地确认目录。</p></div><a class="admin-button" href="/admin/region-import">导入四级目录</a></div>
|
||||
@if (message is not null) { <div class="admin-alert">@message</div> }
|
||||
@if (systemAdmin) { <EditForm Model="draft" OnValidSubmit="CreateVillageAsync" class="admin-form"><h2>在线新增村/社区</h2><label>所属镇/街道<select @bind="draft.ParentId" required><option value="">请选择镇/街道</option>@foreach (var town in townships) { <option value="@town.Id">@town.Name(@town.Code)</option> }</select></label><label>村/社区代码<input @bind="draft.Code" required maxlength="32" /></label><label>名称<input @bind="draft.Name" required maxlength="128" /></label><label>本地目录版本<input @bind="draft.SourceVersion" required maxlength="64" placeholder="如 2026-08" /></label><button class="admin-button" type="submit">保存村/社区</button></EditForm> }
|
||||
@if (systemAdmin) { <EditForm Model="draft" OnValidSubmit="CreateVillageAsync" class="admin-form"><h2>在线新增村/社区</h2><label>省<select value="@draft.ProvinceId" @onchange="SelectProvinceAsync"><option value="">请选择省</option>@foreach (var x in provinces) { <option value="@x.Id">@x.Name</option> }</select></label><label>市<select value="@draft.CityId" @onchange="SelectCityAsync"><option value="">请选择市</option>@foreach (var x in cities) { <option value="@x.Id">@x.Name</option> }</select></label><label>区县<select value="@draft.DistrictId" @onchange="SelectDistrictAsync"><option value="">请选择区县</option>@foreach (var x in districts) { <option value="@x.Id">@x.Name</option> }</select></label><label>镇/街道<select @bind="draft.ParentId" required><option value="">请选择镇/街道</option>@foreach (var x in townships) { <option value="@x.Id">@x.Name</option> }</select></label><label>村/社区代码<input @bind="draft.Code" required maxlength="32" /></label><label>名称<input @bind="draft.Name" required maxlength="128" /></label><label>本地目录版本<input @bind="draft.SourceVersion" required maxlength="64" placeholder="如 2026-08" /></label><button class="admin-button" type="submit">保存村/社区</button></EditForm> }
|
||||
@if (!loaded) { <p class="admin-loading">正在加载目录…</p> } else if (roots.Count == 0) { <p class="admin-empty">尚未导入行政区划目录。</p> } else { <div class="region-tree" role="tree">@foreach (var node in roots) { <RegionNodeView Node="node" OnToggle="ToggleAsync" /> }</div> }</section>
|
||||
@code { [CascadingParameter] private Task<AuthenticationState> AuthenticationStateTask { get; set; } = default!; private bool loaded, systemAdmin; private string? message; private VillageDraft draft = new(); private HashSet<Guid> scope = []; private readonly List<RegionTreeNode> roots = []; private readonly List<Township> townships = [];
|
||||
protected override async Task OnInitializedAsync() { var id = RegionScopeService.GetRequiredUserId((await AuthenticationStateTask).User); systemAdmin = await Db.UserRegionalRoleAssignments.AnyAsync(x => x.UserId == id && x.Role == RegionalRole.SystemAdministrator && x.RegionId == null); scope = await Scope.GetManagedRegionIdsAsync(id, CancellationToken.None); roots.Clear(); var rootIds = systemAdmin ? null : await Db.UserRegionalRoleAssignments.Where(x => x.UserId == id && x.RegionId.HasValue && x.Role != RegionalRole.CountingStationClerk && x.Role != RegionalRole.Supervisor).Select(x => x.RegionId!.Value).Distinct().ToListAsync(); var rootQuery = Db.AdministrativeRegions.Where(x => systemAdmin ? x.ParentId == null : rootIds!.Contains(x.Id)); roots.AddRange(await rootQuery.OrderBy(x => x.Code).Select(x => new RegionTreeNode(x.Id,x.Code,x.Name,x.Level,x.IsActive)).ToListAsync()); if (systemAdmin) { townships.Clear(); townships.AddRange(await Db.AdministrativeRegions.Where(x => x.IsActive && x.Level == AdministrativeRegionLevel.Township).OrderBy(x => x.Code).Select(x => new Township(x.Id, x.Code, x.Name)).ToListAsync()); } loaded = true; }
|
||||
@code { [CascadingParameter] private Task<AuthenticationState> AuthenticationStateTask { get; set; } = default!; private bool loaded, systemAdmin; private string? message; private VillageDraft draft = new(); private HashSet<Guid> scope = []; private readonly List<RegionTreeNode> roots = []; private readonly List<Township> provinces = [], cities = [], districts = [], townships = [];
|
||||
protected override async Task OnInitializedAsync() { var id = RegionScopeService.GetRequiredUserId((await AuthenticationStateTask).User); systemAdmin = await Db.UserRegionalRoleAssignments.AnyAsync(x => x.UserId == id && x.Role == RegionalRole.SystemAdministrator && x.RegionId == null); scope = await Scope.GetManagedRegionIdsAsync(id, CancellationToken.None); roots.Clear(); var rootIds = systemAdmin ? null : await Db.UserRegionalRoleAssignments.Where(x => x.UserId == id && x.RegionId.HasValue && x.Role != RegionalRole.CountingStationClerk && x.Role != RegionalRole.Supervisor).Select(x => x.RegionId!.Value).Distinct().ToListAsync(); var rootQuery = Db.AdministrativeRegions.Where(x => systemAdmin ? x.ParentId == null : rootIds!.Contains(x.Id)); roots.AddRange(await rootQuery.OrderBy(x => x.Code).Select(x => new RegionTreeNode(x.Id,x.Code,x.Name,x.Level,x.IsActive)).ToListAsync()); if (systemAdmin) await LoadOptionsAsync(provinces, null, AdministrativeRegionLevel.Province); loaded = true; }
|
||||
private async Task SelectProvinceAsync(ChangeEventArgs e) { draft.ProvinceId=Parse(e); draft.CityId=draft.DistrictId=draft.ParentId=null; cities.Clear(); districts.Clear(); townships.Clear(); if(draft.ProvinceId.HasValue) await LoadOptionsAsync(cities,draft.ProvinceId,AdministrativeRegionLevel.City); } private async Task SelectCityAsync(ChangeEventArgs e) { draft.CityId=Parse(e); draft.DistrictId=draft.ParentId=null; districts.Clear(); townships.Clear(); if(draft.CityId.HasValue) await LoadOptionsAsync(districts,draft.CityId,AdministrativeRegionLevel.District); } private async Task SelectDistrictAsync(ChangeEventArgs e) { draft.DistrictId=Parse(e); draft.ParentId=null; townships.Clear(); if(draft.DistrictId.HasValue) await LoadOptionsAsync(townships,draft.DistrictId,AdministrativeRegionLevel.Township); } private async Task LoadOptionsAsync(List<Township> target,Guid? parent,AdministrativeRegionLevel level) { target.Clear(); target.AddRange(await Db.AdministrativeRegions.Where(x=>x.IsActive&&x.Level==level&&x.ParentId==parent).OrderBy(x=>x.Code).Select(x=>new Township(x.Id,x.Code,x.Name)).ToListAsync()); } private static Guid? Parse(ChangeEventArgs e)=>Guid.TryParse(e.Value?.ToString(),out var id)?id:null;
|
||||
private async Task ToggleAsync(RegionTreeNode node) { if (node.Children is null) node.Children = await Db.AdministrativeRegions.Where(x => x.ParentId == node.Id && scope.Contains(x.Id)).OrderBy(x => x.Code).Select(x => new RegionTreeNode(x.Id,x.Code,x.Name,x.Level,x.IsActive)).ToListAsync(); node.Expanded = !node.Expanded; }
|
||||
private async Task CreateVillageAsync() { if (!draft.ParentId.HasValue || string.IsNullOrWhiteSpace(draft.Code) || string.IsNullOrWhiteSpace(draft.Name) || string.IsNullOrWhiteSpace(draft.SourceVersion)) { message = "请完整填写村/社区信息。"; return; } var parent = await Db.AdministrativeRegions.SingleOrDefaultAsync(x => x.Id == draft.ParentId && x.IsActive && x.Level == AdministrativeRegionLevel.Township); if (parent is null) { message = "请选择有效的镇/街道。"; return; } if (await Db.AdministrativeRegions.AnyAsync(x => x.Code == draft.Code.Trim() && x.SourceVersion == draft.SourceVersion.Trim())) { message = "该代码与目录版本已存在。"; return; } Db.AdministrativeRegions.Add(new AdministrativeRegion { ParentId = parent.Id, Code = draft.Code.Trim(), Name = draft.Name.Trim(), Level = AdministrativeRegionLevel.Village, Source = "LocalApprovedVillageImport", SourceVersion = draft.SourceVersion.Trim() }); await Db.SaveChangesAsync(); message = "村/社区已保存。"; draft = new(); await OnInitializedAsync(); }
|
||||
private static string Level(AdministrativeRegionLevel value) => value switch { AdministrativeRegionLevel.Province => "省", AdministrativeRegionLevel.City => "市", AdministrativeRegionLevel.District => "区/县", AdministrativeRegionLevel.Township => "镇/街道", _ => "村/社区" }; private sealed record Township(Guid Id, string Code, string Name); private sealed class VillageDraft { public Guid? ParentId { get; set; } public string Code { get; set; } = ""; public string Name { get; set; } = ""; public string SourceVersion { get; set; } = ""; } }
|
||||
private static string Level(AdministrativeRegionLevel value) => value switch { AdministrativeRegionLevel.Province => "省", AdministrativeRegionLevel.City => "市", AdministrativeRegionLevel.District => "区/县", AdministrativeRegionLevel.Township => "镇/街道", _ => "村/社区" }; private sealed record Township(Guid Id, string Code, string Name); private sealed class VillageDraft { public Guid? ProvinceId { get; set; } public Guid? CityId { get; set; } public Guid? DistrictId { get; set; } public Guid? ParentId { get; set; } public string Code { get; set; } = ""; public string Name { get; set; } = ""; public string SourceVersion { get; set; } = ""; } }
|
||||
Reference in new issue
Block a user