准考证优化

This commit is contained in:
2026-07-20 14:18:37 +08:00 Unverified
parent 9566542414
commit c5b18d0b15
13 changed files with 447 additions and 57 deletions
+142
View File
@@ -78,6 +78,18 @@ const resourceSpecs = {
],
validations: { published: ['发布', '不发布'] },
numberColumns: ['score']
},
admit_cards: {
title: '准考证信息台账', sheet: '准考证信息',
columns: [
['schoolName', '考生学校', 24, ''], ['className', '班级', 20, ''],
['candidateNumber', '报名号', 24, ''], ['candidateName', '姓名', 14, ''], ['idNumber', '证件号码', 22, ''],
['examCode', '考试代码', 18, ''], ['examName', '考试名称', 28, ''], ['cardNumber', '准考证号', 22, ''],
['centerCode', '考点代码', 16, ''], ['centerName', '考点名称', 26, ''], ['centerAddress', '考点详细地址', 38, ''],
['subjectName', '科目', 14, ''], ['subjectDate', '日期', 14, ''], ['subjectTime', '时间', 16, ''],
['examRoomCode', '考试考场序号', 16, ''], ['roomName', '考场通用名称', 20, ''], ['roomCode', '物理场地代码', 16, ''],
['building', '楼栋', 16, ''], ['floor', '楼层', 12, ''], ['seat', '座位号', 12, '']
]
}
};
@@ -173,3 +185,133 @@ export async function buildWorkbook(resource, rows = [], { template = false, sub
for (const col of requiredColumns) header.getCell(col).font = { name: '微软雅黑', bold: true, color: { argb: 'FFFFE7A3' } };
return workbook.xlsx.writeBuffer();
}
function addOperationalSheet(workbook, name, title, columns, rows, { landscape = true } = {}) {
const sheet = workbook.addWorksheet(name, { views: [{ state: 'frozen', ySplit: 2, showGridLines: false }] });
sheet.columns = columns.map(([key, , width]) => ({ key, width }));
sheet.mergeCells(1, 1, 1, columns.length);
const titleCell = sheet.getCell(1, 1);
titleCell.value = title;
titleCell.font = { name: '微软雅黑', size: 16, bold: true, color: { argb: 'FFFFFFFF' } };
titleCell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF173F60' } };
titleCell.alignment = { vertical: 'middle', horizontal: 'left' };
sheet.getRow(1).height = 34;
const header = sheet.getRow(2);
header.values = columns.map(([, label]) => label);
header.height = 28;
header.font = { name: '微软雅黑', bold: true, color: { argb: 'FFFFFFFF' } };
header.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF2C7080' } };
header.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
rows.forEach((item, index) => {
const row = sheet.addRow(Object.fromEntries(columns.map(([key]) => [key, item[key] ?? ''])));
row.height = 28;
row.font = { name: '微软雅黑', size: 10, color: { argb: 'FF243B4A' } };
row.alignment = { vertical: 'middle', wrapText: true };
if (index % 2 === 1) row.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF3F7F8' } };
row.eachCell(cell => { cell.border = { bottom: { style: 'hair', color: { argb: 'FFD8E2E7' } } }; });
});
sheet.autoFilter = { from: { row: 2, column: 1 }, to: { row: Math.max(2, sheet.rowCount), column: columns.length } };
sheet.pageSetup = { orientation: landscape ? 'landscape' : 'portrait', paperSize: 9, fitToPage: true, fitToWidth: 1, fitToHeight: 0, margins: { left: .25, right: .25, top: .4, bottom: .4, header: .2, footer: .2 } };
sheet.printTitlesRow = '1:2';
return sheet;
}
function addDeskStickerSheet(workbook, rows) {
const sheet = workbook.addWorksheet('桌贴', { views: [{ showGridLines: false, zoomScale: 85 }] });
const labelColumns = [14.5, 14.5, 14.5, 3, 14.5, 14.5, 14.5];
labelColumns.forEach((width, index) => { sheet.getColumn(index + 1).width = width; });
sheet.pageSetup = {
orientation: 'portrait', paperSize: 9, fitToPage: true, fitToWidth: 1, fitToHeight: 0,
margins: { left: .25, right: .25, top: .25, bottom: .25, header: 0, footer: 0 },
horizontalCentered: true, verticalCentered: false
};
sheet.headerFooter = { oddFooter: '&C第 &P / &N 页' };
const rowsPerLabel = 8;
const labelsPerPage = 6;
const rowsPerPage = rowsPerLabel * 3;
const borderColor = { argb: 'FF173F60' };
const paleBlue = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFEAF1F5' } };
const paleGold = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFF1C7' } };
const mergeLine = (row, startColumn) => {
sheet.mergeCells(row, startColumn, row, startColumn + 2);
return sheet.getCell(row, startColumn);
};
const styleLine = (cell, { size = 10, bold = false, color = 'FF243B4A', fill = null } = {}) => {
cell.font = { name: '微软雅黑', size, bold, color: { argb: color } };
cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true, shrinkToFit: true };
if (fill) cell.fill = fill;
};
rows.forEach((item, index) => {
const page = Math.floor(index / labelsPerPage);
const slot = index % labelsPerPage;
const startRow = page * rowsPerPage + Math.floor(slot / 2) * rowsPerLabel + 1;
const startColumn = slot % 2 === 0 ? 1 : 5;
for (let row = startRow; row < startRow + rowsPerLabel; row += 1) sheet.getRow(row).height = 32.5;
const cells = Array.from({ length: rowsPerLabel }, (_, offset) => mergeLine(startRow + offset, startColumn));
cells[0].value = item.examName;
styleLine(cells[0], { size: 11, bold: true, color: 'FFFFFFFF', fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF173F60' } } });
cells[1].value = `${item.subjectName}${item.subjectDate} ${item.subjectTime}`;
styleLine(cells[1], { size: 9, bold: true, fill: paleBlue });
cells[2].value = '考试考场序号';
styleLine(cells[2], { size: 9, bold: true, color: 'FF2C7080' });
cells[3].value = { richText: [{ text: String(item.examRoomCode || '') }] };
cells[3].numFmt = '@';
styleLine(cells[3], { size: 25, bold: true, color: 'FF173F60' });
cells[4].value = `座位号 ${item.seat}`;
styleLine(cells[4], { size: 20, bold: true, color: 'FF8A4B08', fill: paleGold });
cells[5].value = `${item.roomName}(场地 ${item.roomCode})|${item.building} ${item.floor}`;
styleLine(cells[5], { size: 9, bold: true });
cells[6].value = `${item.candidateName}${item.schoolName} ${item.className}`;
styleLine(cells[6], { size: 10, bold: true, fill: paleBlue });
cells[7].value = `${item.centerName}|准考证号 ${item.cardNumber}`;
styleLine(cells[7], { size: 8, color: 'FF526576' });
for (let row = startRow; row < startRow + rowsPerLabel; row += 1) {
for (let column = startColumn; column < startColumn + 3; column += 1) {
const cell = sheet.getCell(row, column);
cell.border = {
top: { style: row === startRow ? 'medium' : 'hair', color: borderColor },
bottom: { style: row === startRow + rowsPerLabel - 1 ? 'medium' : 'hair', color: borderColor },
left: { style: column === startColumn ? 'medium' : 'hair', color: borderColor },
right: { style: column === startColumn + 2 ? 'medium' : 'hair', color: borderColor }
};
}
}
if ((index + 1) % labelsPerPage === 0 && index + 1 < rows.length) sheet.getRow(startRow + rowsPerLabel - 1).addPageBreak();
});
if (rows.length) sheet.pageSetup.printArea = `A1:G${Math.ceil(rows.length / labelsPerPage) * rowsPerPage}`;
return sheet;
}
export async function buildCenterMaterialsWorkbook(rows, subtitle = '') {
const workbook = new ExcelJS.Workbook();
workbook.creator = '衡准考试信息管理系统';
workbook.created = new Date();
const sorted = [...rows].sort((a, b) => String(a.centerCode).localeCompare(String(b.centerCode))
|| String(a.subjectDate).localeCompare(String(b.subjectDate)) || String(a.subjectTime).localeCompare(String(b.subjectTime))
|| String(a.examRoomCode).localeCompare(String(b.examRoomCode)) || String(a.seat).localeCompare(String(b.seat)));
const baseTitle = subtitle ? `${subtitle}` : '';
addDeskStickerSheet(workbook, sorted);
const doorMap = new Map();
for (const item of sorted) {
const key = [item.examCode, item.subjectName, item.centerCode, item.examRoomCode, item.roomCode].join('|');
const current = doorMap.get(key) || { ...item, candidateCount: 0 };
current.candidateCount += 1;
doorMap.set(key, current);
}
addOperationalSheet(workbook, '门贴', `考场门贴${baseTitle}`, [
['examName', '考试', 28], ['subjectName', '科目', 14], ['subjectDate', '日期', 14], ['subjectTime', '时间', 16],
['centerName', '考点', 24], ['examRoomCode', '考试考场序号', 16], ['roomName', '考场通用名称', 20],
['roomCode', '场地代码', 14], ['building', '楼栋', 16], ['floor', '楼层', 10], ['candidateCount', '人数', 10]
], [...doorMap.values()]);
addOperationalSheet(workbook, '考场签名单', `考场考生签名单${baseTitle}`, [
['examName', '考试', 26], ['subjectName', '科目', 14], ['subjectDate', '日期', 14], ['subjectTime', '时间', 16],
['examRoomCode', '考试考场序号', 16], ['roomName', '考场通用名称', 20], ['building', '楼栋', 16], ['floor', '楼层', 10],
['seat', '座位号', 10], ['cardNumber', '准考证号', 22], ['candidateName', '姓名', 14],
['schoolName', '考生学校', 24], ['className', '班级', 18], ['signature', '考生签名', 18]
], sorted.map(item => ({ ...item, signature: '' })));
return workbook.xlsx.writeBuffer();
}