教师档案增加“激活账号”:管理员设置初始密码,服务端校验管理权限、学院数据范围、在职状态、工号冲突,并自动关联教师角色与档案。

已发布课表增加“发布后修改”:创建修订草稿,旧课表继续生效;修订版重新发布后再替换旧版本。
修复 390px 窄屏下课表页头按钮溢出。
This commit is contained in:
2026-07-25 10:09:05 +08:00 Unverified
parent ce65fe82d9
commit db91988264
5 changed files with 333 additions and 11 deletions
@@ -5,6 +5,7 @@ using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@@ -15,7 +16,8 @@ namespace Jiaowu.Api.Controllers;
[Route("api/personnel")]
public sealed class PersonnelController(
AppDbContext db,
ICurrentUserDataScope currentUserDataScope) : ControllerBase
ICurrentUserDataScope currentUserDataScope,
UserManager<ApplicationUser> userManager) : ControllerBase
{
private const string ReadRoles =
SystemRoles.SuperAdmin + "," +
@@ -149,6 +151,61 @@ public sealed class PersonnelController(
return await SaveNoContentAsync(cancellationToken);
}
[HttpPost("teachers/{id:guid}/activate-account")]
[Authorize(Roles = WriteRoles)]
public async Task<ActionResult> ActivateTeacherAccount(
Guid id,
TeacherAccountActivationRequest request,
CancellationToken cancellationToken)
{
var teacher = await db.Teachers.FindAsync([id], cancellationToken);
if (teacher is null) return NotFound();
if (!CanAccessCollege(teacher.CollegeId)) return Forbid();
if (teacher.Status != TeacherStatus.Active)
{
return ConflictProblem("仅在职教师可以激活登录账号。");
}
if (teacher.UserId.HasValue)
{
return ConflictProblem("该教师档案已经关联登录账号。");
}
var userName = teacher.TeacherNumber.Trim();
if (await userManager.FindByNameAsync(userName) is not null)
{
return ConflictProblem("该工号已有登录账号但未正确关联,请到账号管理中核对。");
}
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);
var user = new ApplicationUser
{
UserName = userName,
DisplayName = teacher.Name,
StaffNumber = userName,
CollegeId = teacher.CollegeId,
IsEnabled = true,
LockoutEnabled = true
};
var result = await userManager.CreateAsync(user, request.Password);
if (!result.Succeeded)
{
await transaction.RollbackAsync(cancellationToken);
return IdentityValidationProblem(result);
}
result = await userManager.AddToRoleAsync(user, SystemRoles.Teacher);
if (!result.Succeeded)
{
await transaction.RollbackAsync(cancellationToken);
return IdentityValidationProblem(result);
}
teacher.UserId = user.Id;
await db.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
return Ok(new { user.Id, UserName = userName });
}
[HttpGet("students")]
public async Task<ActionResult<PagedResult<object>>> GetStudents(
[FromQuery] PersonnelQuery query,
@@ -393,6 +450,13 @@ public sealed class PersonnelController(
Status = StatusCodes.Status409Conflict
});
private ActionResult IdentityValidationProblem(IdentityResult result)
{
foreach (var error in result.Errors)
ModelState.AddModelError(error.Code, error.Description);
return ValidationProblem(ModelState);
}
private static string? Normalize(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
@@ -436,3 +500,6 @@ public sealed record StudentRequest(
[MaxLength(30)] string? Phone,
[EmailAddress, MaxLength(100)] string? Email,
[MaxLength(500)] string? Notes);
public sealed record TeacherAccountActivationRequest(
[Required, MinLength(8), MaxLength(100)] string Password);