Commit 659f7573 by ruyun.zhang@suvalue.com

Merge branch 'feature/在线浏览编辑Excel' into develop

parents 4467e5d3 4d573014
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Performance.DtoModels;
using Performance.Services;
using Performance.Services.OnlineExcel;
namespace Performance.Api.Controllers
{
[Route("api/online/excel")]
[ApiController]
public class OnlineExcelController : Controller
{
private readonly AllotService _allotService;
private readonly OnlineExcelService _excelService;
public OnlineExcelController(
AllotService allotService,
OnlineExcelService excelService)
{
_allotService = allotService;
_excelService = excelService;
}
[Route("sheet/name")]
[HttpGet]
[AllowAnonymous]
public ApiResponse SheetName(int allotId)
{
var allot = _allotService.GetAllot(allotId);
if (allot == null)
return new ApiResponse(ResponseType.Fail, "当前绩效信息无效", "当前绩效信息无效");
var sheetNames = _excelService.GetExcelSheetName(allot);
if (sheetNames == null || sheetNames.Count == 0)
return new ApiResponse(ResponseType.Fail, "未能找到有效[SHEET]", "未能找到有效[SHEET]");
return new ApiResponse(ResponseType.OK, sheetNames);
}
[Route("sheet/data")]
[HttpGet]
[AllowAnonymous]
public ApiResponse SheetName(int allotId, string sheetName)
{
var allot = _allotService.GetAllot(allotId);
if (allot == null)
return new ApiResponse(ResponseType.Fail, "当前绩效信息无效", "当前绩效信息无效");
var s = _excelService.ReadSheet(allot, sheetName);
return new ApiResponse(ResponseType.OK, "", JsonConvert.SerializeObject(s, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));
}
[Route("sheet/chanage/{allotId}")]
[HttpPost]
[AllowAnonymous]
public ApiResponse WriteSheet(int allotId, [FromBody] EpChanage chanage)
{
var allot = _allotService.GetAllot(allotId);
if (allot == null)
return new ApiResponse(ResponseType.Fail, "当前绩效信息无效", "当前绩效信息无效");
_excelService.WriteSheet(allot, chanage);
return new ApiResponse(ResponseType.OK);
}
}
}
...@@ -2165,6 +2165,7 @@ ...@@ -2165,6 +2165,7 @@
<param name="query"></param> <param name="query"></param>
<returns></returns> <returns></returns>
</member> </member>
<<<<<<< HEAD
<member name="M:Performance.Api.BackgroundJob.Execute_Allot_Generate(Performance.Services.TaskService,Performance.EntityModels.bg_task)"> <member name="M:Performance.Api.BackgroundJob.Execute_Allot_Generate(Performance.Services.TaskService,Performance.EntityModels.bg_task)">
<summary> <summary>
生成测算表 生成测算表
...@@ -2200,11 +2201,14 @@ ...@@ -2200,11 +2201,14 @@
<param name="service"></param> <param name="service"></param>
<param name="tasks"></param> <param name="tasks"></param>
</member> </member>
=======
>>>>>>> 在线浏览编辑Excel
<member name="T:Performance.Api.ClearLoggerJob"> <member name="T:Performance.Api.ClearLoggerJob">
<summary> <summary>
删除历史日志 删除历史日志
</summary> </summary>
</member> </member>
<<<<<<< HEAD
<member name="T:Performance.Api.ExpirationLimitMiddleware"> <member name="T:Performance.Api.ExpirationLimitMiddleware">
<summary> <summary>
过期限制 过期限制
...@@ -2217,6 +2221,8 @@ ...@@ -2217,6 +2221,8 @@
<param name="builder"></param> <param name="builder"></param>
<returns></returns> <returns></returns>
</member> </member>
=======
>>>>>>> 在线浏览编辑Excel
<member name="M:Performance.Api.ClaimService.GetUserId"> <member name="M:Performance.Api.ClaimService.GetUserId">
<summary> <summary>
获取当前请求登录ID 获取当前请求登录ID
......
using System;
using System.Collections.Generic;
using System.Text;
namespace Performance.Infrastructure
{
public static partial class UtilExtensions
{
/// <summary>
/// 时间戳计时开始时间
/// </summary>
private static DateTime timeStampStartTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
/// <summary>
/// DateTime转换为10位时间戳(单位:秒)
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static long ToTimeStamp(this DateTime dateTime)
{
return (long)(dateTime.ToUniversalTime() - timeStampStartTime).TotalSeconds;
}
/// <summary>
/// DateTime转换为13位时间戳(单位:毫秒)
/// </summary>
/// <param name="dateTime"> DateTime</param>
/// <returns>13位时间戳(单位:毫秒)</returns>
public static long ToLongTimeStamp(this DateTime dateTime)
{
return (long)(dateTime.ToUniversalTime() - timeStampStartTime).TotalMilliseconds;
}
/// <summary>
/// 10位时间戳(单位:秒)转换为DateTime
/// </summary>
/// <param name="timeStamp">10位时间戳(单位:秒)</param>
/// <returns>DateTime</returns>
public static DateTime ToDateTime(this long timeStamp)
{
return timeStampStartTime.AddSeconds(timeStamp).ToLocalTime();
}
/// <summary>
/// 13位时间戳(单位:毫秒)转换为DateTime
/// </summary>
/// <param name="longTimeStamp">13位时间戳(单位:毫秒)</param>
/// <returns>DateTime</returns>
public static DateTime ToDateTimeLongTimeStamp(this long longTimeStamp)
{
return timeStampStartTime.AddMilliseconds(longTimeStamp).ToLocalTime();
}
}
}
using System.Collections.Generic;
namespace Performance.Services.OnlineExcel
{
public class EpColumn
{
public int row { get; set; }
public int col { get; set; }
public string renderer { get; set; }
}
/// <summary>
/// 单元格Class
/// </summary>
public class EpCellClass
{
private List<string> _className;
public int row { get; set; }
public int col { get; set; }
public bool editor { get; set; }
public string className
{
get
{
if (_className == null)
return "";
return string.Join(" ", _className.ToArray());
}
}
public void AddClassName(string name)
{
if (_className == null)
_className = new List<string>();
_className.Add(name);
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
namespace Performance.Services.OnlineExcel
{
public enum Operation
{
InsertRow = 1,
DeleteRow = 2,
InsertColumn = 3,
DeleteColumn = 4,
}
/// <summary>
/// 操作情况
/// </summary>
public class OperationRecord
{
public DateTime DateTime { get; set; }
public Operation Operation { get; set; }
public int From { get; set; }
public int Count { get; set; }
}
/// <summary>
/// 数据变更提交记录
/// </summary>
public class EpChanage
{
public string SheetName { get; set; }
public string Version { get; set; }
public OperationRecord[] OperationRecord { get; set; }
public List<dynamic> Data { get; set; }
}
}
\ No newline at end of file
namespace Performance.Services.OnlineExcel
{
/// <summary>
/// 边框(弃用 影响性能)
/// </summary>
public class EpCustomBorders
{
public int row { get; set; }
public int col { get; set; }
public Style left { get; set; }
public Style right { get; set; }
public Style top { get; set; }
public Style bottom { get; set; }
public class Style
{
public int width { get; set; }
public string color { get; set; }
}
}
}
\ No newline at end of file
namespace Performance.Services.OnlineExcel
{
/// <summary>
/// 单元格合并
/// </summary>
public class EpMerge
{
public int row { get; set; }
public int col { get; set; }
public int rowspan { get; set; }
public int colspan { get; set; }
}
}
\ No newline at end of file
using System.Collections.Generic;
namespace Performance.Services.OnlineExcel
{
/// <summary>
/// 加载Excel汇总信息
/// </summary>
public class EpSheet
{
public object renders { get; set; }
public object mergeCells { get; set; }
public object data { get; set; }
public object cell { get; set; }
public object colWidths { get; set; }
}
}
\ No newline at end of file
using Performance.DtoModels;
namespace Performance.Services.OnlineExcel
{
public partial class OnlineExcelService
{
public class ExcelSheetInfo
{
public string Name { get; set; }
public int Row { get; set; }
public int Column { get; set; }
public string Version { get; set; }
public string Message
{
get
{
if (Row * Column > 500 * 50)
return "数据量很大,加载需要较长时间";
else if (Row * Column > 100 * 50)
return "数据较多,可能需要较长加载时间";
return "";
}
}
public SheetType SheetType { get; internal set; }
public string ModuleName { get; internal set; }
}
}
}
\ No newline at end of file
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using Performance.DtoModels;
using Performance.EntityModels;
using Performance.Infrastructure;
using Polly;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Performance.Services.OnlineExcel
{
public partial class OnlineExcelService : IAutoInjection
{
public static Dictionary<string, string> HorizontalMapps = new Dictionary<string, string>()
{
{ "Left", "htLeft" },
{ "Center", "htCenter" },
{ "Right", "htRight" },
{ "Justify", "htJustify" },
};
public static Dictionary<string, string> VerticalMapps = new Dictionary<string, string>()
{
{ "Top", "htTop" },
{ "Center", "htMiddle" },
{ "Bottom", "htBottom" },
};
public static TimeSpan absoluteExpirationRelativeToNow = new TimeSpan(0, 5, 0);
private readonly IMemoryCache _cache;
private readonly PerSheetService _sheetService;
public OnlineExcelService(
IMemoryCache cache,
PerSheetService sheetService)
{
_cache = cache;
_sheetService = sheetService;
}
/// <summary>
/// 读取Sheet名称
/// </summary>
/// <param name="allot"></param>
/// <returns></returns>
public List<ExcelSheetInfo> GetExcelSheetName(per_allot allot)
{
// 优先返回缓存内容
var key = $"SheetName:{allot.Path}";
if (_cache.TryGetValue(key, out List<ExcelSheetInfo> sheetNames))
return sheetNames;
sheetNames = sheetNames ?? new List<ExcelSheetInfo>();
FileInfo file = new FileInfo(allot.Path);
var version = file.LastWriteTimeUtc.ToTimeStamp();
using (ExcelPackage package = new ExcelPackage(file))
{
foreach (var sheet in package.Workbook.Worksheets)
{
SheetType sheetType = _sheetService.GetSheetType(sheet.Name);
if (!sheetNames.Exists(w => w.Name == sheet.Name))
{
sheetNames.Add(new ExcelSheetInfo
{
Name = sheet.Name,
Row = sheet.Dimension.End.Row,
Column = sheet.Dimension.End.Column,
SheetType = sheetType,
ModuleName = EnumHelper.GetDescription(sheetType),
Version = version.ToString(),
});
}
}
}
sheetNames = sheetNames
.OrderBy(w => w.SheetType == SheetType.Unidentifiable ? 1 : 0).ToList();
_cache.Set(key, sheetNames, absoluteExpirationRelativeToNow);
return sheetNames;
}
/// <summary>
/// 读取Sheet信息
/// </summary>
/// <param name="allot"></param>
/// <param name="sheetName"></param>
/// <returns></returns>
public EpSheet ReadSheet(per_allot allot, string sheetName)
{
// 优先返回缓存内容
var key = $"SheetData-{sheetName}:{allot.Path}";
if (_cache.TryGetValue(key, out EpSheet cacheSheet))
return cacheSheet;
FileInfo file = new FileInfo(allot.Path);
using (ExcelPackage package = new ExcelPackage(file))
{
foreach (var sheet in package.Workbook.Worksheets)
{
if (sheet.Name != sheetName) continue;
IEnumerable<EpMerge> mergeCells = GetMergeCells(sheet);
List<double> colWidths = GetColWidths(sheet);
#region data
List<Dictionary<string, object>> datas = new List<Dictionary<string, object>>();
List<EpCellClass> cells = new List<EpCellClass>();
List<EpColumn> renders = new List<EpColumn>();
for (int row = 1, n = sheet.Dimension.End.Row; row <= n; row++)
{
Dictionary<string, object> data = new Dictionary<string, object>();
for (int col = 1, k = sheet.Dimension.End.Column; col <= k; col++)
{
var cell = sheet.Cells[row, col];
if (cell == null) continue;
var value = GetCellValue(cell);
var colName = Regex.Replace(cell.Address, "[0-9]", "", RegexOptions.IgnoreCase);
data.Add(colName, value);
var cellStyle = GetCellClass(cell, row, col);
if (cellStyle != null)
cells.Add(cellStyle);
var render = GetCellRender(cell, row, col);
if (render != null)
renders.Add(render);
}
datas.Add(data);
}
#endregion
EpSheet epSheet = new EpSheet()
{
cell = cells,
colWidths = colWidths,
data = datas,
renders = renders,
mergeCells = mergeCells,
};
_cache.Set(key, epSheet, absoluteExpirationRelativeToNow);
return epSheet;
}
}
return null;
}
#region
//private static List<double> GetRowHeights(ExcelWorksheet sheet)
//{
// List<double> rowHeights = new List<double>();
// for (int row = 1, k = sheet.Dimension.End.Row; row <= k; row++)
// {
// rowHeights.Add(Math.Round(sheet.Row(row).Height));
// }
// return rowHeights;
//}
//private EpCustomBorders GetCellBorders(ExcelRange cell, int row, int col)
//{
// if (cell.Style.Border.Top.Style != ExcelBorderStyle.None
// || cell.Style.Border.Bottom.Style != ExcelBorderStyle.None
// || cell.Style.Border.Left.Style != ExcelBorderStyle.None
// || cell.Style.Border.Right.Style != ExcelBorderStyle.None)
// {
// EpCustomBorders borders = new EpCustomBorders()
// {
// row = row - 1,
// col = col - 1
// };
// borders.top = new EpCustomBorders.Style { width = 2, color = "#000" };
// borders.bottom = new EpCustomBorders.Style { width = 2, color = "#000" };
// borders.left = new EpCustomBorders.Style { width = 2, color = "#000" };
// borders.right = new EpCustomBorders.Style { width = 2, color = "#000" };
// return borders;
// }
// return null;
//}
#endregion
/// <summary>
/// 读取列宽度
/// </summary>
/// <param name="sheet"></param>
/// <returns></returns>
private static List<double> GetColWidths(ExcelWorksheet sheet)
{
List<double> colWidths = new List<double>();
for (int col = 1, k = sheet.Dimension.End.Column; col <= k; col++)
{
colWidths.Add(Math.Round(sheet.Column(col).Width * 10));
}
return colWidths;
}
/// <summary>
/// 读取单元格渲染方式
/// </summary>
/// <param name="cell"></param>
/// <param name="row"></param>
/// <param name="col"></param>
/// <returns></returns>
private EpColumn GetCellRender(ExcelRange cell, int row, int col)
{
var column = new EpColumn
{
row = row - 1,
col = col - 1,
};
if (cell.Value is ExcelErrorValue)
{
column.renderer = "customExcelErrorValueStylesRenderer";
}
else if (!string.IsNullOrEmpty(cell.Formula))
{
column.renderer = "customFormulaStylesRenderer";
}
else if (cell.Style.Border.Top.Style != ExcelBorderStyle.None
|| cell.Style.Border.Bottom.Style != ExcelBorderStyle.None
|| cell.Style.Border.Left.Style != ExcelBorderStyle.None
|| cell.Style.Border.Right.Style != ExcelBorderStyle.None)
{
column.renderer = "customExcelBorderStylesRenderer";
}
if (string.IsNullOrEmpty(column.renderer))
return null;
return column;
}
/// <summary>
/// 读取类Class
/// </summary>
/// <param name="cell"></param>
/// <param name="row"></param>
/// <param name="col"></param>
/// <returns></returns>
private EpCellClass GetCellClass(ExcelRange cell, int row, int col)
{
var cellStyle = new EpCellClass
{
row = row - 1,
col = col - 1,
editor = false
};
//if (HorizontalMapps.ContainsKey(cell.Style.HorizontalAlignment.ToString()))
//{
// cellStyle.AddClassName(HorizontalMapps[cell.Style.HorizontalAlignment.ToString()]);
//}
//if (VerticalMapps.ContainsKey(cell.Style.VerticalAlignment.ToString()))
//{
// cellStyle.AddClassName(VerticalMapps[cell.Style.VerticalAlignment.ToString()]);
//}
cellStyle.AddClassName("htCenter");
cellStyle.AddClassName("htMiddle");
return cellStyle;
}
/// <summary>
/// 读取值
/// </summary>
/// <param name="cell"></param>
/// <returns></returns>
private object GetCellValue(ExcelRange cell)
{
//return (cell.Value is ExcelErrorValue || !string.IsNullOrEmpty(cell.Formula)) ? cell.Text : cell.Value;
return cell.Text;
}
/// <summary>
/// 读取合并
/// </summary>
/// <param name="sheet"></param>
/// <returns></returns>
private IEnumerable<EpMerge> GetMergeCells(ExcelWorksheet sheet)
{
var mergeCells = sheet.MergedCells.Select(merged =>
{
ExcelAddress address = new ExcelAddress(merged);
return new EpMerge
{
row = address.Start.Row - 1,
rowspan = address.End.Row - address.Start.Row + 1,
col = address.Start.Column - 1,
colspan = address.End.Column - address.Start.Column + 1,
};
});
return mergeCells;
}
}
}
\ No newline at end of file
using Newtonsoft.Json;
using OfficeOpenXml;
using Performance.DtoModels;
using Performance.EntityModels;
using Performance.Infrastructure;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Performance.Services.OnlineExcel
{
public partial class OnlineExcelService
{
public static Dictionary<Operation, Action<ExcelWorksheet, int, int>> OperationMapps = new Dictionary<Operation, Action<ExcelWorksheet, int, int>>
{
{ Operation.InsertRow, (sheet,from,count) => sheet.InsertRow(from, count, from + 1) },
{ Operation.DeleteRow, (sheet,from,count) => sheet.DeleteRow(from, count) },
{ Operation.InsertColumn, (sheet,from,count) => sheet.InsertColumn(from, count, from + 1) },
{ Operation.DeleteColumn, (sheet,from,count) => sheet.DeleteColumn(from, count) },
};
private List<string> GetColumns()
{
var columns = new List<string> { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
List<string> newColumns = new List<string>(columns);
foreach (var column in columns)
{
foreach (var item in columns)
{
newColumns.Add($"{column}{item}");
}
}
return newColumns;
}
public void WriteSheet(per_allot allot, EpChanage chanage)
{
FileInfo file = new FileInfo(allot.Path);
if (file.LastWriteTimeUtc.ToTimeStamp().ToString() != chanage.Version)
throw new PerformanceException("您读取的文件已被其他人更改");
using (ExcelPackage package = new ExcelPackage(file))
{
foreach (var sheet in package.Workbook.Worksheets)
{
if (sheet.Name != chanage.SheetName) continue;
// 新增删除 行 列 信息
if (chanage.OperationRecord != null && chanage.OperationRecord.Length > 0)
{
foreach (var item in chanage.OperationRecord.Where(w => w.Count > 0).OrderBy(w => w.DateTime))
{
OperationMapps[item.Operation].Invoke(sheet, item.From, item.Count);
}
}
// 写入数据
var columns = GetColumns();
for (int row = 0; row < chanage.Data.Count; row++)
{
var tempData = JsonConvert.DeserializeObject<Dictionary<string, object>>(JsonConvert.SerializeObject(chanage.Data[row]));
foreach (var key in tempData.Keys)
{
var col = columns.IndexOf(key);
var cell = sheet.Cells[row + 1, col + 1];
if (!(cell.Value is ExcelErrorValue) && string.IsNullOrEmpty(cell.Formula))
cell.Value = tempData[key];
}
}
_cache.Remove($"SheetData-{chanage.SheetName}:{allot.Path}");
}
package.Save();
}
}
}
}
\ No newline at end of file
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<PackageReference Include="FluentScheduler" Version="5.5.1" /> <PackageReference Include="FluentScheduler" Version="5.5.1" />
<PackageReference Include="GraphQL" Version="2.4.0" /> <PackageReference Include="GraphQL" Version="2.4.0" />
<PackageReference Include="MassTransit.AspNetCore" Version="7.2.4" /> <PackageReference Include="MassTransit.AspNetCore" Version="7.2.4" />
<PackageReference Include="Polly" Version="7.2.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment