180 lines
6.6 KiB
TypeScript
180 lines
6.6 KiB
TypeScript
|
|
import * as ExcelJS from 'exceljs';
|
|||
|
|
import { EliteFormData } from '@fenghuo/common';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 导出精英表单为 Excel 文件
|
|||
|
|
* @param data 表格数据
|
|||
|
|
* @param columns 列头
|
|||
|
|
* @param filename 文件名(可选)
|
|||
|
|
*/
|
|||
|
|
export async function exportEliteFormToExcel(
|
|||
|
|
data: EliteFormData[],
|
|||
|
|
columns: string[],
|
|||
|
|
filename?: string
|
|||
|
|
): Promise<void> {
|
|||
|
|
try {
|
|||
|
|
// 创建工作簿
|
|||
|
|
const workbook = new ExcelJS.Workbook();
|
|||
|
|
const worksheet = workbook.addWorksheet('骨干名册');
|
|||
|
|
|
|||
|
|
// 设置列定义
|
|||
|
|
worksheet.columns = [
|
|||
|
|
{ header: '序号', key: 'sequence', width: 8 },
|
|||
|
|
{ header: '专业', key: 'profession', width: 15 },
|
|||
|
|
{ header: '台站', key: 'station', width: 20 },
|
|||
|
|
{ header: '技师(值班代号)', key: 'technician', width: 25 },
|
|||
|
|
{ header: '领班员/台站长(值班代号)', key: 'supervisor', width: 28 },
|
|||
|
|
{ header: '值机员/值班员(值班代号)', key: 'operator', width: 25 }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 设置表头样式
|
|||
|
|
const headerRow = worksheet.getRow(1);
|
|||
|
|
headerRow.height = 25;
|
|||
|
|
headerRow.eachCell((cell) => {
|
|||
|
|
// 移除背景色
|
|||
|
|
cell.font = {
|
|||
|
|
size: 10,
|
|||
|
|
name: '黑体'
|
|||
|
|
};
|
|||
|
|
cell.alignment = {
|
|||
|
|
vertical: 'middle',
|
|||
|
|
horizontal: 'center'
|
|||
|
|
};
|
|||
|
|
cell.border = {
|
|||
|
|
top: { style: 'thin' },
|
|||
|
|
left: { style: 'thin' },
|
|||
|
|
bottom: { style: 'thin' },
|
|||
|
|
right: { style: 'thin' }
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 按专业分组数据
|
|||
|
|
const groupedData = new Map<string, EliteFormData[]>();
|
|||
|
|
data.forEach(row => {
|
|||
|
|
const profession = row.profession;
|
|||
|
|
if (!groupedData.has(profession)) {
|
|||
|
|
groupedData.set(profession, []);
|
|||
|
|
}
|
|||
|
|
const professionRows = groupedData.get(profession);
|
|||
|
|
if (professionRows) {
|
|||
|
|
professionRows.push(row);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加数据行并处理合并单元格
|
|||
|
|
let currentRow = 2; // 从第2行开始(第1行是表头)
|
|||
|
|
let sequenceNumber = 1;
|
|||
|
|
|
|||
|
|
// 辅助函数:根据职务等级生成星星
|
|||
|
|
const getStars = (dutyLevel: number): string => {
|
|||
|
|
return '★'.repeat(dutyLevel);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 辅助函数:格式化人员信息
|
|||
|
|
const formatPersonInfo = (person: { name: string; dutyCode: string; dutyLevel: number }): string => {
|
|||
|
|
const stars = person.dutyLevel > 0 ? getStars(person.dutyLevel) : '';
|
|||
|
|
return `${person.name}(${person.dutyCode})${stars}`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
for (const [profession, rows] of groupedData) {
|
|||
|
|
const startRow = currentRow;
|
|||
|
|
|
|||
|
|
// 添加该专业的所有行
|
|||
|
|
for (let i = 0; i < rows.length; i++) {
|
|||
|
|
const row = rows[i];
|
|||
|
|
if (!row) continue; // 跳过undefined的行
|
|||
|
|
|
|||
|
|
const excelRow = worksheet.addRow({
|
|||
|
|
sequence: i === 0 ? sequenceNumber : '', // 只在第一行显示序号
|
|||
|
|
profession: i === 0 ? profession : '', // 只在第一行显示专业名称
|
|||
|
|
station: row.parentOrganization ? `${row.parentOrganization}${row.station}` : row.station,
|
|||
|
|
technician: row.technicians.map(formatPersonInfo).join('、') || '',
|
|||
|
|
supervisor: row.supervisors.map(formatPersonInfo).join('、') || '',
|
|||
|
|
operator: row.operators.map(formatPersonInfo).join('、') || ''
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 设置行高(根据内容自动调整)
|
|||
|
|
const maxLines = Math.max(
|
|||
|
|
row.technicians.length || 1,
|
|||
|
|
row.supervisors.length || 1,
|
|||
|
|
row.operators.length || 1
|
|||
|
|
);
|
|||
|
|
excelRow.height = Math.max(40, maxLines * 20);
|
|||
|
|
|
|||
|
|
// 设置单元格样式
|
|||
|
|
excelRow.eachCell((cell, colNumber) => {
|
|||
|
|
cell.alignment = {
|
|||
|
|
vertical: 'middle',
|
|||
|
|
horizontal: colNumber <= 3 ? 'center' : 'left', // 序号、专业、台站居中,其他左对齐
|
|||
|
|
wrapText: true
|
|||
|
|
};
|
|||
|
|
cell.border = {
|
|||
|
|
top: { style: 'thin' },
|
|||
|
|
left: { style: 'thin' },
|
|||
|
|
bottom: { style: 'thin' },
|
|||
|
|
right: { style: 'thin' }
|
|||
|
|
};
|
|||
|
|
cell.font = {
|
|||
|
|
name: '仿宋GB2312',
|
|||
|
|
size: 10
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
currentRow++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果该专业有多行,合并序号和专业单元格
|
|||
|
|
if (rows.length > 1) {
|
|||
|
|
const endRow = currentRow - 1;
|
|||
|
|
|
|||
|
|
// 合并序号列
|
|||
|
|
worksheet.mergeCells(startRow, 1, endRow, 1);
|
|||
|
|
|
|||
|
|
// 合并专业列
|
|||
|
|
worksheet.mergeCells(startRow, 2, endRow, 2);
|
|||
|
|
|
|||
|
|
// 设置合并后单元格的对齐方式和字体
|
|||
|
|
worksheet.getCell(startRow, 1).alignment = {
|
|||
|
|
vertical: 'middle',
|
|||
|
|
horizontal: 'center'
|
|||
|
|
};
|
|||
|
|
worksheet.getCell(startRow, 1).font = {
|
|||
|
|
name: '仿宋GB2312',
|
|||
|
|
size: 11
|
|||
|
|
};
|
|||
|
|
worksheet.getCell(startRow, 2).alignment = {
|
|||
|
|
vertical: 'middle',
|
|||
|
|
horizontal: 'center'
|
|||
|
|
};
|
|||
|
|
worksheet.getCell(startRow, 2).font = {
|
|||
|
|
name: '仿宋GB2312',
|
|||
|
|
size: 11
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sequenceNumber++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成文件并下载
|
|||
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
|||
|
|
const blob = new Blob([buffer], {
|
|||
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const defaultFilename = `骨干名册_${new Date().toISOString()}.xlsx`;
|
|||
|
|
|
|||
|
|
// 创建下载链接
|
|||
|
|
const url = window.URL.createObjectURL(blob);
|
|||
|
|
const link = document.createElement('a');
|
|||
|
|
link.href = url;
|
|||
|
|
link.download = filename || defaultFilename;
|
|||
|
|
document.body.appendChild(link);
|
|||
|
|
link.click();
|
|||
|
|
document.body.removeChild(link);
|
|||
|
|
window.URL.revokeObjectURL(url);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('导出Excel失败:', error);
|
|||
|
|
throw new Error('导出Excel失败,请稍后重试');
|
|||
|
|
}
|
|||
|
|
}
|