feat 导出

This commit is contained in:
2025-04-18 16:06:16 +08:00
parent 4f9cd67fea
commit b68a6c471f
14 changed files with 391 additions and 10 deletions

View File

@@ -51,6 +51,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dlhis.Constants", "Src\Dlhi
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dlhis.Canteen.Api", "Src\Dlhis.Canteen.Api\Dlhis.Canteen.Api.csproj", "{7934BE46-9409-4D28-8B35-82A0F900DD01}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dlhis.Canteen.Api", "Src\Dlhis.Canteen.Api\Dlhis.Canteen.Api.csproj", "{7934BE46-9409-4D28-8B35-82A0F900DD01}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dlhis.ExcelHelper", "Src\Dlhis.ExcelHelper\Dlhis.ExcelHelper.csproj", "{F8287775-E641-479D-952B-FFE7D826E52E}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -129,6 +131,10 @@ Global
{7934BE46-9409-4D28-8B35-82A0F900DD01}.Debug|Any CPU.Build.0 = Debug|Any CPU {7934BE46-9409-4D28-8B35-82A0F900DD01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7934BE46-9409-4D28-8B35-82A0F900DD01}.Release|Any CPU.ActiveCfg = Release|Any CPU {7934BE46-9409-4D28-8B35-82A0F900DD01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7934BE46-9409-4D28-8B35-82A0F900DD01}.Release|Any CPU.Build.0 = Release|Any CPU {7934BE46-9409-4D28-8B35-82A0F900DD01}.Release|Any CPU.Build.0 = Release|Any CPU
{F8287775-E641-479D-952B-FFE7D826E52E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8287775-E641-479D-952B-FFE7D826E52E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8287775-E641-479D-952B-FFE7D826E52E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8287775-E641-479D-952B-FFE7D826E52E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -24,6 +24,7 @@
<ProjectReference Include="..\Dlhis.Basic.Api\Dlhis.Basic.Api.csproj" /> <ProjectReference Include="..\Dlhis.Basic.Api\Dlhis.Basic.Api.csproj" />
<ProjectReference Include="..\Dlhis.Constants\Dlhis.Constants.csproj" /> <ProjectReference Include="..\Dlhis.Constants\Dlhis.Constants.csproj" />
<ProjectReference Include="..\Dlhis.Entity\Dlhis.Entity.csproj" /> <ProjectReference Include="..\Dlhis.Entity\Dlhis.Entity.csproj" />
<ProjectReference Include="..\Dlhis.ExcelHelper\Dlhis.ExcelHelper.csproj" />
<ProjectReference Include="..\Dlhis.Sys.Api\Dlhis.Sys.Api.csproj" /> <ProjectReference Include="..\Dlhis.Sys.Api\Dlhis.Sys.Api.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -12,8 +12,10 @@ ARG BUILD_CONFIGURATION=Release
WORKDIR /src WORKDIR /src
COPY ["Src/Dlhis.Canteen/Dlhis.Canteen.csproj", "Src/Dlhis.Canteen/"] COPY ["Src/Dlhis.Canteen/Dlhis.Canteen.csproj", "Src/Dlhis.Canteen/"]
COPY ["Src/Dlhis.Basic.Api/Dlhis.Basic.Api.csproj", "Src/Dlhis.Basic.Api/"] COPY ["Src/Dlhis.Basic.Api/Dlhis.Basic.Api.csproj", "Src/Dlhis.Basic.Api/"]
COPY ["Src/Dlhis.Constants/Dlhis.Constants.csproj", "Src/Dlhis.Constants/"]
COPY ["Src/Dlhis.Entity/Dlhis.Entity.csproj", "Src/Dlhis.Entity/"] COPY ["Src/Dlhis.Entity/Dlhis.Entity.csproj", "Src/Dlhis.Entity/"]
COPY ["Src/Dlhis.DataPermission.Model/Dlhis.DataPermission.Model.csproj", "Src/Dlhis.DataPermission.Model/"] COPY ["Src/Dlhis.DataPermission.Model/Dlhis.DataPermission.Model.csproj", "Src/Dlhis.DataPermission.Model/"]
COPY ["Src/Dlhis.ExcelHelper/Dlhis.ExcelHelper.csproj", "Src/Dlhis.ExcelHelper/"]
COPY ["Src/Dlhis.Sys.Api/Dlhis.Sys.Api.csproj", "Src/Dlhis.Sys.Api/"] COPY ["Src/Dlhis.Sys.Api/Dlhis.Sys.Api.csproj", "Src/Dlhis.Sys.Api/"]
RUN dotnet restore "./Src/Dlhis.Canteen/Dlhis.Canteen.csproj" RUN dotnet restore "./Src/Dlhis.Canteen/Dlhis.Canteen.csproj"
COPY . . COPY . .

View File

@@ -0,0 +1,43 @@
using Dlhis.ExcelHelper;
using Kurisu.AspNetCore.DataAccess.Dto;
using Kurisu.AspNetCore.UnifyResultAndValidation.Abstractions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Dlhis.Canteen;
/// <summary>
/// 忽略响应<see cref="IApiResult"/>.Data的自动camelCase
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ExcelResultAttribute : Attribute, IAsyncActionFilter
{
private readonly string _fileName;
public ExcelResultAttribute(string fileName)
{
_fileName = fileName;
}
/// <inheritdoc />
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var r = await next();
var or = r.Result as ObjectResult;
var v = or.Value;
var type = or.DeclaredType.GenericTypeArguments[0];
if (or.DeclaredType.IsGenericType && or.DeclaredType.GetGenericTypeDefinition() == typeof(Pagination<>))
{
v = v.GetType().GetProperty("Data").GetValue(v, null);
}
var method = typeof(ExcelExtensions).GetMethod(nameof(ExcelExtensions.ToExcelBytesAsync));
var task = (Task<byte[]>)method.MakeGenericMethod(type).Invoke(null, new[] { v });
var data = await task;
r.Result = new FileContentResult(data, "application/vnd.ms-excel")
{
FileDownloadName = _fileName
};
}
}

