前端支持cms
This commit is contained in:
1 parent
6b0c4d054d
commit
f56a63d4ce
16 files changed
+439
-10
No files matched your search
@@ -0,0 +1,93 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace ElectionSystem.Api.Modules.Cms;
|
||||
|
||||
/// <summary>
|
||||
/// Uses the CMS's deliberately small, read-only public contract. It never connects to the CMS database.
|
||||
/// </summary>
|
||||
public sealed class PublicContentClient(HttpClient httpClient, ILogger<PublicContentClient> logger)
|
||||
{
|
||||
public async Task<PublicContentFetch<IReadOnlyList<PublicContentSummary>>> ListAsync(
|
||||
string? contentType,
|
||||
int take,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var requestUri = contentType is null
|
||||
? $"api/public-content?take={take}"
|
||||
: $"api/public-content?contentType={Uri.EscapeDataString(contentType)}&take={take}";
|
||||
|
||||
try
|
||||
{
|
||||
using var response = await httpClient.GetAsync(requestUri, cancellationToken);
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return PublicContentFetch<IReadOnlyList<PublicContentSummary>>.Unavailable;
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var content = await response.Content.ReadFromJsonAsync<List<PublicContentSummary>>(cancellationToken);
|
||||
return new PublicContentFetch<IReadOnlyList<PublicContentSummary>>(content ?? [], true);
|
||||
}
|
||||
catch (HttpRequestException exception)
|
||||
{
|
||||
logger.LogWarning(exception, "The CMS public-content endpoint is unavailable.");
|
||||
return PublicContentFetch<IReadOnlyList<PublicContentSummary>>.Unavailable;
|
||||
}
|
||||
catch (TaskCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogWarning("The CMS public-content endpoint timed out.");
|
||||
return PublicContentFetch<IReadOnlyList<PublicContentSummary>>.Unavailable;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PublicContentFetch<PublicContentDetail?>> GetAsync(string contentItemId, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var response = await httpClient.GetAsync($"api/public-content/{Uri.EscapeDataString(contentItemId)}", cancellationToken);
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
return new PublicContentFetch<PublicContentDetail?>(null, true);
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
return new PublicContentFetch<PublicContentDetail?>(
|
||||
await response.Content.ReadFromJsonAsync<PublicContentDetail>(cancellationToken),
|
||||
true);
|
||||
}
|
||||
catch (HttpRequestException exception)
|
||||
{
|
||||
logger.LogWarning(exception, "The CMS public-content endpoint is unavailable.");
|
||||
return PublicContentFetch<PublicContentDetail?>.Unavailable;
|
||||
}
|
||||
catch (TaskCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogWarning("The CMS public-content endpoint timed out.");
|
||||
return PublicContentFetch<PublicContentDetail?>.Unavailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record PublicContentFetch<T>(T? Value, bool IsAvailable)
|
||||
{
|
||||
public static PublicContentFetch<T> Unavailable { get; } = new(default, false);
|
||||
}
|
||||
|
||||
public sealed record PublicContentSummary(
|
||||
string ContentItemId,
|
||||
string ContentType,
|
||||
string Title,
|
||||
string? RoutePath,
|
||||
DateTime? PublishedUtc);
|
||||
|
||||
public sealed record PublicContentDetail(
|
||||
string ContentItemId,
|
||||
string ContentType,
|
||||
string Title,
|
||||
string? RoutePath,
|
||||
string Html,
|
||||
DateTime? PublishedUtc,
|
||||
string? ElectionReference,
|
||||
string? EffectiveFromUtc,
|
||||
string? EffectiveToUtc);
|
||||
@@ -0,0 +1,34 @@
|
||||
@page "/content/item/{contentItemId}"
|
||||
@model ElectionSystem.Api.Pages.Content.DetailModel
|
||||
@{
|
||||
ViewData["Title"] = Model.Item?.Title ?? "公开内容";
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - 选举与投票信息系统</title>
|
||||
<link rel="stylesheet" href="~/css/site.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header"><a class="brand" href="/">选举与投票信息系统</a><nav aria-label="主导航"><a href="/content">信息公开</a></nav></header>
|
||||
<main class="container article">
|
||||
@if (!Model.CmsAvailable)
|
||||
{
|
||||
<p class="status-note">信息公开服务暂不可用,请稍后刷新页面。</p>
|
||||
}
|
||||
else if (Model.Item is { } item)
|
||||
{
|
||||
<p class="eyebrow">@item.ContentType</p>
|
||||
<h1>@item.Title</h1>
|
||||
@if (item.PublishedUtc is { } publishedUtc)
|
||||
{
|
||||
<p class="published-at">发布时间:<time datetime="@publishedUtc.ToString("O")">@publishedUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm")</time></p>
|
||||
}
|
||||
<div class="cms-body">@Html.Raw(item.Html)</div>
|
||||
}
|
||||
</main>
|
||||
<footer class="site-footer">所有时间和结果以活动正式公告为准。</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
using ElectionSystem.Api.Modules.Cms;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace ElectionSystem.Api.Pages.Content;
|
||||
|
||||
public sealed class DetailModel(PublicContentClient publicContentClient) : PageModel
|
||||
{
|
||||
public PublicContentDetail? Item { get; private set; }
|
||||
public bool CmsAvailable { get; private set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(string contentItemId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await publicContentClient.GetAsync(contentItemId, cancellationToken);
|
||||
CmsAvailable = result.IsAvailable;
|
||||
Item = result.Value;
|
||||
return !CmsAvailable ? Page() : Item is null ? NotFound() : Page();
|
||||
}
|
||||
}
|
||||
Loaded 3 of 16 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user