- 选民、计票所列表改为每页 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:
biss committed 2026-08-29 20:01:02 +08:00
1 parent 660d6fa2c4
commit 08fe656480
15 files changed
+251 -20

No files matched your search

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