培养方案下放学院维护,学院账号被后端限制在本学院范围;新增按年级、学院、专业联动筛选及维护范围提示。

课程库实行分级维护:学院只能维护本学院的专业必修、专业选修和实践课程;通识课程可查看、引用但不可修改。校级账号保留全校统筹权限。
“排课与课表”已从“教学建设”迁移到“教学运行”,原路由保持不变。
权限规则同时落实在前端和 API,并新增专项测试。
This commit is contained in:
2026-07-24 18:31:33 +08:00 Unverified
parent 022e79c48d
commit 5a7a8d530b
8 changed files with 265 additions and 41 deletions
@@ -27,6 +27,7 @@ public sealed class CurriculumPlansController(
int page = 1,
int pageSize = 20,
string? keyword = null,
Guid? collegeId = null,
Guid? majorId = null,
int? effectiveGrade = null,
CurriculumPlanStatus? status = null,
@@ -35,6 +36,8 @@ public sealed class CurriculumPlansController(
page = Math.Max(1, page);
pageSize = Math.Clamp(pageSize, 10, 100);
var source = ScopedPlans().AsNoTracking();
if (collegeId.HasValue)
source = source.Where(x => x.Major!.CollegeId == collegeId.Value);
if (majorId.HasValue) source = source.Where(x => x.MajorId == majorId);
if (effectiveGrade.HasValue)
source = source.Where(x => x.EffectiveGrade == effectiveGrade);
@@ -76,6 +79,49 @@ public sealed class CurriculumPlansController(
return Ok(new PagedResult<object>(items, total, page, pageSize));
}
[HttpGet("filter-options")]
public async Task<ActionResult> GetFilterOptions(CancellationToken cancellationToken)
{
var collegeId = ScopedCollegeId();
var majors = db.Majors.AsNoTracking().Where(x => x.IsEnabled);
if (collegeId.HasValue)
majors = majors.Where(x => x.CollegeId == collegeId.Value);
var majorOptions = await majors
.OrderBy(x => x.College!.SortOrder)
.ThenBy(x => x.SortOrder)
.ThenBy(x => x.Code)
.Select(x => new
{
x.Id,
x.Code,
x.Name,
x.CollegeId,
CollegeName = x.College!.Name,
x.SchoolingYears
})
.ToListAsync(cancellationToken);
var accessibleCollegeIds = majorOptions
.Select(x => x.CollegeId)
.Distinct()
.ToArray();
var colleges = await db.Colleges.AsNoTracking()
.Where(x => x.IsEnabled && accessibleCollegeIds.Contains(x.Id))
.OrderBy(x => x.SortOrder)
.ThenBy(x => x.Code)
.Select(x => new { x.Id, x.Code, x.Name })
.ToListAsync(cancellationToken);
var grades = await ScopedPlans()
.AsNoTracking()
.Select(x => x.EffectiveGrade)
.Distinct()
.OrderByDescending(x => x)
.ToListAsync(cancellationToken);
return Ok(new { Colleges = colleges, Majors = majorOptions, Grades = grades });
}
[HttpGet("{id:guid}")]
public async Task<ActionResult> GetPlan(Guid id, CancellationToken cancellationToken)
{