已完成“组织与权限”优化:
基础数据拆分为:组织机构、学年学期、教学场所。 人员档案拆分为:教师档案、学生档案。 基础数据、教师、学生、用户权限均支持 Excel 模板下载、批量导入和导出。 导入按编码/工号/学号/账号新增或更新;整批校验,失败不写入,限制 2000 条、10 MB。 用户列表新增密码重置,包含双重确认、最低 8 位校验和服务端权限保护。 导入严格执行角色及学院数据范围校验,并保护当前超级管理员账号。 保留旧地址重定向,原有入口不会失效。
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
using Jiaowu.Api.Domain.Academic;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
using Jiaowu.Api.Infrastructure.Auth;
|
||||
using Jiaowu.Api.Infrastructure.Excel;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jiaowu.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Roles = ReadRoles)]
|
||||
[Route("api/personnel")]
|
||||
public sealed class PersonnelExcelController(
|
||||
AppDbContext db,
|
||||
ICurrentUserDataScope currentUserDataScope) : ControllerBase
|
||||
{
|
||||
private const string ReadRoles =
|
||||
SystemRoles.SuperAdmin + "," +
|
||||
SystemRoles.AcademicAdmin + "," +
|
||||
SystemRoles.CollegeAdmin + "," +
|
||||
SystemRoles.Counselor + "," +
|
||||
SystemRoles.Teacher + "," +
|
||||
SystemRoles.Student;
|
||||
|
||||
private const string WriteRoles =
|
||||
SystemRoles.SuperAdmin + "," +
|
||||
SystemRoles.AcademicAdmin + "," +
|
||||
SystemRoles.CollegeAdmin;
|
||||
|
||||
private static readonly string[] TeacherHeaders =
|
||||
[
|
||||
"工号", "姓名", "性别", "学院编码", "职称", "任职状态",
|
||||
"入职日期", "教师类别", "联系电话", "电子邮箱", "备注"
|
||||
];
|
||||
|
||||
private static readonly string[] StudentHeaders =
|
||||
[
|
||||
"学号", "姓名", "性别", "行政班编码", "入学年级", "入学日期",
|
||||
"学籍状态", "出生日期", "联系电话", "电子邮箱", "备注"
|
||||
];
|
||||
|
||||
[HttpGet("{kind}/template")]
|
||||
[Authorize(Roles = WriteRoles)]
|
||||
public IActionResult DownloadTemplate(string kind)
|
||||
{
|
||||
if (!TryGetHeaders(kind, out var headers)) return NotFound();
|
||||
var bytes = ExcelWorkbookHelper.Create(
|
||||
"档案导入",
|
||||
headers,
|
||||
[],
|
||||
[
|
||||
"请勿修改第一行列名;每行填写一位人员。",
|
||||
kind.Equals("teachers", StringComparison.OrdinalIgnoreCase)
|
||||
? "工号是唯一标识;学院编码必须已存在。"
|
||||
: "学号是唯一标识;行政班编码必须已存在。",
|
||||
"编号已存在时更新档案,不存在时新增档案。",
|
||||
"日期填写为 yyyy-MM-dd;不适用的可选字段可以留空。",
|
||||
"整批数据会先校验,任一行有误时均不会写入。"
|
||||
]);
|
||||
return File(bytes, ExcelWorkbookHelper.ContentType,
|
||||
$"{RegistryName(kind)}导入模板.xlsx");
|
||||
}
|
||||
|
||||
[HttpGet("{kind}/export")]
|
||||
public async Task<IActionResult> Export(
|
||||
string kind,
|
||||
[FromQuery] PersonnelQuery query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!TryGetHeaders(kind, out var headers)) return NotFound();
|
||||
IReadOnlyList<IReadOnlyList<object?>> rows;
|
||||
if (kind.Equals("teachers", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var source = ApplyTeacherFilters(
|
||||
ApplyTeacherScope(db.Teachers.AsNoTracking()),
|
||||
query);
|
||||
rows = (await source.Include(x => x.College).OrderBy(x => x.TeacherNumber)
|
||||
.ToListAsync(cancellationToken))
|
||||
.Select(x => Row(
|
||||
x.TeacherNumber, x.Name, GenderName(x.Gender), x.College!.Code,
|
||||
x.Title, TeacherStatusName(x.Status), x.HireDate,
|
||||
x.IsExternal ? "外聘" : "校内", x.Phone, x.Email, x.Notes))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var source = ApplyStudentFilters(
|
||||
ApplyStudentScope(db.Students.AsNoTracking()),
|
||||
query);
|
||||
rows = (await source.Include(x => x.AdministrativeClass)
|
||||
.OrderBy(x => x.StudentNumber).ToListAsync(cancellationToken))
|
||||
.Select(x => Row(
|
||||
x.StudentNumber, x.Name, GenderName(x.Gender),
|
||||
x.AdministrativeClass!.Code, x.EnrollmentYear, x.EnrollmentDate,
|
||||
StudentStatusName(x.Status), x.DateOfBirth, x.Phone, x.Email, x.Notes))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var bytes = ExcelWorkbookHelper.Create(RegistryName(kind), headers, rows);
|
||||
return File(bytes, ExcelWorkbookHelper.ContentType,
|
||||
$"{RegistryName(kind)}-{DateTime.Now:yyyyMMdd-HHmm}.xlsx");
|
||||
}
|
||||
|
||||
[HttpPost("{kind}/import")]
|
||||
[Authorize(Roles = WriteRoles)]
|
||||
[RequestSizeLimit(10 * 1024 * 1024)]
|
||||
public async Task<ActionResult<ExcelImportResult>> Import(
|
||||
string kind,
|
||||
IFormFile file,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!TryGetHeaders(kind, out var headers)) return NotFound();
|
||||
IReadOnlyList<ExcelRow> rows;
|
||||
try
|
||||
{
|
||||
rows = await ExcelWorkbookHelper.ReadAsync(file, headers, cancellationToken);
|
||||
}
|
||||
catch (InvalidDataException exception)
|
||||
{
|
||||
return ValidationProblem(exception.Message);
|
||||
}
|
||||
if (rows.Count == 0) return ValidationProblem("Excel 中没有可导入的数据。");
|
||||
|
||||
var errors = new List<string>();
|
||||
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
var result = kind.Equals("teachers", StringComparison.OrdinalIgnoreCase)
|
||||
? await ImportTeachersAsync(rows, errors, cancellationToken)
|
||||
: await ImportStudentsAsync(rows, errors, cancellationToken);
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
await transaction.RollbackAsync(cancellationToken);
|
||||
return ImportValidationProblem(errors);
|
||||
}
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
await transaction.RollbackAsync(cancellationToken);
|
||||
return Conflict(new ProblemDetails
|
||||
{
|
||||
Title = "导入失败",
|
||||
Detail = "存在重复编号或无效关联,未写入任何档案。",
|
||||
Status = StatusCodes.Status409Conflict
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ExcelImportResult> ImportTeachersAsync(
|
||||
IReadOnlyList<ExcelRow> rows,
|
||||
List<string> errors,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var existing = await db.Teachers.ToDictionaryAsync(x => x.TeacherNumber,
|
||||
StringComparer.OrdinalIgnoreCase, cancellationToken);
|
||||
var colleges = await db.Colleges.AsNoTracking().ToDictionaryAsync(x => x.Code,
|
||||
StringComparer.OrdinalIgnoreCase, cancellationToken);
|
||||
var created = 0;
|
||||
var updated = 0;
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var number = Required(row, "工号", errors);
|
||||
var name = Required(row, "姓名", errors);
|
||||
var collegeCode = Required(row, "学院编码", errors);
|
||||
var gender = ParseGender(row, errors);
|
||||
var status = ParseTeacherStatus(row, errors);
|
||||
var hireDate = ParseDate(row, "入职日期", false, errors);
|
||||
var isExternal = ParseTeacherType(row, errors);
|
||||
if (number is null || name is null || collegeCode is null ||
|
||||
gender is null || status is null || isExternal is null) continue;
|
||||
if (!colleges.TryGetValue(collegeCode, out var college))
|
||||
{
|
||||
errors.Add($"第 {row.RowNumber} 行:学院编码“{collegeCode}”不存在。");
|
||||
continue;
|
||||
}
|
||||
if (!CanAccessCollege(college.Id))
|
||||
{
|
||||
errors.Add($"第 {row.RowNumber} 行:无权维护学院“{college.Name}”的教师。");
|
||||
continue;
|
||||
}
|
||||
var entity = existing.GetValueOrDefault(number);
|
||||
if (entity is not null && !CanAccessCollege(entity.CollegeId))
|
||||
{
|
||||
errors.Add($"第 {row.RowNumber} 行:工号“{number}”已属于无权维护的学院。");
|
||||
continue;
|
||||
}
|
||||
if (entity is null)
|
||||
{
|
||||
entity = new Teacher
|
||||
{
|
||||
TeacherNumber = number,
|
||||
Name = name,
|
||||
CollegeId = college.Id
|
||||
};
|
||||
db.Teachers.Add(entity);
|
||||
existing[number] = entity;
|
||||
created++;
|
||||
}
|
||||
else updated++;
|
||||
entity.Name = name;
|
||||
entity.Gender = gender.Value;
|
||||
entity.CollegeId = college.Id;
|
||||
entity.Title = Optional(row, "职称");
|
||||
entity.Status = status.Value;
|
||||
entity.HireDate = hireDate;
|
||||
entity.IsExternal = isExternal.Value;
|
||||
entity.Phone = Optional(row, "联系电话");
|
||||
entity.Email = Optional(row, "电子邮箱");
|
||||
entity.Notes = Optional(row, "备注");
|
||||
}
|
||||
return new(created, updated, rows.Count);
|
||||
}
|
||||
|
||||
private async Task<ExcelImportResult> ImportStudentsAsync(
|
||||
IReadOnlyList<ExcelRow> rows,
|
||||
List<string> errors,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var existing = await db.Students.Include(x => x.AdministrativeClass)
|
||||
.ThenInclude(x => x!.Major)
|
||||
.ToDictionaryAsync(x => x.StudentNumber, StringComparer.OrdinalIgnoreCase,
|
||||
cancellationToken);
|
||||
var classes = await db.AdministrativeClasses.AsNoTracking().Include(x => x.Major)
|
||||
.ToDictionaryAsync(x => x.Code, StringComparer.OrdinalIgnoreCase,
|
||||
cancellationToken);
|
||||
var created = 0;
|
||||
var updated = 0;
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var number = Required(row, "学号", errors);
|
||||
var name = Required(row, "姓名", errors);
|
||||
var classCode = Required(row, "行政班编码", errors);
|
||||
var gender = ParseGender(row, errors);
|
||||
var enrollmentYear = ParseInt(row, "入学年级", 2000, 2200, errors);
|
||||
var enrollmentDate = ParseDate(row, "入学日期", true, errors);
|
||||
var status = ParseStudentStatus(row, errors);
|
||||
var dateOfBirth = ParseDate(row, "出生日期", false, errors);
|
||||
if (number is null || name is null || classCode is null || gender is null ||
|
||||
enrollmentYear is null || enrollmentDate is null || status is null) continue;
|
||||
if (!classes.TryGetValue(classCode, out var targetClass))
|
||||
{
|
||||
errors.Add($"第 {row.RowNumber} 行:行政班编码“{classCode}”不存在。");
|
||||
continue;
|
||||
}
|
||||
var collegeId = targetClass.Major!.CollegeId;
|
||||
if (!CanAccessCollege(collegeId))
|
||||
{
|
||||
errors.Add($"第 {row.RowNumber} 行:无权维护行政班“{targetClass.Name}”的学生。");
|
||||
continue;
|
||||
}
|
||||
var entity = existing.GetValueOrDefault(number);
|
||||
if (entity is not null &&
|
||||
!CanAccessCollege(entity.AdministrativeClass!.Major!.CollegeId))
|
||||
{
|
||||
errors.Add($"第 {row.RowNumber} 行:学号“{number}”已属于无权维护的学院。");
|
||||
continue;
|
||||
}
|
||||
if (entity is null)
|
||||
{
|
||||
entity = new Student
|
||||
{
|
||||
StudentNumber = number,
|
||||
Name = name,
|
||||
AdministrativeClassId = targetClass.Id,
|
||||
EnrollmentDate = enrollmentDate.Value
|
||||
};
|
||||
db.Students.Add(entity);
|
||||
existing[number] = entity;
|
||||
created++;
|
||||
}
|
||||
else updated++;
|
||||
entity.Name = name;
|
||||
entity.Gender = gender.Value;
|
||||
entity.AdministrativeClassId = targetClass.Id;
|
||||
entity.EnrollmentYear = enrollmentYear.Value;
|
||||
entity.EnrollmentDate = enrollmentDate.Value;
|
||||
entity.Status = status.Value;
|
||||
entity.DateOfBirth = dateOfBirth;
|
||||
entity.Phone = Optional(row, "联系电话");
|
||||
entity.Email = Optional(row, "电子邮箱");
|
||||
entity.Notes = Optional(row, "备注");
|
||||
}
|
||||
return new(created, updated, rows.Count);
|
||||
}
|
||||
|
||||
private IQueryable<Teacher> ApplyTeacherFilters(
|
||||
IQueryable<Teacher> source,
|
||||
PersonnelQuery query)
|
||||
{
|
||||
if (query.CollegeId.HasValue)
|
||||
source = source.Where(x => x.CollegeId == query.CollegeId);
|
||||
if (query.TeacherStatus.HasValue)
|
||||
source = source.Where(x => x.Status == query.TeacherStatus);
|
||||
if (!string.IsNullOrWhiteSpace(query.Keyword))
|
||||
{
|
||||
var keyword = query.Keyword.Trim();
|
||||
source = source.Where(x =>
|
||||
x.TeacherNumber.Contains(keyword) || x.Name.Contains(keyword) ||
|
||||
(x.Title != null && x.Title.Contains(keyword)));
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private IQueryable<Student> ApplyStudentFilters(
|
||||
IQueryable<Student> source,
|
||||
PersonnelQuery query)
|
||||
{
|
||||
if (query.CollegeId.HasValue)
|
||||
source = source.Where(x =>
|
||||
x.AdministrativeClass!.Major!.CollegeId == query.CollegeId);
|
||||
if (query.MajorId.HasValue)
|
||||
source = source.Where(x => x.AdministrativeClass!.MajorId == query.MajorId);
|
||||
if (query.ClassId.HasValue)
|
||||
source = source.Where(x => x.AdministrativeClassId == query.ClassId);
|
||||
if (query.EnrollmentYear.HasValue)
|
||||
source = source.Where(x => x.EnrollmentYear == query.EnrollmentYear);
|
||||
if (query.StudentStatus.HasValue)
|
||||
source = source.Where(x => x.Status == query.StudentStatus);
|
||||
if (!string.IsNullOrWhiteSpace(query.Keyword))
|
||||
{
|
||||
var keyword = query.Keyword.Trim();
|
||||
source = source.Where(x =>
|
||||
x.StudentNumber.Contains(keyword) || x.Name.Contains(keyword));
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
private IQueryable<Teacher> ApplyTeacherScope(IQueryable<Teacher> source)
|
||||
{
|
||||
var scope = currentUserDataScope.Current;
|
||||
if (scope.Scope == DataScope.All) return source;
|
||||
if (scope.Scope == DataScope.College)
|
||||
return source.Where(x => x.CollegeId == scope.RestrictedCollegeId);
|
||||
if (scope.Scope == DataScope.Class)
|
||||
return source.Where(teacher => db.TeachingTasks.Any(task =>
|
||||
task.Teachers.Any(item => item.TeacherId == teacher.Id) &&
|
||||
task.Classes.Any(item =>
|
||||
item.AdministrativeClass!.CounselorUserId == scope.UserId)));
|
||||
var userId = scope.UserId;
|
||||
return scope.IsInRole(SystemRoles.Teacher)
|
||||
? source.Where(x => x.UserId == userId)
|
||||
: source.Where(_ => false);
|
||||
}
|
||||
|
||||
private IQueryable<Student> ApplyStudentScope(IQueryable<Student> source)
|
||||
{
|
||||
var scope = currentUserDataScope.Current;
|
||||
if (scope.Scope == DataScope.All) return source;
|
||||
if (scope.Scope == DataScope.College)
|
||||
return source.Where(x =>
|
||||
x.AdministrativeClass!.Major!.CollegeId == scope.RestrictedCollegeId);
|
||||
if (scope.Scope == DataScope.Class)
|
||||
return source.Where(x => x.AdministrativeClass!.CounselorUserId == scope.UserId);
|
||||
var userId = scope.UserId;
|
||||
var isTeacher = scope.IsInRole(SystemRoles.Teacher);
|
||||
var isStudent = scope.IsInRole(SystemRoles.Student);
|
||||
return source.Where(student =>
|
||||
isStudent && student.UserId == userId ||
|
||||
isTeacher && db.TeachingTasks.Any(task =>
|
||||
task.Teachers.Any(item => item.Teacher!.UserId == userId) &&
|
||||
task.Classes.Any(item =>
|
||||
item.AdministrativeClassId == student.AdministrativeClassId)));
|
||||
}
|
||||
|
||||
private bool CanAccessCollege(Guid collegeId) =>
|
||||
currentUserDataScope.Current.CanAccessCollege(collegeId);
|
||||
|
||||
private ActionResult ImportValidationProblem(IReadOnlyList<string> errors)
|
||||
{
|
||||
foreach (var error in errors.Take(50))
|
||||
ModelState.AddModelError("file", error);
|
||||
if (errors.Count > 50)
|
||||
ModelState.AddModelError("file", $"另有 {errors.Count - 50} 条错误未显示。");
|
||||
return ValidationProblem(ModelState);
|
||||
}
|
||||
|
||||
private static bool TryGetHeaders(string kind, out string[] headers)
|
||||
{
|
||||
if (kind.Equals("teachers", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
headers = TeacherHeaders;
|
||||
return true;
|
||||
}
|
||||
if (kind.Equals("students", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
headers = StudentHeaders;
|
||||
return true;
|
||||
}
|
||||
headers = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string? Required(ExcelRow row, string header, List<string> errors)
|
||||
{
|
||||
var value = Optional(row, header);
|
||||
if (value is not null) return value;
|
||||
errors.Add($"第 {row.RowNumber} 行:“{header}”不能为空。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? Optional(ExcelRow row, string header) =>
|
||||
string.IsNullOrWhiteSpace(row[header]) ? null : row[header].Trim();
|
||||
|
||||
private static int? ParseInt(
|
||||
ExcelRow row, string header, int minimum, int maximum, List<string> errors)
|
||||
{
|
||||
if (int.TryParse(row[header], out var value) && value >= minimum && value <= maximum)
|
||||
return value;
|
||||
errors.Add($"第 {row.RowNumber} 行:“{header}”必须是 {minimum} 至 {maximum} 的整数。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DateOnly? ParseDate(
|
||||
ExcelRow row, string header, bool required, List<string> errors)
|
||||
{
|
||||
var value = row[header];
|
||||
if (string.IsNullOrWhiteSpace(value) && !required) return null;
|
||||
if (DateOnly.TryParse(value, out var date)) return date;
|
||||
errors.Add($"第 {row.RowNumber} 行:“{header}”必须是有效日期(如 2026-09-01)。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Gender? ParseGender(ExcelRow row, List<string> errors)
|
||||
{
|
||||
var value = row["性别"];
|
||||
if (string.IsNullOrWhiteSpace(value) || value is "未设置" or "未知" ||
|
||||
value.Equals("Unknown", StringComparison.OrdinalIgnoreCase)) return Gender.Unknown;
|
||||
if (value == "男" || value.Equals("Male", StringComparison.OrdinalIgnoreCase))
|
||||
return Gender.Male;
|
||||
if (value == "女" || value.Equals("Female", StringComparison.OrdinalIgnoreCase))
|
||||
return Gender.Female;
|
||||
errors.Add($"第 {row.RowNumber} 行:“性别”请填写男、女或未设置。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static TeacherStatus? ParseTeacherStatus(ExcelRow row, List<string> errors)
|
||||
{
|
||||
var value = row["任职状态"];
|
||||
if (value == "在职" || value.Equals("Active", StringComparison.OrdinalIgnoreCase))
|
||||
return TeacherStatus.Active;
|
||||
if (value == "离岗" || value.Equals("OnLeave", StringComparison.OrdinalIgnoreCase))
|
||||
return TeacherStatus.OnLeave;
|
||||
if (value == "退休" || value.Equals("Retired", StringComparison.OrdinalIgnoreCase))
|
||||
return TeacherStatus.Retired;
|
||||
if (value == "离职" || value.Equals("Departed", StringComparison.OrdinalIgnoreCase))
|
||||
return TeacherStatus.Departed;
|
||||
errors.Add($"第 {row.RowNumber} 行:“任职状态”请填写在职、离岗、退休或离职。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static StudentStatus? ParseStudentStatus(ExcelRow row, List<string> errors)
|
||||
{
|
||||
var value = row["学籍状态"];
|
||||
if (value == "在籍" || value.Equals("Active", StringComparison.OrdinalIgnoreCase))
|
||||
return StudentStatus.Active;
|
||||
if (value == "休学" || value.Equals("Suspended", StringComparison.OrdinalIgnoreCase))
|
||||
return StudentStatus.Suspended;
|
||||
if (value == "毕业" || value.Equals("Graduated", StringComparison.OrdinalIgnoreCase))
|
||||
return StudentStatus.Graduated;
|
||||
if (value == "退学" || value.Equals("Withdrawn", StringComparison.OrdinalIgnoreCase))
|
||||
return StudentStatus.Withdrawn;
|
||||
errors.Add($"第 {row.RowNumber} 行:“学籍状态”请填写在籍、休学、毕业或退学。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool? ParseTeacherType(ExcelRow row, List<string> errors)
|
||||
{
|
||||
var value = row["教师类别"];
|
||||
if (value == "校内" || value == "否" || value == "0") return false;
|
||||
if (value == "外聘" || value == "是" || value == "1") return true;
|
||||
errors.Add($"第 {row.RowNumber} 行:“教师类别”请填写校内或外聘。");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<object?> Row(params object?[] values) => values;
|
||||
private static string RegistryName(string kind) =>
|
||||
kind.Equals("teachers", StringComparison.OrdinalIgnoreCase) ? "教师档案" : "学生档案";
|
||||
private static string GenderName(Gender value) => value switch
|
||||
{
|
||||
Gender.Male => "男",
|
||||
Gender.Female => "女",
|
||||
_ => "未设置"
|
||||
};
|
||||
private static string TeacherStatusName(TeacherStatus value) => value switch
|
||||
{
|
||||
TeacherStatus.Active => "在职",
|
||||
TeacherStatus.OnLeave => "离岗",
|
||||
TeacherStatus.Retired => "退休",
|
||||
_ => "离职"
|
||||
};
|
||||
private static string StudentStatusName(StudentStatus value) => value switch
|
||||
{
|
||||
StudentStatus.Active => "在籍",
|
||||
StudentStatus.Suspended => "休学",
|
||||
StudentStatus.Graduated => "毕业",
|
||||
_ => "退学"
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user