- 选民、计票所列表改为每页 50 条的游标分页,支持上一页/下一页。
- 加入省→市→区县→镇街四级连锁筛选;镇街筛选会包含其下属村级数据。 - 服务端 API 增加: - GET /api/admin/voters/cursor - GET /api/admin/counting-stations/cursor - 服务端会先执行管理员地域授权和行政层级校验,再应用筛选与游标;非法游标、跨层级或越权地区参数返回 400。 - 所有 Blazor 管理页面已启用 Interactive Server,按钮、表单和分页不再只是静态外壳。 - 更新了管理端说明:[admin-management.md](E:\election system\docs\admin-management.md)。 验证通过:dotnet build 0 错误,dotnet test 8/8 通过。尚未在实际浏览器和 MySQL 数据库中做联调
This commit is contained in:
1 parent
660d6fa2c4
commit
08fe656480
15 files changed
+251
-20
No files matched your search
@@ -1,5 +1,6 @@
|
||||
using ElectionSystem.Api.Infrastructure.Auditing;
|
||||
using ElectionSystem.Api.Infrastructure.Persistence;
|
||||
using ElectionSystem.Api.Common.Pagination;
|
||||
using ElectionSystem.Api.Modules.Identity.Domain;
|
||||
using ElectionSystem.Api.Modules.Organization;
|
||||
using ElectionSystem.Api.Modules.Organization.Domain;
|
||||
@@ -86,4 +87,45 @@ public sealed class PersistenceModelTests
|
||||
Assert.NotEqual(PasswordVerificationResult.Failed, hasher.VerifyHashedPassword(user, hash, "Correct-Horse-7!Battery"));
|
||||
Assert.Equal(PasswordVerificationResult.Failed, hasher.VerifyHashedPassword(user, hash, "wrong-password"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cursor_token_round_trips_and_rejects_malformed_values()
|
||||
{
|
||||
var cursor = new TestCursor("张三", Guid.NewGuid());
|
||||
|
||||
var encoded = CursorToken.Encode(cursor);
|
||||
|
||||
Assert.True(CursorToken.TryDecode<TestCursor>(encoded, out var decoded));
|
||||
Assert.Equal(cursor, decoded);
|
||||
Assert.False(CursorToken.TryDecode<TestCursor>("not-a-cursor", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Four_level_filter_returns_selected_subtree_and_rejects_cross_branch_selection()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ElectionDbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
|
||||
await using var context = new ElectionDbContext(options);
|
||||
var province = Region("11", "省", AdministrativeRegionLevel.Province);
|
||||
var city = Region("1101", "市", AdministrativeRegionLevel.City, province.Id);
|
||||
var district = Region("110101", "区", AdministrativeRegionLevel.District, city.Id);
|
||||
var township = Region("110101001", "镇", AdministrativeRegionLevel.Township, district.Id);
|
||||
var village = Region("110101001001", "村", AdministrativeRegionLevel.Village, township.Id);
|
||||
var otherCity = Region("1201", "另一市", AdministrativeRegionLevel.City, province.Id);
|
||||
context.AddRange(province, city, district, township, village, otherCity);
|
||||
await context.SaveChangesAsync();
|
||||
var service = new RegionCascadeFilterService(context);
|
||||
var scope = new HashSet<Guid> { province.Id, city.Id, district.Id, township.Id, village.Id, otherCity.Id };
|
||||
|
||||
var result = await service.ResolveAsync(scope, new AdministrativeRegionFilter(province.Id, city.Id, district.Id, township.Id), CancellationToken.None);
|
||||
|
||||
Assert.Equal(new[] { township.Id, village.Id }.Order(), result.Order());
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => service.ResolveAsync(scope, new AdministrativeRegionFilter(province.Id, otherCity.Id, district.Id, null), CancellationToken.None));
|
||||
}
|
||||
|
||||
private static AdministrativeRegion Region(string code, string name, AdministrativeRegionLevel level, Guid? parentId = null) => new()
|
||||
{
|
||||
Code = code, Name = name, Level = level, ParentId = parentId, Source = "test", SourceVersion = "test"
|
||||
};
|
||||
|
||||
private sealed record TestCursor(string Name, Guid Id);
|
||||
}
|
||||
Reference in new issue
Block a user