feat: 添加AI阳光跑数据接口与班级筛选
从 XW_SunshineRunTrain 按学校权限查询运动记录,支持分页、姓名、日期和年级班级 ID 精确筛选。 提供 Excel 导出并附带学校角色的菜单授权脚本。
This commit is contained in:
parent
e1fac41f8c
commit
cd7206ede5
14
VOL.Business/IServices/SunshineRun/ISunshineRunService.cs
Normal file
14
VOL.Business/IServices/SunshineRun/ISunshineRunService.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using VOL.Model;
|
||||
using VOL.Model.SunshineRun;
|
||||
|
||||
namespace VOL.Business.IServices.SunshineRun
|
||||
{
|
||||
public interface ISunshineRunService
|
||||
{
|
||||
Task<PageDataDto<SunshineRunRecordModel>> GetPageList(SunshineRunPageParam param);
|
||||
|
||||
Task<List<SunshineRunRecordModel>> GetExportList(SunshineRunFilterParam param);
|
||||
}
|
||||
}
|
||||
212
VOL.Business/Services/SunshineRun/SunshineRunService.cs
Normal file
212
VOL.Business/Services/SunshineRun/SunshineRunService.cs
Normal file
@ -0,0 +1,212 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using VOL.Business.IServices.SunshineRun;
|
||||
using VOL.Core.EFDbContext;
|
||||
using VOL.Core.Extensions.AutofacManager;
|
||||
using VOL.Core.ManageUser;
|
||||
using VOL.Entity.DomainModels;
|
||||
using VOL.Model;
|
||||
using VOL.Model.SunshineRun;
|
||||
|
||||
namespace VOL.Business.Services.SunshineRun
|
||||
{
|
||||
public class SunshineRunService : ISunshineRunService, IDependency
|
||||
{
|
||||
private readonly VOLContext _dbContext;
|
||||
|
||||
public SunshineRunService(VOLContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
public async Task<PageDataDto<SunshineRunRecordModel>> GetPageList(SunshineRunPageParam param)
|
||||
{
|
||||
param ??= new SunshineRunPageParam();
|
||||
ValidateDateRange(param);
|
||||
var schoolId = await GetSchoolId();
|
||||
var total = await GetCount(param, schoolId);
|
||||
var records = await GetRecords(param, schoolId, param.PageIndex, param.PageSize);
|
||||
return new PageDataDto<SunshineRunRecordModel>
|
||||
{
|
||||
Total = total,
|
||||
TotalPages = (int)Math.Ceiling(total / (double)param.PageSize),
|
||||
PageIndex = param.PageIndex,
|
||||
PageSize = param.PageSize,
|
||||
Datas = records
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<SunshineRunRecordModel>> GetExportList(SunshineRunFilterParam param)
|
||||
{
|
||||
param ??= new SunshineRunFilterParam();
|
||||
ValidateDateRange(param);
|
||||
return await GetRecords(param, await GetSchoolId(), null, null);
|
||||
}
|
||||
|
||||
private static void ValidateDateRange(SunshineRunFilterParam param)
|
||||
{
|
||||
if (param.StartDate.HasValue && param.EndDate.HasValue &&
|
||||
param.StartDate.Value.Date > param.EndDate.Value.Date)
|
||||
throw new ArgumentException("结束日期不能早于开始日期");
|
||||
}
|
||||
|
||||
private async Task<string> GetSchoolId()
|
||||
{
|
||||
if (UserContext.Current.IsSuperAdmin)
|
||||
return null;
|
||||
|
||||
var schoolId = await _dbContext.Set<S_School>().AsNoTracking()
|
||||
.Where(x => x.SchoolCode == UserContext.Current.TenantId)
|
||||
.Select(x => (int?)x.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
return schoolId?.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
|
||||
}
|
||||
|
||||
private static string BuildWhere(SunshineRunFilterParam param, string schoolId)
|
||||
{
|
||||
var conditions = new List<string> { "1 = 1" };
|
||||
if (schoolId != null)
|
||||
conditions.Add("r.OrgId = @OrgId");
|
||||
if (!string.IsNullOrWhiteSpace(param.StudentName))
|
||||
conditions.Add("r.StudentName LIKE @StudentName");
|
||||
if (!string.IsNullOrWhiteSpace(param.ClassName))
|
||||
conditions.Add("(r.ClassName LIKE @ClassName OR CONCAT(r.GradeName, r.ClassName) LIKE @ClassName)");
|
||||
if (param.GradeId.HasValue)
|
||||
conditions.Add("r.GradeId = @GradeId");
|
||||
if (param.ClassId.HasValue)
|
||||
conditions.Add("r.ClassId = @ClassId");
|
||||
// CreateTime is stored as an ISO-formatted string in XW_SunshineRunTrain.
|
||||
if (param.StartDate.HasValue)
|
||||
conditions.Add("r.CreateTime >= @StartDate");
|
||||
if (param.EndDate.HasValue)
|
||||
conditions.Add("r.CreateTime < @EndDateExclusive");
|
||||
return string.Join(" AND ", conditions);
|
||||
}
|
||||
|
||||
private static void AddParameter(DbCommand command, string name, object value)
|
||||
{
|
||||
var parameter = command.CreateParameter();
|
||||
parameter.ParameterName = name;
|
||||
parameter.Value = value ?? DBNull.Value;
|
||||
command.Parameters.Add(parameter);
|
||||
}
|
||||
|
||||
private static void AddFilters(DbCommand command, SunshineRunFilterParam param, string schoolId)
|
||||
{
|
||||
if (schoolId != null)
|
||||
AddParameter(command, "@OrgId", schoolId);
|
||||
if (!string.IsNullOrWhiteSpace(param.StudentName))
|
||||
AddParameter(command, "@StudentName", "%" + param.StudentName.Trim() + "%");
|
||||
if (!string.IsNullOrWhiteSpace(param.ClassName))
|
||||
AddParameter(command, "@ClassName", "%" + param.ClassName.Trim() + "%");
|
||||
if (param.GradeId.HasValue)
|
||||
AddParameter(command, "@GradeId", param.GradeId.Value.ToString(CultureInfo.InvariantCulture));
|
||||
if (param.ClassId.HasValue)
|
||||
AddParameter(command, "@ClassId", param.ClassId.Value.ToString(CultureInfo.InvariantCulture));
|
||||
if (param.StartDate.HasValue)
|
||||
AddParameter(command, "@StartDate",
|
||||
param.StartDate.Value.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
|
||||
if (param.EndDate.HasValue)
|
||||
AddParameter(command, "@EndDateExclusive",
|
||||
param.EndDate.Value.Date.AddDays(1).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private async Task<int> GetCount(SunshineRunFilterParam param, string schoolId)
|
||||
{
|
||||
var connection = _dbContext.Database.GetDbConnection();
|
||||
await connection.OpenAsync();
|
||||
try
|
||||
{
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT COUNT(*) FROM XW_SunshineRunTrain r WHERE " + BuildWhere(param, schoolId);
|
||||
AddFilters(command, param, schoolId);
|
||||
return Convert.ToInt32(await command.ExecuteScalarAsync(), CultureInfo.InvariantCulture);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connection.CloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<SunshineRunRecordModel>> GetRecords(
|
||||
SunshineRunFilterParam param, string schoolId, int? pageIndex, int? pageSize)
|
||||
{
|
||||
var connection = _dbContext.Database.GetDbConnection();
|
||||
await connection.OpenAsync();
|
||||
try
|
||||
{
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText = @"SELECT r.StudentName, r.Sex, r.GradeName, r.ClassName,
|
||||
r.EffectiveDistance, r.InvalidDistance, r.Times, r.InvalidTimes, r.CreateTime
|
||||
FROM XW_SunshineRunTrain r WHERE " + BuildWhere(param, schoolId) +
|
||||
" ORDER BY r.CreateTime DESC, r.Id DESC" +
|
||||
(pageIndex.HasValue ? " OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY" : "");
|
||||
AddFilters(command, param, schoolId);
|
||||
if (pageIndex.HasValue)
|
||||
{
|
||||
AddParameter(command, "@Offset", (pageIndex.Value - 1) * pageSize.Value);
|
||||
AddParameter(command, "@PageSize", pageSize.Value);
|
||||
}
|
||||
|
||||
var rows = new List<SunshineRunRecordModel>();
|
||||
await using var reader = await command.ExecuteReaderAsync();
|
||||
var index = pageIndex.HasValue ? (pageIndex.Value - 1) * pageSize.Value : 0;
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
var effectiveDistance = GetInt(reader, 4);
|
||||
var invalidDistance = GetInt(reader, 5);
|
||||
var seconds = GetInt(reader, 6);
|
||||
var invalidSeconds = GetInt(reader, 7);
|
||||
var start = reader.IsDBNull(8) ? null : reader.GetString(8);
|
||||
var parsedStart = DateTime.TryParse(start, CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None, out var startTime);
|
||||
rows.Add(new SunshineRunRecordModel
|
||||
{
|
||||
Index = ++index,
|
||||
StudentName = GetString(reader, 0),
|
||||
SexName = GetInt(reader, 1) switch { 1 => "男", 2 => "女", _ => "-" },
|
||||
ClassName = string.Join(" ", new[] { GetString(reader, 2), GetString(reader, 3) }
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))),
|
||||
EffectiveDistance = FormatDistance(effectiveDistance),
|
||||
EffectiveTime = FormatTime(seconds.HasValue
|
||||
? Math.Max(0, seconds.Value - (invalidSeconds ?? 0)) : null),
|
||||
TotalDistance = FormatDistance(effectiveDistance.HasValue || invalidDistance.HasValue
|
||||
? (effectiveDistance ?? 0) + (invalidDistance ?? 0) : null),
|
||||
TotalTime = FormatTime(seconds),
|
||||
StartTime = parsedStart ? startTime.ToString("yyyy-MM-dd HH:mm:ss") : start ?? "-",
|
||||
EndTime = parsedStart && seconds.HasValue
|
||||
? startTime.AddSeconds(seconds.Value).ToString("yyyy-MM-dd HH:mm:ss") : "-"
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await connection.CloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetString(DbDataReader reader, int ordinal)
|
||||
=> reader.IsDBNull(ordinal) ? string.Empty : reader.GetString(ordinal);
|
||||
|
||||
private static int? GetInt(DbDataReader reader, int ordinal)
|
||||
=> reader.IsDBNull(ordinal) ? null : Convert.ToInt32(reader.GetValue(ordinal), CultureInfo.InvariantCulture);
|
||||
|
||||
private static string FormatDistance(int? meters)
|
||||
=> meters.HasValue ? (meters.Value / 1000m).ToString("0.##", CultureInfo.InvariantCulture) + "公里" : "-";
|
||||
|
||||
private static string FormatTime(int? seconds)
|
||||
{
|
||||
if (!seconds.HasValue)
|
||||
return "-";
|
||||
var value = Math.Max(0, seconds.Value);
|
||||
var minutes = value / 60;
|
||||
return minutes > 0 ? minutes + "分" + value % 60 + "秒" : value + "秒";
|
||||
}
|
||||
}
|
||||
}
|
||||
62
VOL.Model/SunshineRun/SunshineRunModels.cs
Normal file
62
VOL.Model/SunshineRun/SunshineRunModels.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace VOL.Model.SunshineRun
|
||||
{
|
||||
public class SunshineRunFilterParam
|
||||
{
|
||||
public string StudentName { get; set; }
|
||||
|
||||
public string ClassName { get; set; }
|
||||
|
||||
public int? GradeId { get; set; }
|
||||
|
||||
public int? ClassId { get; set; }
|
||||
|
||||
public DateTime? StartDate { get; set; }
|
||||
|
||||
public DateTime? EndDate { get; set; }
|
||||
}
|
||||
|
||||
public class SunshineRunPageParam : SunshineRunFilterParam
|
||||
{
|
||||
[Range(1, int.MaxValue)]
|
||||
public int PageIndex { get; set; } = 1;
|
||||
|
||||
[Range(1, 200)]
|
||||
public int PageSize { get; set; } = 10;
|
||||
}
|
||||
|
||||
public class SunshineRunRecordModel
|
||||
{
|
||||
[Display(Name = "序号")]
|
||||
public int Index { get; set; }
|
||||
|
||||
[Display(Name = "姓名")]
|
||||
public string StudentName { get; set; }
|
||||
|
||||
[Display(Name = "性别")]
|
||||
public string SexName { get; set; }
|
||||
|
||||
[Display(Name = "班级")]
|
||||
public string ClassName { get; set; }
|
||||
|
||||
[Display(Name = "有效里程")]
|
||||
public string EffectiveDistance { get; set; }
|
||||
|
||||
[Display(Name = "有效用时")]
|
||||
public string EffectiveTime { get; set; }
|
||||
|
||||
[Display(Name = "总里程")]
|
||||
public string TotalDistance { get; set; }
|
||||
|
||||
[Display(Name = "总用时")]
|
||||
public string TotalTime { get; set; }
|
||||
|
||||
[Display(Name = "开始时间")]
|
||||
public string StartTime { get; set; }
|
||||
|
||||
[Display(Name = "结束时间")]
|
||||
public string EndTime { get; set; }
|
||||
}
|
||||
}
|
||||
60
VOL.WebApi/Controllers/Business/SunshineRunController.cs
Normal file
60
VOL.WebApi/Controllers/Business/SunshineRunController.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using VOL.Business.IServices.SunshineRun;
|
||||
using VOL.Core.Enums;
|
||||
using VOL.Core.Filters;
|
||||
using VOL.Core.Utilities;
|
||||
using VOL.Model;
|
||||
using VOL.Model.SunshineRun;
|
||||
|
||||
namespace VOL.WebApi.Controllers.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// AI阳光跑运动记录。
|
||||
/// </summary>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(GroupName = "v3")]
|
||||
[TypeFilter(typeof(CustomApiResponseFilter))]
|
||||
public class SunshineRunController : ControllerBase
|
||||
{
|
||||
private readonly ISunshineRunService _service;
|
||||
|
||||
public SunshineRunController(ISunshineRunService service)
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetPageList))]
|
||||
[ApiActionPermission("XW_SunshineRunTrain", ActionPermissionOptions.Search)]
|
||||
public Task<PageDataDto<SunshineRunRecordModel>> GetPageList([FromQuery] SunshineRunPageParam param)
|
||||
=> _service.GetPageList(param);
|
||||
|
||||
[HttpGet(nameof(Export))]
|
||||
[ApiActionPermission("XW_SunshineRunTrain", ActionPermissionOptions.Search)]
|
||||
public async Task<IActionResult> Export([FromQuery] SunshineRunFilterParam param)
|
||||
{
|
||||
var rows = await _service.GetExportList(param);
|
||||
var columns = new List<string>
|
||||
{
|
||||
nameof(SunshineRunRecordModel.Index),
|
||||
nameof(SunshineRunRecordModel.StudentName),
|
||||
nameof(SunshineRunRecordModel.SexName),
|
||||
nameof(SunshineRunRecordModel.ClassName),
|
||||
nameof(SunshineRunRecordModel.EffectiveDistance),
|
||||
nameof(SunshineRunRecordModel.EffectiveTime),
|
||||
nameof(SunshineRunRecordModel.TotalDistance),
|
||||
nameof(SunshineRunRecordModel.TotalTime),
|
||||
nameof(SunshineRunRecordModel.StartTime),
|
||||
nameof(SunshineRunRecordModel.EndTime)
|
||||
};
|
||||
var bytes = Tool.ExportToExcel(
|
||||
new Dictionary<string, List<SunshineRunRecordModel>> { ["AI阳光跑"] = rows },
|
||||
columns, autoFitColumns: false);
|
||||
return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
$"AI阳光跑{DateTime.Now:yyyyMMddHHmmss}.xlsx");
|
||||
}
|
||||
}
|
||||
}
|
||||
29
sunshine-run-menu.sql
Normal file
29
sunshine-run-menu.sql
Normal file
@ -0,0 +1,29 @@
|
||||
SET XACT_ABORT ON;
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
DECLARE @parentId int = (SELECT TOP 1 Menu_Id FROM Sys_Menu WHERE MenuName = N'运动记录' AND ParentId = 0);
|
||||
IF @parentId IS NULL
|
||||
THROW 50001, N'未找到运动记录父菜单', 1;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM Sys_Role WHERE Role_Id = 2 AND RoleName = N'学校')
|
||||
THROW 50002, N'未找到学校角色', 1;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM Sys_Menu WHERE Url = '/sunshineRun')
|
||||
BEGIN
|
||||
INSERT INTO Sys_Menu
|
||||
(ParentId, MenuName, TableName, Url, Auth, Description, Icon,
|
||||
OrderNo, Creator, CreateDate, Enable, MenuType)
|
||||
VALUES
|
||||
(@parentId, N'AI阳光跑', N'XW_SunshineRunTrain', '/sunshineRun',
|
||||
N'[{"text":"查询","value":"Search"}]', N'芯未阳光跑运动记录',
|
||||
'el-icon-s-data', 1, 'system', GETDATE(), 1, 0);
|
||||
END;
|
||||
|
||||
DECLARE @menuId int = (SELECT TOP 1 Menu_Id FROM Sys_Menu WHERE Url = '/sunshineRun');
|
||||
IF NOT EXISTS (SELECT 1 FROM Sys_RoleAuth WHERE Role_Id = 2 AND Menu_Id = @menuId)
|
||||
BEGIN
|
||||
INSERT INTO Sys_RoleAuth (Role_Id, Menu_Id, AuthValue, Creator, CreateDate)
|
||||
VALUES (2, @menuId, 'Search', 'system', GETDATE());
|
||||
END;
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
Loading…
x
Reference in New Issue
Block a user