支持五级混编、稳定种子、容量预检、固定考点、逐科考场/座位及四种准考证号规则。
正式编排会事务性替换整场计划、准考证、座位和审计记录。 支持三级管理员范围读取、批量 HTML 准考证、准考证信息 Excel,以及校级考点桌贴、门贴和签名单。 核心实现见 [AdminArrangementPlanner.cs (line 7)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Administration/AdminArrangementPlanner.cs:7)、[AdminArrangementService.cs (line 11)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Administration/AdminArrangementService.cs:11) 和 [AdminArrangementExporter.cs (line 5)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Administration/AdminArrangementExporter.cs:5)。 新增开关 ADMIN_NATIVE_ARRANGEMENTS_ENABLED=true,说明已更新到 [MIGRATION.md (line 57)](C:/Users/BI/Documents/EIS-dotnet/MIGRATION.md:57)。 Excel 由 .NET 直接生成,使用 ClosedXML 0.105.0;该库支持服务端生成 Excel 2007+ 工作簿。ClosedXML NuGet 页面
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
using ClosedXML.Excel;
|
||||
|
||||
namespace Eis.Infrastructure.Administration;
|
||||
|
||||
internal static class AdminArrangementExporter
|
||||
{
|
||||
private static readonly string[] InformationHeaders =
|
||||
[
|
||||
"考生学校", "班级", "报名号", "姓名", "证件号码", "考试代码", "考试名称", "准考证号",
|
||||
"考点代码", "考点名称", "考点详细地址", "科目", "日期", "时间", "考试考场序号",
|
||||
"考场通用名称", "物理场地代码", "楼栋", "楼层", "座位号"
|
||||
];
|
||||
|
||||
public static byte[] AdmitCardInformation(
|
||||
IReadOnlyList<ArrangementExportRow> rows,
|
||||
string subtitle)
|
||||
{
|
||||
using var workbook = new XLWorkbook();
|
||||
var sheet = workbook.Worksheets.Add("准考证信息");
|
||||
ConfigureTable(
|
||||
sheet,
|
||||
$"准考证信息台账|{subtitle}",
|
||||
InformationHeaders,
|
||||
rows.Select(InformationValues).ToArray());
|
||||
return Save(workbook);
|
||||
}
|
||||
|
||||
public static byte[] CenterMaterials(
|
||||
IReadOnlyList<ArrangementExportRow> rows,
|
||||
string subtitle)
|
||||
{
|
||||
using var workbook = new XLWorkbook();
|
||||
var sorted = rows
|
||||
.OrderBy(item => item.CenterCode, StringComparer.Ordinal)
|
||||
.ThenBy(item => item.SubjectDate, StringComparer.Ordinal)
|
||||
.ThenBy(item => item.SubjectTime, StringComparer.Ordinal)
|
||||
.ThenBy(item => item.ExamRoomCode, StringComparer.Ordinal)
|
||||
.ThenBy(item => item.Seat, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
AddDeskStickers(workbook.Worksheets.Add("桌贴"), sorted);
|
||||
var doors = sorted
|
||||
.GroupBy(
|
||||
item => new
|
||||
{
|
||||
item.ExamCode,
|
||||
item.SubjectName,
|
||||
item.CenterCode,
|
||||
item.ExamRoomCode,
|
||||
item.RoomCode
|
||||
})
|
||||
.Select(group => (Row: group.First(), Count: group.Count()))
|
||||
.ToArray();
|
||||
ConfigureTable(
|
||||
workbook.Worksheets.Add("门贴"),
|
||||
$"考场门贴|{subtitle}",
|
||||
[
|
||||
"考试", "科目", "日期", "时间", "考点", "考试考场序号",
|
||||
"考场通用名称", "场地代码", "楼栋", "楼层", "人数"
|
||||
],
|
||||
doors.Select(item => new object[]
|
||||
{
|
||||
item.Row.ExamName,
|
||||
item.Row.SubjectName,
|
||||
item.Row.SubjectDate,
|
||||
item.Row.SubjectTime,
|
||||
item.Row.CenterName,
|
||||
item.Row.ExamRoomCode,
|
||||
item.Row.RoomName,
|
||||
item.Row.RoomCode,
|
||||
item.Row.Building,
|
||||
item.Row.Floor,
|
||||
item.Count
|
||||
}).ToArray());
|
||||
ConfigureTable(
|
||||
workbook.Worksheets.Add("考场签名单"),
|
||||
$"考场考生签名单|{subtitle}",
|
||||
[
|
||||
"考试", "科目", "日期", "时间", "考试考场序号", "考场通用名称",
|
||||
"楼栋", "楼层", "座位号", "准考证号", "姓名", "考生学校", "班级", "考生签名"
|
||||
],
|
||||
sorted.Select(item => new object[]
|
||||
{
|
||||
item.ExamName,
|
||||
item.SubjectName,
|
||||
item.SubjectDate,
|
||||
item.SubjectTime,
|
||||
item.ExamRoomCode,
|
||||
item.RoomName,
|
||||
item.Building,
|
||||
item.Floor,
|
||||
item.Seat,
|
||||
item.CardNumber,
|
||||
item.CandidateName,
|
||||
item.SchoolName,
|
||||
item.ClassName,
|
||||
""
|
||||
}).ToArray());
|
||||
return Save(workbook);
|
||||
}
|
||||
|
||||
private static void AddDeskStickers(
|
||||
IXLWorksheet sheet,
|
||||
IReadOnlyList<ArrangementExportRow> rows)
|
||||
{
|
||||
sheet.ShowGridLines = false;
|
||||
for (var column = 1; column <= 7; column++)
|
||||
sheet.Column(column).Width = column == 4 ? 3 : 14.5;
|
||||
for (var index = 0; index < rows.Count; index++)
|
||||
{
|
||||
var item = rows[index];
|
||||
var slot = index % 2;
|
||||
var group = index / 2;
|
||||
var startRow = group * 9 + 1;
|
||||
var startColumn = slot == 0 ? 1 : 5;
|
||||
var values = new[]
|
||||
{
|
||||
item.ExamName,
|
||||
$"{item.SubjectName}|{item.SubjectDate} {item.SubjectTime}",
|
||||
"考试考场序号",
|
||||
item.ExamRoomCode,
|
||||
$"座位号 {item.Seat}",
|
||||
$"{item.RoomName}(场地 {item.RoomCode})|{item.Building} {item.Floor}",
|
||||
$"{item.CandidateName}|{item.SchoolName} {item.ClassName}",
|
||||
$"{item.CenterName}|准考证号 {item.CardNumber}"
|
||||
};
|
||||
for (var offset = 0; offset < values.Length; offset++)
|
||||
{
|
||||
var range = sheet.Range(
|
||||
startRow + offset,
|
||||
startColumn,
|
||||
startRow + offset,
|
||||
startColumn + 2);
|
||||
range.Merge();
|
||||
range.Value = values[offset];
|
||||
range.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
||||
range.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
|
||||
range.Style.Alignment.WrapText = true;
|
||||
range.Style.Border.OutsideBorder = XLBorderStyleValues.Medium;
|
||||
range.Style.Border.InsideBorder = XLBorderStyleValues.Hair;
|
||||
sheet.Row(startRow + offset).Height = 28;
|
||||
}
|
||||
var title = sheet.Range(startRow, startColumn, startRow, startColumn + 2);
|
||||
title.Style.Fill.BackgroundColor = XLColor.FromHtml("#173F60");
|
||||
title.Style.Font.FontColor = XLColor.White;
|
||||
title.Style.Font.Bold = true;
|
||||
var room = sheet.Range(startRow + 3, startColumn, startRow + 3, startColumn + 2);
|
||||
room.Style.Font.FontSize = 24;
|
||||
room.Style.Font.Bold = true;
|
||||
var seat = sheet.Range(startRow + 4, startColumn, startRow + 4, startColumn + 2);
|
||||
seat.Style.Fill.BackgroundColor = XLColor.FromHtml("#FFF1C7");
|
||||
seat.Style.Font.FontColor = XLColor.FromHtml("#8A4B08");
|
||||
seat.Style.Font.FontSize = 18;
|
||||
seat.Style.Font.Bold = true;
|
||||
}
|
||||
sheet.PageSetup.PageOrientation = XLPageOrientation.Portrait;
|
||||
sheet.PageSetup.FitToPages(1, 0);
|
||||
sheet.PageSetup.Margins.SetLeft(0.25);
|
||||
sheet.PageSetup.Margins.SetRight(0.25);
|
||||
sheet.PageSetup.Margins.SetTop(0.25);
|
||||
sheet.PageSetup.Margins.SetBottom(0.25);
|
||||
}
|
||||
|
||||
private static void ConfigureTable(
|
||||
IXLWorksheet sheet,
|
||||
string title,
|
||||
IReadOnlyList<string> headers,
|
||||
IReadOnlyList<object[]> rows)
|
||||
{
|
||||
sheet.ShowGridLines = false;
|
||||
var lastColumn = headers.Count;
|
||||
var titleRange = sheet.Range(1, 1, 1, lastColumn);
|
||||
titleRange.Merge();
|
||||
titleRange.Value = title;
|
||||
titleRange.Style.Fill.BackgroundColor = XLColor.FromHtml("#173F60");
|
||||
titleRange.Style.Font.FontColor = XLColor.White;
|
||||
titleRange.Style.Font.FontSize = 16;
|
||||
titleRange.Style.Font.Bold = true;
|
||||
titleRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
|
||||
sheet.Row(1).Height = 28;
|
||||
for (var column = 0; column < headers.Count; column++)
|
||||
{
|
||||
var cell = sheet.Cell(2, column + 1);
|
||||
cell.Value = headers[column];
|
||||
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#2C7080");
|
||||
cell.Style.Font.FontColor = XLColor.White;
|
||||
cell.Style.Font.Bold = true;
|
||||
cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
||||
cell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
|
||||
cell.Style.Alignment.WrapText = true;
|
||||
}
|
||||
for (var rowIndex = 0; rowIndex < rows.Count; rowIndex++)
|
||||
{
|
||||
for (var column = 0; column < headers.Count; column++)
|
||||
{
|
||||
var value = column < rows[rowIndex].Length ? rows[rowIndex][column] : "";
|
||||
var cell = sheet.Cell(rowIndex + 3, column + 1);
|
||||
if (value is int integer) cell.Value = integer;
|
||||
else if (value is double number) cell.Value = number;
|
||||
else cell.Value = Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture) ?? "";
|
||||
cell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
|
||||
cell.Style.Alignment.WrapText = true;
|
||||
cell.Style.Border.BottomBorder = XLBorderStyleValues.Hair;
|
||||
cell.Style.Border.BottomBorderColor = XLColor.FromHtml("#D8E2E7");
|
||||
}
|
||||
}
|
||||
sheet.SheetView.FreezeRows(2);
|
||||
if (rows.Count > 0) sheet.Range(2, 1, rows.Count + 2, lastColumn).SetAutoFilter();
|
||||
sheet.Columns().AdjustToContents(8, 40);
|
||||
sheet.PageSetup.PageOrientation = XLPageOrientation.Landscape;
|
||||
sheet.PageSetup.FitToPages(1, 0);
|
||||
sheet.PageSetup.SetRowsToRepeatAtTop(1, 2);
|
||||
}
|
||||
|
||||
private static object[] InformationValues(ArrangementExportRow item) =>
|
||||
[
|
||||
item.SchoolName,
|
||||
item.ClassName,
|
||||
item.CandidateNumber,
|
||||
item.CandidateName,
|
||||
item.IdNumber,
|
||||
item.ExamCode,
|
||||
item.ExamName,
|
||||
item.CardNumber,
|
||||
item.CenterCode,
|
||||
item.CenterName,
|
||||
item.CenterAddress,
|
||||
item.SubjectName,
|
||||
item.SubjectDate,
|
||||
item.SubjectTime,
|
||||
item.ExamRoomCode,
|
||||
item.RoomName,
|
||||
item.RoomCode,
|
||||
item.Building,
|
||||
item.Floor,
|
||||
item.Seat
|
||||
];
|
||||
|
||||
private static byte[] Save(XLWorkbook workbook)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
workbook.SaveAs(stream);
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user