- 选民、计票所列表改为每页 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
@@ -26,9 +26,11 @@ dotnet user-secrets remove "Bootstrap:SystemAdministrator:Password" --project sr
|
|||||||
|
|
||||||
- `/admin/dashboard`:仅显示当前授权范围内的汇总。
|
- `/admin/dashboard`:仅显示当前授权范围内的汇总。
|
||||||
- `/admin/regions` 与 `/admin/region-import`:系统管理员导入 AreaCity 四级目录;村/社区使用本地确认目录。
|
- `/admin/regions` 与 `/admin/region-import`:系统管理员导入 AreaCity 四级目录;村/社区使用本地确认目录。
|
||||||
- `/admin/voters` 与 `/admin/voter-import`:按地域维护选民、预校验并确认 Excel 导入。
|
- `/admin/voters` 与 `/admin/voter-import`:按地域维护选民、预校验并确认 Excel 导入。选民列表使用每页 50 条的游标分页,并支持省→市→区县→镇街四级级联筛选。
|
||||||
- `/admin/candidates`:维护候选人资料和服务端允许的状态转换。
|
- `/admin/candidates`:维护候选人资料和服务端允许的状态转换。
|
||||||
- `/admin/roles`:系统管理员授予地域管理员、计票员或监督员角色。
|
- `/admin/roles`:系统管理员授予地域管理员、计票员或监督员角色。
|
||||||
- `/admin/counting-stations`:建立辖区计票所,并向已获地域角色的人员分配具体计票所。
|
- `/admin/counting-stations`:建立辖区计票所,并向已获地域角色的人员分配具体计票所;列表同样使用游标分页及四级级联筛选。
|
||||||
|
|
||||||
|
对应管理 API 提供 `GET /api/admin/voters/cursor` 与 `GET /api/admin/counting-stations/cursor`。`cursor` 只能用于同一组筛选条件;筛选范围、下级地域展开和管理员地域授权均在服务端先行执行。非法游标或跨层级、越权的行政区划参数会返回 `400`,不会放宽数据范围。
|
||||||
|
|
||||||
管理端用于第二阶段基础资料与授权操作。选举活动、匿名投票、线下双录计票和公开结果将在后续阶段接入相应菜单,且不会通过本阶段页面绕过其规则。
|
管理端用于第二阶段基础资料与授权操作。选举活动、匿名投票、线下双录计票和公开结果将在后续阶段接入相应菜单,且不会通过本阶段页面绕过其规则。
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ElectionSystem.Api.Common.Pagination;
|
||||||
|
|
||||||
|
/// <summary>Keyset-pagination response. The cursor represents the last item in the current stable ordering.</summary>
|
||||||
|
public sealed record CursorPagedResult<T>(IReadOnlyCollection<T> Items, string? NextCursor, bool HasMore, int PageSize);
|
||||||
|
|
||||||
|
public static class CursorToken
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
|
||||||
|
|
||||||
|
public static string Encode<T>(T value)
|
||||||
|
{
|
||||||
|
var bytes = JsonSerializer.SerializeToUtf8Bytes(value, SerializerOptions);
|
||||||
|
return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryDecode<T>(string? cursor, out T? value)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
if (string.IsNullOrWhiteSpace(cursor)) return true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var normalized = cursor.Replace('-', '+').Replace('_', '/');
|
||||||
|
normalized = normalized.PadRight(normalized.Length + (4 - normalized.Length % 4) % 4, '=');
|
||||||
|
value = JsonSerializer.Deserialize<T>(Convert.FromBase64String(normalized), SerializerOptions);
|
||||||
|
return value is not null;
|
||||||
|
}
|
||||||
|
catch (FormatException) { return false; }
|
||||||
|
catch (JsonException) { return false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@page "/admin/candidates"
|
@page "/admin/candidates"
|
||||||
|
@rendermode InteractiveServer
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@layout AdminLayout
|
@layout AdminLayout
|
||||||
@inject ElectionDbContext Db
|
@inject ElectionDbContext Db
|
||||||
|
|||||||
Loaded 3 of 15 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user