MonthlyPsMatrixExport.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. <?php
  2. namespace App\Exports;
  3. use Maatwebsite\Excel\Concerns\WithMultipleSheets;
  4. use Maatwebsite\Excel\Concerns\WithEvents;
  5. use Maatwebsite\Excel\Concerns\WithTitle;
  6. use Maatwebsite\Excel\Events\AfterSheet;
  7. use PhpOffice\PhpSpreadsheet\Style\Alignment;
  8. use PhpOffice\PhpSpreadsheet\Style\Border;
  9. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  10. // 1. 主导出类:负责拆分年份和分发 Sheet
  11. class MonthlyPsMatrixExport implements WithMultipleSheets
  12. {
  13. protected $yearlyData;
  14. public function __construct(array $yearlyData)
  15. {
  16. $this->yearlyData = $yearlyData;
  17. }
  18. /**
  19. * 系统会自动识别这个方法,有多少个年份就自动创建多少个 Sheet
  20. */
  21. public function sheets(): array
  22. {
  23. $sheets = [];
  24. foreach ($this->yearlyData as $year => $employeeRows) {
  25. // 将每一年独立的数据和年份名称传给子 Sheet 类
  26. $sheets[] = new SingleYearPsMatrixSheet($year, $employeeRows);
  27. }
  28. return $sheets;
  29. }
  30. }
  31. // 2. 子 Sheet 类:独立负责单张年份表的样式和数据填充
  32. class SingleYearPsMatrixSheet implements WithEvents, WithTitle
  33. {
  34. protected $year;
  35. protected $employeeRows;
  36. public function __construct($year, array $employeeRows)
  37. {
  38. $this->year = $year;
  39. $this->employeeRows = $employeeRows;
  40. }
  41. /**
  42. * 定义当前 Sheet 页的标签名称
  43. */
  44. public function title(): string
  45. {
  46. return $this->year . '年';
  47. }
  48. /**
  49. * 编写当前单张表的渲染逻辑
  50. */
  51. public function registerEvents(): array
  52. {
  53. return [
  54. AfterSheet::class => function (AfterSheet $event) {
  55. $sheet = $event->sheet->getDelegate();
  56. // 🆕 细节修改 1:去掉了工号列后,总列数减少到 14 列。
  57. // A列(姓名) + B到M列(1-12月) = 13列,所以“年度合计”刚好是第 14 列,即 N 列。
  58. $lastColLetter = 'N';
  59. $dataCount = count($this->employeeRows);
  60. $lastRow = 2 + $dataCount; // 表头占2行
  61. // --- 1. 第一行:主标题 ---
  62. $sheet->mergeCells("A1:{$lastColLetter}1");
  63. $sheet->setCellValue('A1', "{$this->year}年 员工月份薪资明细");
  64. $sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
  65. // --- 2. 第二行:表头设置 ---
  66. $sheet->setCellValue('A2', '人员/月份');
  67. // 循环生成 1月 到 12月 的列头
  68. for ($m = 1; $m <= 12; $m++) {
  69. // 🆕 细节修改 2:A列是姓名(1),1月应该从 B列(2) 开始。所以是 $m + 1
  70. $colLetter = Coordinate::stringFromColumnIndex($m + 1);
  71. $sheet->setCellValue("{$colLetter}2", $m . '月');
  72. }
  73. // 🆕 细节修改 3:显式写入最后一列“年度合计”
  74. $sheet->setCellValue("{$lastColLetter}2", '年度合计');
  75. $sheet->getStyle("A2:{$lastColLetter}2")->getFont()->setBold(true);
  76. // --- 3. 第三行开始:填充矩阵数据 ---
  77. $currentRow = 3;
  78. foreach ($this->employeeRows as $empKey => $monthsData) {
  79. $empInfo = explode('_', $empKey);
  80. $empName = $empInfo[1] ?? ($empInfo[0] ?? ''); // 预防万一,如果切不出姓名则用工号兜底
  81. // 🆕 细节修改 4:这一行数据从 A 列开始,第一个格子放姓名
  82. $rowData = [$empName];
  83. $rowYearTotal = 0;
  84. for ($m = 1; $m <= 12; $m++) {
  85. $salary = $monthsData[$m] ?? 0;
  86. if ($salary > 0) {
  87. $rowData[] = $salary;
  88. $rowYearTotal += $salary;
  89. } else {
  90. $rowData[] = '-'; // 空白月用 '-' 占位
  91. }
  92. }
  93. // 追加年度合计
  94. $rowData[] = $rowYearTotal;
  95. // 将一整行数组直接写入当前行的 A 列起始位置
  96. $sheet->fromArray($rowData, null, "A{$currentRow}");
  97. $currentRow++;
  98. }
  99. // --- 4. 全局对齐与样式 ---
  100. $fullRange = "A1:{$lastColLetter}{$lastRow}";
  101. $sheet->getStyle($fullRange)->getAlignment()->applyFromArray([
  102. 'vertical' => Alignment::VERTICAL_CENTER,
  103. 'horizontal' => Alignment::HORIZONTAL_CENTER,
  104. 'wrapText' => true,
  105. ]);
  106. // --- 5. 行高控制 ---
  107. $sheet->getRowDimension(1)->setRowHeight(35); // 主标题高度
  108. $sheet->getRowDimension(2)->setRowHeight(30); // 表头高度
  109. // --- 6. 列宽自适应调节 ---
  110. // 🆕 细节修改 5:range范围自动变成了 A 到 N,不会多出空白列
  111. foreach (range('A', $lastColLetter) as $col) {
  112. $sheet->getColumnDimension($col)->setAutoSize(true);
  113. }
  114. // --- 7. 渲染全边框 ---
  115. $sheet->getStyle("A2:{$lastColLetter}{$lastRow}")
  116. ->getBorders()
  117. ->getAllBorders()
  118. ->setBorderStyle(Border::BORDER_THIN);
  119. },
  120. ];
  121. }
  122. }