From 11b90517ee3c75612ba3cd488b659fead0a86480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E6=A2=93=E5=BD=AD?= Date: Wed, 18 Mar 2026 16:25:49 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B5=9B=E4=BA=8B=E9=A1=B9=E7=9B=AE=E5=85=B3?= =?UTF-8?q?=E8=81=94=E8=A1=A8=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Client/EventClientController.cs | 54 ++-- YD_Event.Application/Event/Dtos/EventDtos.cs | 74 ++++- .../Event/Dtos/EventStepDtos.cs | 24 ++ .../Event/EventArrangementService.cs | 50 +++- YD_Event.Application/Event/EventService.cs | 276 +++++++++++++----- .../Event/UserCompetitionService.cs | 71 +++-- .../Project/Dtos/ProjectDtos.cs | 16 - .../Project/ProjectService.cs | 12 +- YD_Event.Application/YD_Event.Application.xml | 89 ++++-- YD_Event.Application/applicationsettings.json | 2 +- YD_Event.Core/Entities/EventProject.cs | 47 +++ YD_Event.Core/Entities/EventProjectGroup.cs | 55 ++++ YD_Event.Core/Entities/Project.cs | 5 - YD_Event.Core/YD_Event.Core.xml | 181 ++++++------ 14 files changed, 670 insertions(+), 286 deletions(-) create mode 100644 YD_Event.Core/Entities/EventProject.cs create mode 100644 YD_Event.Core/Entities/EventProjectGroup.cs diff --git a/YD_Event.Application/Client/EventClientController.cs b/YD_Event.Application/Client/EventClientController.cs index 7cf7177..f0b9b54 100644 --- a/YD_Event.Application/Client/EventClientController.cs +++ b/YD_Event.Application/Client/EventClientController.cs @@ -18,6 +18,7 @@ public class EventClientController : IDynamicApiController { private readonly ISqlSugarRepository _eventRepository; private readonly ISqlSugarRepository _projectRepository; + private readonly ISqlSugarRepository _eventProjectRepository; private readonly ISqlSugarRepository _groupRepository; private readonly ISqlSugarRepository _registrationRepository; private readonly ISqlSugarRepository _registrationItemRepository; @@ -31,6 +32,7 @@ public class EventClientController : IDynamicApiController public EventClientController( ISqlSugarRepository eventRepository, ISqlSugarRepository projectRepository, + ISqlSugarRepository eventProjectRepository, ISqlSugarRepository groupRepository, ISqlSugarRepository registrationRepository, ISqlSugarRepository registrationItemRepository, @@ -43,6 +45,7 @@ public class EventClientController : IDynamicApiController { _eventRepository = eventRepository; _projectRepository = projectRepository; + _eventProjectRepository = eventProjectRepository; _groupRepository = groupRepository; _registrationRepository = registrationRepository; _registrationItemRepository = registrationItemRepository; @@ -164,18 +167,22 @@ public class EventClientController : IDynamicApiController if (evt == null) throw Oops.Bah("赛事不存在"); // 获取项目及组别 - var projects = await _projectRepository.AsQueryable() + var eventProjects = await _eventProjectRepository.AsQueryable() + .Where(ep => ep.EventId == id) + .ToListAsync(); + + var projectIds = eventProjects.Select(ep => ep.ProjectId).Distinct().ToList(); + + var baseProjects = await _projectRepository.AsQueryable() .LeftJoin((p, t) => p.ProjectTypeId == t.Id) - .Where(p => p.EventId == id && !p.DeleteMark && p.Status == AvailabilityStatus.Enable) + .Where(p => projectIds.Contains(p.Id) && !p.DeleteMark && p.Status == AvailabilityStatus.Enable) .Select((p, t) => new { p.Id, p.Name, - p.Fee, TypeName = t.Name }) .ToListAsync(); - var projectIds = projects.Select(p => p.Id).ToList(); var groups = await _groupRepository.AsQueryable() .Where(g => projectIds.Contains(g.ProjectId) && !g.DeleteMark) .ToListAsync(); @@ -222,20 +229,24 @@ public class EventClientController : IDynamicApiController LogoUrl = u.LogoUrl }).ToList(), - Projects = projects.Select(p => new EventProjectDto + Projects = eventProjects.Select(ep => { - Id = p.Id, - Name = p.Name, - TypeName = p.TypeName, - Fee = p.Fee, - Groups = groups.Where(g => g.ProjectId == p.Id).Select(g => new EventProjectGroupDto + var bp = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId); + return new EventProjectDto { - Id = g.Id, - GroupName = g.GroupName, - GenderRequirement = g.Gender == Gender.Male ? "男" : (g.Gender == Gender.Female ? "女" : "不限"), - AgeRequirement = $"{g.BirthDateStart:yyyy-MM-dd} 至 {g.BirthDateEnd:yyyy-MM-dd}" - }).ToList() - }).ToList() + Id = ep.Id, + Name = bp?.Name ?? "", + TypeName = bp?.TypeName ?? "", + Fee = ep.Price / 100m, + Groups = groups.Where(g => g.ProjectId == ep.ProjectId).Select(g => new EventProjectGroupDto + { + Id = g.Id, + GroupName = g.GroupName, + GenderRequirement = g.Gender == Gender.Male ? "男" : (g.Gender == Gender.Female ? "女" : "不限"), + AgeRequirement = $"{g.BirthDateStart:yyyy-MM-dd} 至 {g.BirthDateEnd:yyyy-MM-dd}" + }).ToList() + }; + }).Where(x => !string.IsNullOrEmpty(x.Name)).ToList() }; return detail; @@ -306,8 +317,11 @@ public class EventClientController : IDynamicApiController foreach (var item in input.Items) { - var project = await _projectRepository.GetFirstAsync(p => p.Id == item.ProjectId); - if (project == null) throw Oops.Bah($"项目不存在: {item.ProjectId}"); + var eventProject = await _eventProjectRepository.GetFirstAsync(p => p.Id == item.ProjectId); + if (eventProject == null) throw Oops.Bah($"项目不存在: {item.ProjectId}"); + + var project = await _projectRepository.GetFirstAsync(p => p.Id == eventProject.ProjectId); + if (project == null) throw Oops.Bah($"关联基础项目不存在: {item.ProjectId}"); // 校验组别 // 略,假设前端传参正确,或需根据 GroupId 查 @@ -323,9 +337,9 @@ public class EventClientController : IDynamicApiController ProjectId = item.ProjectId, ProjectName = project.Name, GroupName = item.GroupName, - UnitPrice = project.Fee, + UnitPrice = eventProject.Price / 100m, PersonCount = item.Persons.Count, - TotalPrice = project.Fee * item.Persons.Count, + TotalPrice = (eventProject.Price / 100m) * item.Persons.Count, CreatedUserId = userId, CreatedTime = DateTime.Now }; diff --git a/YD_Event.Application/Event/Dtos/EventDtos.cs b/YD_Event.Application/Event/Dtos/EventDtos.cs index 50016b5..7418884 100644 --- a/YD_Event.Application/Event/Dtos/EventDtos.cs +++ b/YD_Event.Application/Event/Dtos/EventDtos.cs @@ -300,40 +300,45 @@ public class AddEventInput public class EventProjectInput { /// - /// 项目ID (更新时使用) + /// 赛事项目ID (更新时使用) /// public long? Id { get; set; } /// - /// 项目名称 + /// 项目ID /// - [Required(ErrorMessage = "项目名称不能为空")] - public string Name { get; set; } + [Required(ErrorMessage = "项目ID不能为空")] + public long ProjectId { get; set; } + + /// + /// 项目名称 (显示用) + /// + public string? Name { get; set; } /// - /// 项目类型ID + /// 项目类型ID (显示用) /// - public long ProjectTypeId { get; set; } + public long? ProjectTypeId { get; set; } /// /// 项目类型名称 (显示用) /// public string? ProjectTypeName { get; set; } - - /// - /// 完成人数 - /// - public int CompletionCount { get; set; } /// - /// 比赛时长(分) + /// 性别要求 /// - public int DurationMinutes { get; set; } + public Gender GenderType { get; set; } /// - /// 比赛时长(秒) + /// 比赛时长(分) (显示用) /// - public int DurationSeconds { get; set; } + public int? DurationMinutes { get; set; } + + /// + /// 比赛时长(秒) (显示用) + /// + public int? DurationSeconds { get; set; } /// /// 项目费用(Step 4) @@ -348,7 +353,44 @@ public class EventProjectInput /// /// 组别列表 /// - public List Groups { get; set; } + public List Groups { get; set; } +} + +/// +/// 赛事项目组别输入 +/// +public class EventProjectGroupInput +{ + /// + /// 赛事项目组别ID (更新时使用) + /// + public long? Id { get; set; } + + /// + /// 关联的项目组别ID + /// + [Required(ErrorMessage = "项目组别ID不能为空")] + public long ProjectGroupId { get; set; } + + /// + /// 组别名称 (显示用) + /// + public string? GroupName { get; set; } + + /// + /// 出生日期起始日期 (显示用) + /// + public DateTime? BirthDateStart { get; set; } + + /// + /// 出生日期结束日期 (显示用) + /// + public DateTime? BirthDateEnd { get; set; } + + /// + /// 性别要求 (显示用) + /// + public Gender? Gender { get; set; } } /// diff --git a/YD_Event.Application/Event/Dtos/EventStepDtos.cs b/YD_Event.Application/Event/Dtos/EventStepDtos.cs index c8ef3e0..e39c065 100644 --- a/YD_Event.Application/Event/Dtos/EventStepDtos.cs +++ b/YD_Event.Application/Event/Dtos/EventStepDtos.cs @@ -170,6 +170,30 @@ public class SaveFeeConfigInput /// 费用配置 /// public EventFeeConfigInput FeeConfig { get; set; } + + /// + /// 项目单价列表(当收费模式为“单价(项目)”时填写) + /// + public List? ProjectFees { get; set; } +} + +/// +/// 项目费用输入 +/// +public class ProjectFeeInput +{ + /// + /// 项目ID + /// + [Required(ErrorMessage = "项目ID不能为空")] + public long ProjectId { get; set; } + + /// + /// 项目单价 + /// + [Required(ErrorMessage = "项目单价不能为空")] + [Range(0.01, (double)decimal.MaxValue, ErrorMessage = "项目单价必须大于0")] + public decimal Fee { get; set; } } /// diff --git a/YD_Event.Application/Event/EventArrangementService.cs b/YD_Event.Application/Event/EventArrangementService.cs index 1a47539..2cf62ef 100644 --- a/YD_Event.Application/Event/EventArrangementService.cs +++ b/YD_Event.Application/Event/EventArrangementService.cs @@ -10,6 +10,7 @@ namespace YD_Event.Application.Event; /// public class EventArrangementService : BaseService, ITransient { + private readonly ISqlSugarRepository _eventProjectRepository; private readonly ISqlSugarRepository _projectRepository; private readonly ISqlSugarRepository _groupRepository; private readonly ISqlSugarRepository _personRepository; @@ -17,12 +18,14 @@ public class EventArrangementService : BaseService _projectTypeRepository; public EventArrangementService( + ISqlSugarRepository eventProjectRepository, ISqlSugarRepository projectRepository, ISqlSugarRepository groupRepository, ISqlSugarRepository personRepository, ISqlSugarRepository registrationRepository, ISqlSugarRepository projectTypeRepository) : base(projectRepository) { + _eventProjectRepository = eventProjectRepository; _projectRepository = projectRepository; _groupRepository = groupRepository; _personRepository = personRepository; @@ -36,10 +39,12 @@ public class EventArrangementService : BaseService> GetList(long eventId) { - var projects = await _projectRepository.GetListAsync(x => x.EventId == eventId); + var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId); + var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); + var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id)); projects = projects.OrderBy(x => x.SortIndex).ToList(); - var groups = await _groupRepository.GetListAsync(x => projects.Select(p => p.Id).Contains(x.ProjectId)); + var groups = await _groupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); // 获取项目类型信息 var typeIds = projects.Select(x => x.ProjectTypeId).Distinct().ToList(); @@ -54,13 +59,16 @@ public class EventArrangementService : BaseService(); - foreach (var proj in projects) + foreach (var eventProj in eventProjects) { + var proj = projects.FirstOrDefault(x => x.Id == eventProj.ProjectId); + if (proj == null) continue; + var type = types.FirstOrDefault(x => x.Id == proj.ProjectTypeId); var dto = new ArrangementProjectListOutput { - ProjectId = proj.Id, + ProjectId = eventProj.Id, ProjectName = proj.Name, ProjectType = type?.Name ?? "未知类型", DurationSeconds = proj.DurationSeconds, @@ -72,7 +80,7 @@ public class EventArrangementService : BaseService g.ProjectId == proj.Id).ToList(); foreach (var group in projGroups) { - var countObj = counts.FirstOrDefault(c => c.ProjectId == proj.Id && c.GroupName == group.GroupName); + var countObj = counts.FirstOrDefault(c => c.ProjectId == eventProj.Id && c.GroupName == group.GroupName); dto.Groups.Add(new ArrangementGroupDto { GroupName = group.GroupName, @@ -98,15 +106,22 @@ public class EventArrangementService : BaseService x.ProjectId).ToList(); + var eventProjects = await _eventProjectRepository.GetListAsync(x => eventProjectIds.Contains(x.Id)); + foreach (var setting in input.Settings) { - await _projectRepository.UpdateAsync(x => new YD_Event.Core.Entities.Project - { - VenueCount = setting.VenueCount, - SortIndex = setting.SortIndex, - DurationSeconds = setting.DurationSeconds, - DurationMinutes = setting.DurationSeconds / 60 - }, x => x.Id == setting.ProjectId); + var ep = eventProjects.FirstOrDefault(x => x.Id == setting.ProjectId); + if (ep != null) + { + await _projectRepository.UpdateAsync(x => new YD_Event.Core.Entities.Project + { + VenueCount = setting.VenueCount, + SortIndex = setting.SortIndex, + DurationSeconds = setting.DurationSeconds, + DurationMinutes = setting.DurationSeconds / 60 + }, x => x.Id == ep.ProjectId); + } } } @@ -117,20 +132,25 @@ public class EventArrangementService : BaseService> Preview(long eventId) { // 1. 获取项目(按顺序) - var projects = await _projectRepository.GetListAsync(x => x.EventId == eventId); + var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId); + var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); + var projects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id)); projects = projects.OrderBy(x => x.SortIndex).ToList(); var result = new List(); // 2. 生成排期 - foreach (var proj in projects) + foreach (var eventProj in eventProjects) { + var proj = projects.FirstOrDefault(x => x.Id == eventProj.ProjectId); + if (proj == null) continue; + int venueCount = proj.VenueCount > 0 ? proj.VenueCount : 1; // 获取参赛人员(排序:组别 -> ID) var persons = await _personRepository.AsQueryable() .InnerJoin((p, r) => p.RegistrationId == r.Id) - .Where((p, r) => p.ProjectId == proj.Id && (r.PaymentStatus == 1 || r.RegistrationStatus == 2)) + .Where((p, r) => p.ProjectId == eventProj.Id && (r.PaymentStatus == 1 || r.RegistrationStatus == 2)) .OrderBy(p => p.GroupName) .OrderBy(p => p.Id) .Select((p, r) => new { diff --git a/YD_Event.Application/Event/EventService.cs b/YD_Event.Application/Event/EventService.cs index 9170c0e..bb51671 100644 --- a/YD_Event.Application/Event/EventService.cs +++ b/YD_Event.Application/Event/EventService.cs @@ -19,6 +19,8 @@ public class EventService : BaseService, ITransient private readonly ISqlSugarRepository _cityRepository; private readonly ISqlSugarRepository _districtRepository; private readonly ISqlSugarRepository _projectRepository; + private readonly ISqlSugarRepository _eventProjectRepository; + private readonly ISqlSugarRepository _eventProjectGroupRepository; private readonly ISqlSugarRepository _projectGroupRepository; private readonly ISqlSugarRepository _projectTypeRepository; private readonly ISqlSugarRepository _registrationLimitRepository; @@ -37,6 +39,8 @@ public class EventService : BaseService, ITransient ISqlSugarRepository cityRepository, ISqlSugarRepository districtRepository, ISqlSugarRepository projectRepository, + ISqlSugarRepository eventProjectRepository, + ISqlSugarRepository eventProjectGroupRepository, ISqlSugarRepository projectGroupRepository, ISqlSugarRepository projectTypeRepository, ISqlSugarRepository registrationLimitRepository, @@ -54,6 +58,8 @@ public class EventService : BaseService, ITransient _cityRepository = cityRepository; _districtRepository = districtRepository; _projectRepository = projectRepository; + _eventProjectRepository = eventProjectRepository; + _eventProjectGroupRepository = eventProjectGroupRepository; _projectGroupRepository = projectGroupRepository; _projectTypeRepository = projectTypeRepository; _registrationLimitRepository = registrationLimitRepository; @@ -171,28 +177,62 @@ public class EventService : BaseService, ITransient } // 4. 获取项目列表 (Step 3) - var projects = await _projectRepository.GetListAsync(x => x.EventId == id); - if (projects.Any()) + var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == id); + if (eventProjects.Any()) { - var projectIds = projects.Select(x => x.Id).ToList(); - var groups = await _projectGroupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); - var projectLimits = await _projectLimitRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); + var eventProjectIds = eventProjects.Select(x => x.Id).ToList(); + var eventProjectGroups = await _eventProjectGroupRepository.GetListAsync(x => eventProjectIds.Contains(x.EventProjectId)); + var projectLimits = await _projectLimitRepository.GetListAsync(x => eventProjectIds.Contains(x.ProjectId)); + // 获取关联的原始项目信息 + var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); + var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id)); + var baseProjectGroups = await _projectGroupRepository.GetListAsync(x => projectIds.Contains(x.ProjectId)); + // 获取项目类型信息 - var typeIds = projects.Select(x => x.ProjectTypeId).Distinct().ToList(); + var typeIds = baseProjects.Select(x => x.ProjectTypeId).Distinct().ToList(); var types = await _projectTypeRepository.GetListAsync(x => typeIds.Contains(x.Id)); - output.Projects = projects.Select(p => + output.Projects = eventProjects.Select(ep => { - var dto = p.Adapt(); - var type = types.FirstOrDefault(x => x.Id == p.ProjectTypeId); - if (type != null) + var baseProj = baseProjects.FirstOrDefault(p => p.Id == ep.ProjectId); + var dto = new EventProjectInput { - dto.ProjectTypeName = type.Name; + Id = ep.Id, + ProjectId = ep.ProjectId, + GenderType = ep.GenderType, + Fee = ep.Price / 100m, + Name = baseProj?.Name, + ProjectTypeId = baseProj?.ProjectTypeId, + DurationMinutes = baseProj?.DurationMinutes, + DurationSeconds = baseProj?.DurationSeconds + }; + + if (baseProj != null) + { + var type = types.FirstOrDefault(x => x.Id == baseProj.ProjectTypeId); + if (type != null) + { + dto.ProjectTypeName = type.Name; + } } - dto.Groups = groups.Where(g => g.ProjectId == p.Id).Select(g => g.Adapt()).ToList(); - var limit = projectLimits.FirstOrDefault(l => l.ProjectId == p.Id); + var epGroups = eventProjectGroups.Where(g => g.EventProjectId == ep.Id).ToList(); + dto.Groups = epGroups.Select(eg => + { + var baseGroup = baseProjectGroups.FirstOrDefault(bg => bg.Id == eg.ProjectGroupId); + return new EventProjectGroupInput + { + Id = eg.Id, + ProjectGroupId = eg.ProjectGroupId, + GroupName = baseGroup?.GroupName, + BirthDateStart = baseGroup?.BirthDateStart, + BirthDateEnd = baseGroup?.BirthDateEnd, + Gender = baseGroup?.Gender + }; + }).ToList(); + + var limit = projectLimits.FirstOrDefault(l => l.ProjectId == ep.Id); if (limit != null) dto.LimitCount = limit.LimitCount; return dto; }).ToList(); @@ -266,29 +306,43 @@ public class EventService : BaseService, ITransient // 2. 自定义字段 (JSON) - Already in eventInfo + var projectFees = new List(); + // 3. 保存项目信息 (Step 3) if (input.Projects != null && input.Projects.Any()) { foreach (var projInput in input.Projects) { - var project = projInput.Adapt(); - project.Id = _idGenerator.NewLong(); - project.EventId = eventInfo.Id; - project.Status = AvailabilityStatus.Enable; - project.CreatedTime = DateTime.Now; - await _projectRepository.InsertAsync(project); + var eventProject = new EventProject + { + Id = _idGenerator.NewLong(), + EventId = eventInfo.Id, + ProjectId = projInput.ProjectId, + GenderType = projInput.GenderType, + Price = (int)(projInput.Fee * 100), + CreatedTime = DateTime.Now + }; + await _eventProjectRepository.InsertAsync(eventProject); + + projectFees.Add(new ProjectFeeInput { ProjectId = eventProject.Id, Fee = projInput.Fee }); // 保存项目分组 if (projInput.Groups != null && projInput.Groups.Any()) { var groups = projInput.Groups.Select(g => { - var group = g.Adapt(); - group.Id = _idGenerator.NewLong(); - group.ProjectId = project.Id; + var group = new EventProjectGroup + { + Id = _idGenerator.NewLong(), + EventId = eventInfo.Id, + EventProjectId = eventProject.Id, + ProjectId = projInput.ProjectId, + ProjectGroupId = g.ProjectGroupId, + CreatedTime = DateTime.Now + }; return group; }).ToList(); - await _projectGroupRepository.InsertRangeAsync(groups); + await _eventProjectGroupRepository.InsertRangeAsync(groups); } // 保存项目报名限制 (Step 3 - Project Limit) @@ -298,8 +352,9 @@ public class EventService : BaseService, ITransient { Id = _idGenerator.NewLong(), EventId = eventInfo.Id, - ProjectId = project.Id, - LimitCount = projInput.LimitCount.Value + ProjectId = eventProject.Id, + LimitCount = projInput.LimitCount.Value, + CreatedTime = DateTime.Now }; await _projectLimitRepository.InsertAsync(projLimit); } @@ -318,10 +373,18 @@ public class EventService : BaseService, ITransient // 5. 保存费用配置 (Step 4) if (input.FeeConfig != null) { - var feeConfig = input.FeeConfig.Adapt(); - feeConfig.Id = _idGenerator.NewLong(); - feeConfig.EventId = eventInfo.Id; - await _feeConfigRepository.InsertAsync(feeConfig); + // 满足 SaveFeeConfig 的前置状态要求 + eventInfo.ProcessStatus = ProcessStatus.ProjectConfigCompleted; + await _repository.UpdateAsync(eventInfo); + + var feeConfigInput = new SaveFeeConfigInput + { + EventId = eventInfo.Id, + FeeConfig = input.FeeConfig, + ProjectFees = projectFees + }; + + await SaveFeeConfig(feeConfigInput); } } @@ -366,38 +429,52 @@ public class EventService : BaseService, ITransient // 2. 更新自定义字段 (JSON) - Updated in eventInfo + var projectFees = new List(); + // 3. 更新项目信息 - var oldProjects = await _projectRepository.GetListAsync(x => x.EventId == input.Id); - var oldProjectIds = oldProjects.Select(x => x.Id).ToList(); + var oldEventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.Id); + var oldEventProjectIds = oldEventProjects.Select(x => x.Id).ToList(); - if (oldProjectIds.Any()) + if (oldEventProjectIds.Any()) { - await _projectGroupRepository.DeleteAsync(x => oldProjectIds.Contains(x.ProjectId)); - await _projectLimitRepository.DeleteAsync(x => oldProjectIds.Contains(x.ProjectId)); - await _projectRepository.DeleteAsync(x => x.EventId == input.Id); + await _eventProjectGroupRepository.DeleteAsync(x => oldEventProjectIds.Contains(x.EventProjectId)); + await _projectLimitRepository.DeleteAsync(x => oldEventProjectIds.Contains(x.ProjectId)); + await _eventProjectRepository.DeleteAsync(x => x.EventId == input.Id); } if (input.Projects != null && input.Projects.Any()) { foreach (var projInput in input.Projects) { - var project = projInput.Adapt(); - project.Id = _idGenerator.NewLong(); - project.EventId = eventInfo.Id; - project.Status = AvailabilityStatus.Enable; - project.CreatedTime = DateTime.Now; - await _projectRepository.InsertAsync(project); + var eventProject = new EventProject + { + Id = _idGenerator.NewLong(), + EventId = eventInfo.Id, + ProjectId = projInput.ProjectId, + GenderType = projInput.GenderType, + Price = (int)(projInput.Fee * 100), + CreatedTime = DateTime.Now + }; + await _eventProjectRepository.InsertAsync(eventProject); + + projectFees.Add(new ProjectFeeInput { ProjectId = eventProject.Id, Fee = projInput.Fee }); if (projInput.Groups != null && projInput.Groups.Any()) { var groups = projInput.Groups.Select(g => { - var group = g.Adapt(); - group.Id = _idGenerator.NewLong(); - group.ProjectId = project.Id; + var group = new EventProjectGroup + { + Id = _idGenerator.NewLong(), + EventId = eventInfo.Id, + EventProjectId = eventProject.Id, + ProjectId = projInput.ProjectId, + ProjectGroupId = g.ProjectGroupId, + CreatedTime = DateTime.Now + }; return group; }).ToList(); - await _projectGroupRepository.InsertRangeAsync(groups); + await _eventProjectGroupRepository.InsertRangeAsync(groups); } if (projInput.LimitCount.HasValue) @@ -406,8 +483,9 @@ public class EventService : BaseService, ITransient { Id = _idGenerator.NewLong(), EventId = eventInfo.Id, - ProjectId = project.Id, - LimitCount = projInput.LimitCount.Value + ProjectId = eventProject.Id, + LimitCount = projInput.LimitCount.Value, + CreatedTime = DateTime.Now }; await _projectLimitRepository.InsertAsync(projLimit); } @@ -425,13 +503,23 @@ public class EventService : BaseService, ITransient } // 5. 更新费用配置 - await _feeConfigRepository.DeleteAsync(x => x.EventId == input.Id); if (input.FeeConfig != null) { - var feeConfig = input.FeeConfig.Adapt(); - feeConfig.Id = _idGenerator.NewLong(); - feeConfig.EventId = eventInfo.Id; - await _feeConfigRepository.InsertAsync(feeConfig); + // 满足 SaveFeeConfig 的前置状态要求 + if (eventInfo.ProcessStatus < ProcessStatus.ProjectConfigCompleted) + { + eventInfo.ProcessStatus = ProcessStatus.ProjectConfigCompleted; + await _repository.UpdateAsync(eventInfo); + } + + var feeConfigInput = new SaveFeeConfigInput + { + EventId = eventInfo.Id, + FeeConfig = input.FeeConfig, + ProjectFees = projectFees + }; + + await SaveFeeConfig(feeConfigInput); } } @@ -629,37 +717,47 @@ public class EventService : BaseService, ITransient } // 1. 更新项目信息 (全量替换) - var oldProjects = await _projectRepository.GetListAsync(x => x.EventId == input.EventId); - var oldProjectIds = oldProjects.Select(x => x.Id).ToList(); + var oldEventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId); + var oldEventProjectIds = oldEventProjects.Select(x => x.Id).ToList(); - if (oldProjectIds.Any()) + if (oldEventProjectIds.Any()) { - await _projectGroupRepository.DeleteAsync(x => oldProjectIds.Contains(x.ProjectId)); - await _projectLimitRepository.DeleteAsync(x => oldProjectIds.Contains(x.ProjectId)); - await _projectRepository.DeleteAsync(x => x.EventId == input.EventId); + await _eventProjectGroupRepository.DeleteAsync(x => oldEventProjectIds.Contains(x.EventProjectId)); + await _projectLimitRepository.DeleteAsync(x => oldEventProjectIds.Contains(x.ProjectId)); + await _eventProjectRepository.DeleteAsync(x => x.EventId == input.EventId); } if (input.Projects != null && input.Projects.Any()) { foreach (var projInput in input.Projects) { - var project = projInput.Adapt(); - project.Id = _idGenerator.NewLong(); - project.EventId = input.EventId; - project.Status = AvailabilityStatus.Enable; - project.CreatedTime = DateTime.Now; - await _projectRepository.InsertAsync(project); + var eventProject = new EventProject + { + Id = _idGenerator.NewLong(), + EventId = input.EventId, + ProjectId = projInput.ProjectId, + GenderType = projInput.GenderType, + Price = (int)(projInput.Fee * 100), + CreatedTime = DateTime.Now + }; + await _eventProjectRepository.InsertAsync(eventProject); if (projInput.Groups != null && projInput.Groups.Any()) { var groups = projInput.Groups.Select(g => { - var group = g.Adapt(); - group.Id = _idGenerator.NewLong(); - group.ProjectId = project.Id; + var group = new EventProjectGroup + { + Id = _idGenerator.NewLong(), + EventId = input.EventId, + EventProjectId = eventProject.Id, + ProjectId = projInput.ProjectId, + ProjectGroupId = g.ProjectGroupId, + CreatedTime = DateTime.Now + }; return group; }).ToList(); - await _projectGroupRepository.InsertRangeAsync(groups); + await _eventProjectGroupRepository.InsertRangeAsync(groups); } if (projInput.LimitCount.HasValue) @@ -668,8 +766,9 @@ public class EventService : BaseService, ITransient { Id = _idGenerator.NewLong(), EventId = input.EventId, - ProjectId = project.Id, - LimitCount = projInput.LimitCount.Value + ProjectId = eventProject.Id, + LimitCount = projInput.LimitCount.Value, + CreatedTime = DateTime.Now }; await _projectLimitRepository.InsertAsync(projLimit); } @@ -711,6 +810,40 @@ public class EventService : BaseService, ITransient throw new InvalidOperationException("请先完成项目配置"); } + // 验证单价(项目)模式下的项目单价 + if (input.FeeConfig != null && input.FeeConfig.ChargeMode == ChargeMode.UnitPriceProject) + { + if (input.ProjectFees == null || !input.ProjectFees.Any()) + { + throw new InvalidOperationException("收费模式为“单价(项目)”时,必须提供项目单价信息"); + } + + var projectIds = input.ProjectFees.Select(x => x.ProjectId).ToList(); + var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == input.EventId && projectIds.Contains(x.Id)); + + foreach (var feeInput in input.ProjectFees) + { + if (feeInput.Fee <= 0) + { + throw new InvalidOperationException("项目单价必须大于0"); + } + + // 格式验证:最多保留两位小数 + if (Math.Round(feeInput.Fee, 2) != feeInput.Fee) + { + throw new InvalidOperationException("项目单价格式不正确,最多保留两位小数"); + } + + var proj = eventProjects.FirstOrDefault(p => p.Id == feeInput.ProjectId); + if (proj != null) + { + proj.Price = (int)(feeInput.Fee * 100); + proj.UpdatedTime = DateTime.Now; + await _eventProjectRepository.UpdateAsync(proj); + } + } + } + // 更新费用配置 await _feeConfigRepository.DeleteAsync(x => x.EventId == input.EventId); if (input.FeeConfig != null) @@ -719,6 +852,9 @@ public class EventService : BaseService, ITransient feeConfig.Id = _idGenerator.NewLong(); feeConfig.EventId = input.EventId; await _feeConfigRepository.InsertAsync(feeConfig); + + // 更新赛事的 ChargeMode + eventInfo.ChargeMode = feeConfig.ChargeMode; } // 生成二维码URL diff --git a/YD_Event.Application/Event/UserCompetitionService.cs b/YD_Event.Application/Event/UserCompetitionService.cs index 85475c3..73f0af3 100644 --- a/YD_Event.Application/Event/UserCompetitionService.cs +++ b/YD_Event.Application/Event/UserCompetitionService.cs @@ -20,7 +20,10 @@ public class UserCompetitionService : IDynamicApiController, ITransient { private readonly ISqlSugarRepository _eventRepository; private readonly ISqlSugarRepository _unitRepository; + private readonly ISqlSugarRepository _eventProjectRepository; + private readonly ISqlSugarRepository _eventProjectGroupRepository; private readonly ISqlSugarRepository _projectRepository; + private readonly ISqlSugarRepository _projectGroupRepository; private readonly ISqlSugarRepository _registrationRepository; private readonly ISqlSugarRepository _registrationItemRepository; private readonly ISqlSugarRepository _registrationPersonRepository; @@ -37,7 +40,10 @@ public class UserCompetitionService : IDynamicApiController, ITransient public UserCompetitionService( ISqlSugarRepository eventRepository, ISqlSugarRepository unitRepository, + ISqlSugarRepository eventProjectRepository, + ISqlSugarRepository eventProjectGroupRepository, ISqlSugarRepository projectRepository, + ISqlSugarRepository projectGroupRepository, ISqlSugarRepository registrationRepository, ISqlSugarRepository registrationItemRepository, ISqlSugarRepository registrationPersonRepository, @@ -53,7 +59,10 @@ public class UserCompetitionService : IDynamicApiController, ITransient { _eventRepository = eventRepository; _unitRepository = unitRepository; + _eventProjectRepository = eventProjectRepository; + _eventProjectGroupRepository = eventProjectGroupRepository; _projectRepository = projectRepository; + _projectGroupRepository = projectGroupRepository; _registrationRepository = registrationRepository; _registrationItemRepository = registrationItemRepository; _registrationPersonRepository = registrationPersonRepository; @@ -146,14 +155,21 @@ public class UserCompetitionService : IDynamicApiController, ITransient }; } - var projects = await _projectRepository.GetListAsync(x => x.EventId == id && x.Status == AvailabilityStatus.Enable); - output.Projects = projects.Select(p => new UserEventProjectListOutput + var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == id); + var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); + var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable); + + output.Projects = eventProjects.Select(ep => { - Id = p.Id, - Name = p.Name, - Fee = p.Fee, - CompletionCount = p.CompletionCount - }).ToList(); + var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId); + return new UserEventProjectListOutput + { + Id = ep.Id, + Name = p?.Name ?? "", + Fee = ep.Price / 100m, + CompletionCount = p?.CompletionCount ?? 0 + }; + }).Where(x => !string.IsNullOrEmpty(x.Name)).ToList(); var userId = _authManager.UserId; var registration = await _registrationRepository.GetFirstAsync(x => x.EventId == id && x.CreatedUserId == userId && !x.DeleteMark); @@ -178,16 +194,22 @@ public class UserCompetitionService : IDynamicApiController, ITransient [HttpGet("projects/{eventId}")] public async Task> GetProjects([FromRoute] long eventId) { - var projects = await _projectRepository.GetListAsync(x => x.EventId == eventId && x.Status == AvailabilityStatus.Enable); - - return projects.Select(p => new UserEventProjectListOutput + var eventProjects = await _eventProjectRepository.GetListAsync(x => x.EventId == eventId); + var projectIds = eventProjects.Select(x => x.ProjectId).Distinct().ToList(); + var baseProjects = await _projectRepository.GetListAsync(x => projectIds.Contains(x.Id) && x.Status == AvailabilityStatus.Enable); + + return eventProjects.Select(ep => { - Id = p.Id, - Name = p.Name, - Fee = p.Fee, - TeamSize = 1, // 默认为1 - CompletionCount = p.CompletionCount - }).ToList(); + var p = baseProjects.FirstOrDefault(b => b.Id == ep.ProjectId); + return new UserEventProjectListOutput + { + Id = ep.Id, + Name = p?.Name ?? "", + Fee = ep.Price / 100m, + TeamSize = 1, // 默认为1 + CompletionCount = p?.CompletionCount ?? 0 + }; + }).Where(x => !string.IsNullOrEmpty(x.Name)).ToList(); } /// @@ -269,23 +291,26 @@ public class UserCompetitionService : IDynamicApiController, ITransient await _registrationItemRepository.DeleteAsync(x => x.RegistrationId == registration.Id); await _registrationPersonRepository.DeleteAsync(x => x.RegistrationId == registration.Id); - var projects = await _projectRepository.GetListAsync(x => input.ProjectIds.Contains(x.Id)); + var eventProjects = await _eventProjectRepository.GetListAsync(x => input.ProjectIds.Contains(x.Id)); + var baseProjectIds = eventProjects.Select(x => x.ProjectId).ToList(); + var baseProjects = await _projectRepository.GetListAsync(x => baseProjectIds.Contains(x.Id)); var members = await _userRepository.GetListAsync(x => input.MemberIds.Contains(x.Id)); decimal totalAmount = 0; int totalPersonCount = 0; - foreach (var project in projects) + foreach (var eventProject in eventProjects) { + var baseProject = baseProjects.FirstOrDefault(x => x.Id == eventProject.ProjectId); var item = new EventRegistrationItem { Id = _idGenerator.NewLong(), RegistrationId = registration.Id, - ProjectId = project.Id, - ProjectName = project.Name, - UnitPrice = project.Fee, + ProjectId = eventProject.Id, + ProjectName = baseProject?.Name ?? "", + UnitPrice = eventProject.Price / 100m, PersonCount = members.Count(), - TotalPrice = project.Fee * members.Count(), + TotalPrice = (eventProject.Price / 100m) * members.Count(), CreatedUserId = userId, CreatedTime = DateTime.Now }; @@ -301,7 +326,7 @@ public class UserCompetitionService : IDynamicApiController, ITransient Id = _idGenerator.NewLong(), RegistrationId = registration.Id, RegistrationItemId = item.Id, - ProjectId = project.Id, + ProjectId = eventProject.Id, Name = member.Name, Gender = member.Gender, IdCard = member.IdCardNumber, diff --git a/YD_Event.Application/Project/Dtos/ProjectDtos.cs b/YD_Event.Application/Project/Dtos/ProjectDtos.cs index fe597e2..f62b1c6 100644 --- a/YD_Event.Application/Project/Dtos/ProjectDtos.cs +++ b/YD_Event.Application/Project/Dtos/ProjectDtos.cs @@ -10,11 +10,6 @@ namespace YD_Event.Application.Project.Dtos; /// public class ProjectQueryInput : Pagination { - /// - /// 赛事ID - /// - public long? EventId { get; set; } - /// /// 项目名称 (模糊查询) /// @@ -28,12 +23,6 @@ public class ProjectQueryInput : Pagination public class AddProjectInput { - /// - /// 关联的赛事ID - /// - [Required(ErrorMessage = "赛事ID不能为空")] - public long EventId { get; set; } - /// /// 项目名称 /// @@ -125,11 +114,6 @@ public class ProjectDto /// public long Id { get; set; } - /// - /// 关联的赛事ID - /// - public long EventId { get; set; } - /// /// 项目名称 /// diff --git a/YD_Event.Application/Project/ProjectService.cs b/YD_Event.Application/Project/ProjectService.cs index 8537bba..1f635c3 100644 --- a/YD_Event.Application/Project/ProjectService.cs +++ b/YD_Event.Application/Project/ProjectService.cs @@ -35,7 +35,6 @@ public class ProjectService : BaseService, ITran { var query = _repository.AsQueryable() .LeftJoin((p, pt) => p.ProjectTypeId == pt.Id) - .WhereIF(input.EventId.HasValue, (p, pt) => p.EventId == input.EventId) .WhereIF(!string.IsNullOrWhiteSpace(input.ProjectName), (p, pt) => p.Name.Contains(input.ProjectName)) .WhereIF(input.ProjectTypeId.HasValue, (p, pt) => p.ProjectTypeId == input.ProjectTypeId) .OrderByDescending((p, pt) => p.Id); @@ -43,7 +42,6 @@ public class ProjectService : BaseService, ITran var result = await query.Select((p, pt) => new ProjectDto { Id = p.Id, - EventId = p.EventId, Name = p.Name, ProjectTypeId = p.ProjectTypeId, ProjectTypeName = pt.Name, @@ -72,9 +70,9 @@ public class ProjectService : BaseService, ITran [UnitOfWork] public async Task Add(AddProjectInput input) { - // 检查项目名称在同一赛事中是否重复 - var isExist = await _repository.IsAnyAsync(x => x.EventId == input.EventId && x.Name == input.Name); - if (isExist) throw new InvalidOperationException("该项目名称在当前赛事中已存在"); + // 检查项目名称是否重复 + var isExist = await _repository.IsAnyAsync(x => x.Name == input.Name); + if (isExist) throw new InvalidOperationException("该项目名称已存在"); var entity = input.Adapt(); entity.Id = _idGenerator.NewLong(); @@ -109,8 +107,8 @@ public class ProjectService : BaseService, ITran // 如果修改了名称,检查是否重复 if (entity.Name != input.Name) { - var isExist = await _repository.IsAnyAsync(x => x.EventId == input.EventId && x.Name == input.Name && x.Id != input.Id); - if (isExist) throw new InvalidOperationException("该项目名称在当前赛事中已存在"); + var isExist = await _repository.IsAnyAsync(x => x.Name == input.Name && x.Id != input.Id); + if (isExist) throw new InvalidOperationException("该项目名称已存在"); } input.Adapt(entity); diff --git a/YD_Event.Application/YD_Event.Application.xml b/YD_Event.Application/YD_Event.Application.xml index ce30b53..86ef115 100644 --- a/YD_Event.Application/YD_Event.Application.xml +++ b/YD_Event.Application/YD_Event.Application.xml @@ -3540,17 +3540,22 @@ - 项目ID (更新时使用) + 赛事项目ID (更新时使用) + + + + + 项目ID - 项目名称 + 项目名称 (显示用) - 项目类型ID + 项目类型ID (显示用) @@ -3558,19 +3563,19 @@ 项目类型名称 (显示用) - + - 完成人数 + 性别要求 - 比赛时长(分) + 比赛时长(分) (显示用) - 比赛时长(秒) + 比赛时长(秒) (显示用) @@ -3588,6 +3593,41 @@ 组别列表 + + + 赛事项目组别输入 + + + + + 赛事项目组别ID (更新时使用) + + + + + 关联的项目组别ID + + + + + 组别名称 (显示用) + + + + + 出生日期起始日期 (显示用) + + + + + 出生日期结束日期 (显示用) + + + + + 性别要求 (显示用) + + 报名限制输入 @@ -3948,6 +3988,26 @@ 费用配置 + + + 项目单价列表(当收费模式为“单价(项目)”时填写) + + + + + 项目费用输入 + + + + + 项目ID + + + + + 项目单价 + + 赛事进度输出 @@ -7184,11 +7244,6 @@ 项目查询输入 - - - 赛事ID - - 项目名称 (模糊查询) @@ -7199,11 +7254,6 @@ 项目类型ID - - - 关联的赛事ID - - 项目名称 @@ -7284,11 +7334,6 @@ ID - - - 关联的赛事ID - - 项目名称 diff --git a/YD_Event.Application/applicationsettings.json b/YD_Event.Application/applicationsettings.json index 50f2a84..c76ca72 100644 --- a/YD_Event.Application/applicationsettings.json +++ b/YD_Event.Application/applicationsettings.json @@ -149,7 +149,7 @@ "ALiYunSMS": { "Endpoint": "dysmsapi.aliyuncs.com", "Sign": "oVpeH1PpDUnWTcBrUSSYkQNS4Fex8Y", - "Template": "SMS_147140268" + "Template": "SMS_254100608" }, // 阿里云ORC识别 "AliyunOCR": { diff --git a/YD_Event.Core/Entities/EventProject.cs b/YD_Event.Core/Entities/EventProject.cs new file mode 100644 index 0000000..1401d9e --- /dev/null +++ b/YD_Event.Core/Entities/EventProject.cs @@ -0,0 +1,47 @@ +using SqlSugar; +using YD_Event.Core.Enum; + +namespace YD_Event.Core.Entities; + +/// +/// 赛事项目关联表 +/// +public class EventProject : Entity, ICreatedTime, IUpdatedTime +{ + /// + /// 赛事ID + /// + public long EventId { get; set; } + + /// + /// 项目ID + /// + public long ProjectId { get; set; } + + /// + /// 性别要求 (男/女/不限) + /// + public Gender GenderType { get; set; } + + /// + /// 价格(单位:分) + /// + public int Price { get; set; } + + /// + /// 赛事项目组别列表(导航属性) + /// + [SugarColumn(IsIgnore = true)] + public List? Groups { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedTime { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? UpdatedTime { get; set; } +} \ No newline at end of file diff --git a/YD_Event.Core/Entities/EventProjectGroup.cs b/YD_Event.Core/Entities/EventProjectGroup.cs new file mode 100644 index 0000000..067e9de --- /dev/null +++ b/YD_Event.Core/Entities/EventProjectGroup.cs @@ -0,0 +1,55 @@ +using YD_Event.Core.Enum; + +namespace YD_Event.Core.Entities; + +/// +/// 赛事项目组别表 +/// +[SugarTable("EventProjectGroup")] +public class EventProjectGroup : Entity, ISoftDelete, ICreatedUserId, ICreatedTime, IUpdatedUserId, IUpdatedTime +{ + /// + /// 赛事ID + /// + public long EventId { get; set; } + + /// + /// 赛事项目ID + /// + public long EventProjectId { get; set; } + + /// + /// 关联的项目ID + /// + public long ProjectId { get; set; } + + /// + /// 关联的项目组别ID + /// + public long ProjectGroupId { get; set; } + + /// + /// 标记删除 + /// + public bool DeleteMark { get; set; } + + /// + /// 创建人 + /// + public long CreatedUserId { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedTime { get; set; } + + /// + /// 更新人 + /// + public long? UpdatedUserId { get; set; } + + /// + /// 更新时间 + /// + public DateTime? UpdatedTime { get; set; } +} diff --git a/YD_Event.Core/Entities/Project.cs b/YD_Event.Core/Entities/Project.cs index e89807a..cff43c7 100644 --- a/YD_Event.Core/Entities/Project.cs +++ b/YD_Event.Core/Entities/Project.cs @@ -9,11 +9,6 @@ namespace YD_Event.Core.Entities; [SugarTable("Project")] public class Project : Entity, ISoftDelete, ICreatedUserId, ICreatedTime, IUpdatedUserId, IUpdatedTime, IAvailability { - /// - /// 关联的赛事ID - /// - public long EventId { get; set; } - /// /// 项目名称 /// diff --git a/YD_Event.Core/YD_Event.Core.xml b/YD_Event.Core/YD_Event.Core.xml index eb69859..49d5389 100644 --- a/YD_Event.Core/YD_Event.Core.xml +++ b/YD_Event.Core/YD_Event.Core.xml @@ -209,17 +209,6 @@ 高度 - - - 证书文档 - - - - - 生成PDF - - PDF字节数组 - DOCX模板解析器 @@ -805,81 +794,6 @@ 更新时间 - - - 证书元素表 (CertificateElement) - - - - - 模板ID (关联 CertificateTemplate) - - - - - 元素类型: Text, Image, QRCode - - - - - 字段名/标签 (维护用) - - - - - 绑定键 (业务数据字段) - - - - - X坐标 (px) - - - - - Y坐标 (px) - - - - - 宽度 (px) - - - - - 高度 (px) - - - - - 字体 - - - - - 字号 - - - - - 颜色代码 #RRGGBB - - - - - 额外样式 JSON - - - - - 排序 - - - - - 软删除 - - 证书占位符表 (CertificatePlaceholder) @@ -1540,6 +1454,96 @@ 更新时间 + + + 赛事项目关联表 + + + + + 赛事ID + + + + + 项目ID + + + + + 性别要求 (男/女/不限) + + + + + 价格(单位:分) + + + + + 赛事项目组别列表(导航属性) + + + + + 创建时间 + + + + + 更新时间 + + + + + 赛事项目组别表 + + + + + 赛事ID + + + + + 赛事项目ID + + + + + 关联的项目ID + + + + + 关联的项目组别ID + + + + + 标记删除 + + + + + 创建人 + + + + + 创建时间 + + + + + 更新人 + + + + + 更新时间 + + 赛事项目报名限制表 @@ -2751,11 +2755,6 @@ 项目表 - - - 关联的赛事ID - - 项目名称