View File

@@ -42,6 +42,7 @@ public class BalancesController : ControllerBase
/// <param name="request"></param> /// <param name="request"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("export")] [HttpGet("export")]
[ExcelResult("员工账务明细")]
[IgnoreTenant] [IgnoreTenant]
public async Task<List<BalanceRecordResponse>> GetPageToExportAsync([FromQuery] BalanceRecordPageRequest request) public async Task<List<BalanceRecordResponse>> GetPageToExportAsync([FromQuery] BalanceRecordPageRequest request)
{ {

View File

@@ -1,11 +1,13 @@
using Dlhis.Entity.Canteen.Enums; using Dlhis.Entity.Canteen.Enums;
using Dlhis.ExcelHelper;
using Kurisu.AspNetCore.Utils.Converters; using Kurisu.AspNetCore.Utils.Converters;
using Kurisu.AspNetCore.Utils.Extensions; using Kurisu.AspNetCore.Utils.Extensions;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Dlhis.Canteen.Order.Response; namespace Dlhis.Canteen.Order.Response;
public class BalanceRecordResponse [Excel("BalanceRecords")]
public class BalanceRecordResponse : IExcelExportable
{ {
public long Id { get; set; } public long Id { get; set; }
@@ -27,34 +29,40 @@ public class BalanceRecordResponse
/// <summary> /// <summary>
/// 姓名 /// 姓名
/// </summary> /// </summary>
[Excel("姓名")]
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// 学工号 /// 学工号
/// </summary> /// </summary>
[Excel("工号")]
public string UserCode { get; set; } public string UserCode { get; set; }
/// <summary> /// <summary>
/// 部门号 /// 部门号
/// </summary> /// </summary>
[Excel("部门号")]
public string DepartmentNo { get; set; } public string DepartmentNo { get; set; }
/// <summary> /// <summary>
/// 部门 /// 部门
/// </summary> /// </summary>
[Excel("部门名称")]
public string DepartmentName { get; set; } public string DepartmentName { get; set; }
/// <summary> /// <summary>
/// 所属成本日期 /// 所属成本日期
/// </summary> /// </summary>
[Excel("营业日期", "yyyy-MM-dd")]
[JsonConverter(typeof(ToDateJsonConverter))] [JsonConverter(typeof(ToDateJsonConverter))]
public DateTime CostDate { get; set; } public DateTime CostDate { get; set; }
/// <summary> /// <summary>
/// 备注 /// 记录产生时间
/// </summary> /// </summary>
public string Remark { get; set; } [Excel("消费时间", "yyyy-MM-dd HH:mm:ss")]
public DateTime CostDateTime { get; set; }
/// <summary> /// <summary>
/// 来源 /// 来源
@@ -64,6 +72,7 @@ public class BalanceRecordResponse
/// <summary> /// <summary>
/// 消费识别来源 /// 消费识别来源
/// </summary> /// </summary>
[Excel("消费来源")]
public string SourceDesc => Source.GetDescription(); public string SourceDesc => Source.GetDescription();
/// <summary> /// <summary>
@@ -76,11 +85,13 @@ public class BalanceRecordResponse
/// <summary> /// <summary>
/// 金额 /// 金额
/// </summary> /// </summary>
[Excel("消费金额", "0.00")]
public decimal Amount { get; set; } public decimal Amount { get; set; }
/// <summary> /// <summary>
/// 营业点 /// 营业点
/// </summary> /// </summary>
[Excel("营业点")]
public string BusinessPlaceName { get; set; } public string BusinessPlaceName { get; set; }
/// <summary> /// <summary>
@@ -91,6 +102,7 @@ public class BalanceRecordResponse
/// <summary> /// <summary>
/// 餐次 /// 餐次
/// </summary> /// </summary>
[Excel("场次")]
public string ShiftTypeDesc => ShiftType?.GetDescription(); public string ShiftTypeDesc => ShiftType?.GetDescription();
/// <summary> /// <summary>
@@ -103,11 +115,6 @@ public class BalanceRecordResponse
/// </summary> /// </summary>
public string CostId { get; set; } public string CostId { get; set; }
/// <summary>
/// 消费者所在基地
/// </summary>
public string OriginTenantId { get; set; }
/// <summary> /// <summary>
/// 数据状态 /// 数据状态
/// </summary> /// </summary>
@@ -116,20 +123,30 @@ public class BalanceRecordResponse
/// <summary> /// <summary>
/// 数据状态描述 /// 数据状态描述
/// </summary> /// </summary>
[Excel("数据状态")]
public string StatusDesc => Status.GetDescription(); public string StatusDesc => Status.GetDescription();
/// <summary> /// <summary>
/// 记录产生时间 /// 消费者所在基地
/// </summary> /// </summary>
public DateTime CostDateTime { get; set; } [Excel("在职厂区")]
public string OriginTenantId { get; set; }
/// <summary> /// <summary>
/// 厂区 /// 厂区
/// </summary> /// </summary>
[Excel("消费厂区")]
public string Factory { get; set; } public string Factory { get; set; }
/// <summary> /// <summary>
/// 人事范围 /// 人事范围
/// </summary> /// </summary>
[Excel("人事范围")]
public string Werks { get; set; } public string Werks { get; set; }
/// <summary>
/// 备注
/// </summary>
[Excel("备注")]
public string Remark { get; set; }
} }

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EPPlus" Version="6.2.19" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
namespace Dlhis.ExcelHelper;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class ExcelAttribute : Attribute
{
public ExcelAttribute(string description) : this(description, string.Empty)
{
}
public ExcelAttribute(string description, string format)
{
Description = description;
Format = format;
}
public string Description { get; set; }
public string Format { get; set; }
}

View File

@@ -0,0 +1,51 @@
using System.Reflection;
using OfficeOpenXml;
namespace Dlhis.ExcelHelper;
public static class ExcelExtensions
{
public static async Task<byte[]> ToExcelBytesAsync<T>(this List<T> data) where T : IExcelExportable
{
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
var type = typeof(T);
var sheetName = type.GetCustomAttribute<ExcelAttribute>()!.Description;
var excelHeaderNames = type.GetProperties().Where(x => x.IsDefined(typeof(ExcelAttribute))).ToList();
using var package = new ExcelPackage();
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add(sheetName);
// 写入表头
for (int i = 0; i < excelHeaderNames.Count; i++)
{
var cell = worksheet.Cells[1, i + 1];
worksheet.Column(i + 1).BestFit = true;
cell.Style.Font.Bold = true;
cell.Value = excelHeaderNames[i].GetCustomAttribute<ExcelAttribute>()!.Description;
}
int row = 2;
foreach (var item in data)
{
for (int col = 0; col < excelHeaderNames.Count; col++)
{
var column = excelHeaderNames[col];
var value = column.GetValue(item);
var cell = worksheet.Cells[row, col + 1];
var format = column.GetCustomAttribute<ExcelAttribute>()!.Format;
cell.Value = value;
if (!string.IsNullOrEmpty(format))
{
cell.Style.Numberformat.Format = format;
}
}
row++;
}
worksheet.Cells.AutoFitColumns();
return await package.GetAsByteArrayAsync();
}
}

View File

@@ -0,0 +1,5 @@
namespace Dlhis.ExcelHelper;
public interface IExcelExportable
{
}

View File

@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
namespace Dlhis.Basic.Common.DataExport.Request
{
public class DataExportRequest
{
[Required]
public string TableName { get; set; }
[MinLength(1)]
public List<string> ColumnNames { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace Dlhis.Basic.Common.DataExport.Request
{
public class DataImportRequest
{
public string TableName { get; set; }
public string FilePath { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
//using Dlhis.Basic.Common.DataExport.Request;
//using Microsoft.AspNetCore.Mvc;
//namespace Dlhis.Basic.Common.DataExport.Services
//{
// public interface IDataExportService : IScopeDependency
// {
// Task<FileContentResult> GetDataExportAsync(DataExportRequest request);
// Task DataImportAsync(IFormFile file);
// }
//}

View File

@@ -0,0 +1,188 @@
//using System.Collections;
//using System.Reflection;
//using Dlhis.Basic.Common.DataExport.Request;
//using Kurisu.AspNetCore.DataAccess.SqlSugar.Services;
//using Kurisu.AspNetCore.Utils.Extensions;
//using Mapster;
//using Microsoft.AspNetCore.Mvc;
//using Newtonsoft.Json.Linq;
//using OfficeOpenXml;
//using SqlSugar;
//namespace Dlhis.Basic.Common.DataExport.Services.Implements
//{
// public class DataExportService : IDataExportService
// {
// protected IDbContext DbContext { get; }
// public DataExportService(IDbContext dbContext)
// {
// DbContext = dbContext;
// ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
// }
// private readonly static MethodInfo _queryMethod = typeof(IAdo).GetMethod(nameof(IAdo.SqlQueryAsync), 1, new[] { typeof(string), typeof(object) }, null);
// private readonly static MethodInfo _handlerTaskMethod = typeof(DataExportService).GetMethod(nameof(HandlerResult), BindingFlags.NonPublic | BindingFlags.Instance);
// public async Task<FileContentResult> GetDataExportAsync(DataExportRequest request)
// {
// var types = Assembly.Load("Dlhis.Entity")
// .GetTypes()
// .Where(x => x.IsClass && x.GetCustomAttribute<SugarTable>() != null);
// var type = types.FirstOrDefault(x => x.GetCustomAttribute<SugarTable>()?.TableName == request.TableName)!;
// type.ThrowIfNull("该表不存在");
// var columns = type.GetProperties().Where(x => request.ColumnNames.Contains(x.Name)).ToList();
// var excelHeaderNames = columns.Select(x => x.GetCustomAttribute<SugarColumn>()?.ColumnDescription ?? x.Name)
// .ToList();
// using (var package = new ExcelPackage())
// {
// ExcelWorksheet worksheet = package.Workbook.Worksheets.Add(request.TableName);
// // 写入表头
// for (int i = 0; i < excelHeaderNames.Count; i++)
// {
// worksheet.Cells[1, i + 1].Value = excelHeaderNames[i];
// }
// var tableName = type.GetCustomAttribute<SugarTable>().TableName;
// var fields = columns.Select(x => x.Name).Join(",");
// var task = _queryMethod.MakeGenericMethod(type).Invoke(DbContext.Client.Ado, new object[] { @$"SELECT {fields} FROM {tableName}", null });
// var data = _handlerTaskMethod.MakeGenericMethod(type).Invoke(this, new object[] { task });
// int row = 2;
// foreach (var item in data.ToJson().ToObject<List<JObject>>())
// {
// for (int col = 0; col < columns.Count; col++)
// {
// var column = columns[col];
// var value = item[column.Name]!.Value<string>();
// if (column.PropertyType.IsEnum)
// {
// value = column.PropertyType.GetFields(BindingFlags.Public | BindingFlags.Static).Where(p => (int)p.GetValue(null) == item[column.Name]!.Value<int>()).Select(p =>
// {
// var e = (Enum)Enum.Parse(p.FieldType, p.GetValue(null).ToString());
// var fd = e.GetDescription();
// return fd;
// }).First();
// }
// worksheet.Cells[row, col + 1].Value = value;
// }
// row++;
// }
// var ms = new MemoryStream();
// await package.SaveAsAsync(ms);
// var contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
// return new FileContentResult(ms.ToArray(), contentType);
// }
// }
// private List<TResult> HandlerResult<TResult>(object o)
// {
// var originalValue = (Task<List<TResult>>)o;
// if (!originalValue.IsCompleted)
// {
// return Task.Run(() => originalValue).GetAwaiter().GetResult();
// }
// return originalValue.Result;
// }
// public async Task DataImportAsync(IFormFile file)
// {
// //获取数据
// ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
// using (var package = new ExcelPackage(file.OpenReadStream()))
// {
// var worksheet = package.Workbook.Worksheets[0];
// //通过表名,获取实体表类型
// var type = GetEntityTypeByTableName(worksheet.Name);
// var tableType = typeof(List<>).MakeGenericType(type);
// var listGenericAddMethod = tableType.GetMethod("Add");
// var tableTypeInstance = Activator.CreateInstance(tableType);
// var headers = new List<string>();
// for (int col = 1; col <= worksheet.Dimension.Columns; col++)
// {
// headers.Add(worksheet.Cells[1, col].Value?.ToString() ?? string.Empty);
// }
// var properties = type.GetProperties();
// for (int row = 2; row <= worksheet.Dimension.Rows; row++)
// {
// var item = Activator.CreateInstance(type);
// for (int col = 1; col <= worksheet.Dimension.Columns; col++)
// {
// var header = headers[col - 1];
// var property = properties.FirstOrDefault(p => (p.GetCustomAttribute<SugarColumn>()?.ColumnDescription ?? p.Name).Equals(header, StringComparison.OrdinalIgnoreCase));
// var value = worksheet.Cells[row, col].Value.Adapt(typeof(object), property.PropertyType);
// if (property != null)
// {
// if (property.PropertyType.IsEnum)
// {
// try
// {
// var enumValue = Enum.Parse(property.PropertyType, value.ToString(), true);
// property.SetValue(item, enumValue);
// }
// catch (Exception ex)
// {
// Console.WriteLine($"无法将值 '{value}' 转换为枚举类型 '{property.PropertyType.Name}':{ex.Message}");
// }
// }
// else
// {
// property.SetValue(item, value);
// }
// }
// }
// listGenericAddMethod.Invoke(tableTypeInstance, new[] { item });
// }
// //foreach (var item in list)
// // await DbContext.Client.InsertableByObject(item).ExecuteCommandAsync();
// var insertMethod = typeof(IDbContext).GetMethods(BindingFlags.Public | BindingFlags.Instance)
// .Where(x => x.Name == nameof(DbContext.InsertAsync))
// .Where(x =>
// {
// var t = x.GetParameters().First().ParameterType;
// var rr = x.GetParameters().First().ParameterType.IsAssignableTo(typeof(IList)) && !t.IsArray;
// return rr;
// })
// .First();
// var task = (Task<int>)insertMethod.MakeGenericMethod(type).Invoke(DbContext, new object[] { tableTypeInstance });
// await task.ConfigureAwait(false);
// }
// }
// public Type GetEntityTypeByTableName(string tableName)
// {
// var type = Assembly.Load("Dlhis.Entity")
// .GetTypes()
// .Where(x => x.IsClass && x.GetCustomAttribute<SugarTable>() != null)
// .FirstOrDefault(x => x.GetCustomAttribute<SugarTable>().TableName == tableName);
// return type;
// }
// }
//}