展板优化
This commit is contained in:
1 parent
b96d0f04a4
commit
7e169d64a2
14 files changed
+8417
-23
No files matched your search
@@ -0,0 +1,104 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Roles = SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin)]
|
||||
[Route("api/venue-display-announcements")]
|
||||
public sealed class VenueDisplayAnnouncementsController(AppDbContext db) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> Get(CancellationToken cancellationToken) => Ok(await db.VenueDisplayAnnouncements.AsNoTracking()
|
||||
.OrderByDescending(x => x.IsPinned).ThenByDescending(x => x.IsUrgent).ThenByDescending(x => x.Priority).ThenByDescending(x => x.UpdatedAt)
|
||||
.Select(x => new { x.Id, x.Title, x.Content, x.Scope, x.TargetId, x.IsPublished, x.IsPinned, x.IsUrgent, x.Priority, x.StartsAt, x.EndsAt, x.UpdatedAt })
|
||||
.ToListAsync(cancellationToken));
|
||||
|
||||
[HttpGet("options")]
|
||||
public async Task<ActionResult> Options(CancellationToken cancellationToken) => Ok(new
|
||||
{
|
||||
Campuses = await db.Campuses.AsNoTracking().Where(x => x.IsEnabled).OrderBy(x => x.Name).Select(x => new { x.Id, x.Name }).ToListAsync(cancellationToken),
|
||||
Buildings = await db.Buildings.AsNoTracking().Where(x => x.IsEnabled).OrderBy(x => x.Name).Select(x => new { x.Id, x.Name, x.CampusId }).ToListAsync(cancellationToken),
|
||||
Classrooms = await db.Classrooms.AsNoTracking().Where(x => x.IsEnabled).OrderBy(x => x.Building!.Name).ThenBy(x => x.Code).Select(x => new { x.Id, x.Name, x.BuildingId, BuildingName = x.Building!.Name }).ToListAsync(cancellationToken)
|
||||
});
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Create(UpsertVenueDisplayAnnouncementRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var announcement = new VenueDisplayAnnouncement { Title = request.Title.Trim(), Content = request.Content.Trim() };
|
||||
var error = Validate(announcement, request);
|
||||
if (error is not null) return ValidationProblem(error);
|
||||
if (!await TargetExistsAsync(request.Scope, request.TargetId, cancellationToken)) return ValidationProblem("公告投放目标不存在或已停用。");
|
||||
Apply(announcement, request);
|
||||
db.VenueDisplayAnnouncements.Add(announcement);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return CreatedAtAction(nameof(Get), new { announcement.Id }, new { announcement.Id });
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<ActionResult> Update(Guid id, UpsertVenueDisplayAnnouncementRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var announcement = await db.VenueDisplayAnnouncements.FindAsync([id], cancellationToken);
|
||||
if (announcement is null) return NotFound();
|
||||
announcement.Title = request.Title.Trim(); announcement.Content = request.Content.Trim();
|
||||
var error = Validate(announcement, request);
|
||||
if (error is not null) return ValidationProblem(error);
|
||||
if (!await TargetExistsAsync(request.Scope, request.TargetId, cancellationToken)) return ValidationProblem("公告投放目标不存在或已停用。");
|
||||
Apply(announcement, request);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<ActionResult> Delete(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var announcement = await db.VenueDisplayAnnouncements.FindAsync([id], cancellationToken);
|
||||
if (announcement is null) return NotFound();
|
||||
db.VenueDisplayAnnouncements.Remove(announcement);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private static string? Validate(VenueDisplayAnnouncement announcement, UpsertVenueDisplayAnnouncementRequest request)
|
||||
{
|
||||
if (announcement.Title.Length is < 1 or > 120) return "公告标题需为 1 至 120 个字符。";
|
||||
if (announcement.Content.Length is < 1 or > 1000) return "公告内容需为 1 至 1000 个字符。";
|
||||
if (request.Scope != VenueDisplayAnnouncementScope.School && !request.TargetId.HasValue) return "请选择公告投放目标。";
|
||||
if (request.Scope == VenueDisplayAnnouncementScope.School && request.TargetId.HasValue) return "全校公告不应指定目标。";
|
||||
if (request.EndsAt.HasValue && request.StartsAt.HasValue && request.EndsAt <= request.StartsAt) return "结束时间必须晚于开始时间。";
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void Apply(VenueDisplayAnnouncement announcement, UpsertVenueDisplayAnnouncementRequest request)
|
||||
{
|
||||
announcement.Scope = request.Scope; announcement.TargetId = request.Scope == VenueDisplayAnnouncementScope.School ? null : request.TargetId;
|
||||
announcement.IsPublished = request.IsPublished; announcement.IsPinned = request.IsPinned; announcement.IsUrgent = request.IsUrgent;
|
||||
announcement.Priority = request.Priority; announcement.StartsAt = request.StartsAt; announcement.EndsAt = request.EndsAt; announcement.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private Task<bool> TargetExistsAsync(VenueDisplayAnnouncementScope scope, Guid? targetId, CancellationToken cancellationToken) => scope switch
|
||||
{
|
||||
VenueDisplayAnnouncementScope.School => Task.FromResult(true),
|
||||
VenueDisplayAnnouncementScope.Campus => db.Campuses.AnyAsync(x => x.Id == targetId && x.IsEnabled, cancellationToken),
|
||||
VenueDisplayAnnouncementScope.Building => db.Buildings.AnyAsync(x => x.Id == targetId && x.IsEnabled, cancellationToken),
|
||||
VenueDisplayAnnouncementScope.Classroom => db.Classrooms.AnyAsync(x => x.Id == targetId && x.IsEnabled, cancellationToken),
|
||||
_ => Task.FromResult(false)
|
||||
};
|
||||
}
|
||||
|
||||
public sealed record UpsertVenueDisplayAnnouncementRequest(
|
||||
[property: Required, StringLength(120)] string Title,
|
||||
[property: Required, StringLength(1000)] string Content,
|
||||
VenueDisplayAnnouncementScope Scope,
|
||||
Guid? TargetId,
|
||||
bool IsPublished,
|
||||
bool IsPinned,
|
||||
bool IsUrgent,
|
||||
[property: Range(0, 100)] int Priority,
|
||||
DateTime? StartsAt,
|
||||
DateTime? EndsAt);
|
||||
Reference in new issue
Block a user