ResearchExpenseSheetExport.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <?php
  2. namespace App\Exports;
  3. use Maatwebsite\Excel\Concerns\FromCollection;
  4. use Maatwebsite\Excel\Concerns\WithEvents;
  5. use Maatwebsite\Excel\Concerns\WithStyles;
  6. use Maatwebsite\Excel\Concerns\WithTitle;
  7. use Maatwebsite\Excel\Concerns\WithCustomStartCell;
  8. use Maatwebsite\Excel\Events\AfterSheet;
  9. use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  10. use PhpOffice\PhpSpreadsheet\Style\Alignment;
  11. use PhpOffice\PhpSpreadsheet\Style\Border;
  12. use PhpOffice\PhpSpreadsheet\Style\Fill;
  13. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  14. class ResearchExpenseSheetExport implements FromCollection, WithEvents, WithStyles, WithCustomStartCell, WithTitle
  15. {
  16. protected $sheetTitle;
  17. protected $data;
  18. protected $projectInfo;
  19. protected $year;
  20. protected $dynamicHeaders;
  21. public function __construct(string $sheetTitle, array $data, array $projectInfo = [], array $dynamicHeaders = [], string $year)
  22. {
  23. $this->sheetTitle = $sheetTitle;
  24. $this->data = $data;
  25. $this->projectInfo = $projectInfo;
  26. $this->year = $year;
  27. $this->dynamicHeaders = $dynamicHeaders; // 此时这里已经包含了排好序的 人员、折旧 及所有动态科目
  28. }
  29. public function title(): string { return $this->sheetTitle; }
  30. public function startCell(): string { return 'A7'; }
  31. public function collection() { return collect($this->data); }
  32. public function registerEvents(): array
  33. {
  34. return [
  35. AfterSheet::class => function(AfterSheet $event) {
  36. $sheet = $event->sheet->getDelegate();
  37. // --- 1. 计算精准列信息 ---
  38. $baseColumnCount = 6; // A-F (固定前 6 列:从 日期 到 税法规定的归集金额)
  39. $dynamicCount = count($this->dynamicHeaders); // 包含排序后的人员、折旧与所有动态科目数
  40. $totalColumnCount = $baseColumnCount + $dynamicCount + 2; // 再加上尾部委托研发 2 列
  41. $highestColumn = Coordinate::stringFromColumnIndex($totalColumnCount);
  42. $highestRow = $sheet->getHighestRow(); // 获取最后一行(合计行)
  43. // --- 2. 样式初始化:防止灰色块和默认填充 ---
  44. $sheet->getStyle("A1:{$highestColumn}{$highestRow}")->getFill()->setFillType(Fill::FILL_NONE);
  45. // --- 3. 大标题 (A2) ---
  46. $sheet->mergeCells("A2:{$highestColumn}2");
  47. $sheet->setCellValue('A2', "{$this->year}年研发支出辅助账");
  48. $sheet->getStyle('A2')->applyFromArray([
  49. 'font' => ['size' => 16],
  50. 'alignment' => [
  51. 'horizontal' => Alignment::HORIZONTAL_CENTER,
  52. 'vertical' => Alignment::VERTICAL_CENTER,
  53. ],
  54. ]);
  55. // --- 4. 项目信息行 (A3) ---
  56. $sheet->mergeCells("A3:C3");
  57. $sheet->setCellValue('A3', '项目编号:' . ($this->projectInfo['code'] ?? ''));
  58. $sheet->mergeCells("D3:G3");
  59. $sheet->setCellValue('D3', '项目名称:' . ($this->projectInfo['name'] ?? ''));
  60. // 单位放在最右边一列
  61. $sheet->setCellValue($highestColumn . '3', '单位:元');
  62. $sheet->getStyle($highestColumn . '3')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
  63. // --- 5. 复杂表头绘制 (4-6行) ---
  64. // A-D 凭证信息
  65. $sheet->mergeCells("A4:D5");
  66. $sheet->setCellValue('A4', '凭证信息');
  67. $sheet->setCellValue('A6', '日期');
  68. $sheet->setCellValue('B6', '种类');
  69. $sheet->setCellValue('C6', '号数');
  70. $sheet->setCellValue('D6', '摘要');
  71. // E-F 金额固定列 (垂直合并)
  72. $sheet->mergeCells("E4:E6");
  73. $sheet->setCellValue('E4', "会计凭证记载\n金额");
  74. $sheet->mergeCells("F4:F6");
  75. $sheet->setCellValue('F4', "税法规定的归\n集金额");
  76. // G-末尾: 费用明细大总标题 (第7列到最后一列)
  77. $sheet->mergeCells("G4:{$highestColumn}4");
  78. $sheet->setCellValue('G4', '费用明细(税法规定)');
  79. // ================== 【核心调整:动态平铺所有科目】 ==================
  80. // 从第 7 列 (G列) 开始动态输出所有排好序的科目(包含人员人工、折旧以及其他动态科目)
  81. $currentColIdx = 7;
  82. foreach ($this->dynamicHeaders as $headerText) {
  83. $colLetter = Coordinate::stringFromColumnIndex($currentColIdx);
  84. // 纵向合并 5 和 6 行
  85. $sheet->mergeCells("{$colLetter}5:{$colLetter}6");
  86. $sheet->setCellValue("{$colLetter}5", $headerText);
  87. $currentColIdx++;
  88. }
  89. // 委托研发 (紧跟在所有排序科目后面的最后两列)
  90. $mCol = Coordinate::stringFromColumnIndex($currentColIdx);
  91. $nCol = Coordinate::stringFromColumnIndex($currentColIdx + 1);
  92. $sheet->mergeCells("{$mCol}5:{$nCol}5");
  93. $sheet->setCellValue("{$mCol}5", '委托研发费用');
  94. $sheet->setCellValue("{$mCol}6", "委托境内研发");
  95. $sheet->setCellValue("{$nCol}6", "委托境外研发");
  96. // ====================================================================
  97. // --- 6. 强制刷新表头样式 (边框和居中) ---
  98. $headerRange = "A4:{$highestColumn}6";
  99. $sheet->getStyle($headerRange)->applyFromArray([
  100. 'borders' => [
  101. 'allBorders' => ['borderStyle' => Border::BORDER_THIN],
  102. ],
  103. 'alignment' => [
  104. 'wrapText' => true,
  105. 'vertical' => Alignment::VERTICAL_CENTER,
  106. 'horizontal' => Alignment::HORIZONTAL_CENTER,
  107. ],
  108. ]);
  109. // --- 7. 合计行特殊处理 (合并 A-D 列) ---
  110. $sheet->mergeCells("A{$highestRow}:D{$highestRow}");
  111. $footerRange = "A{$highestRow}:{$highestColumn}{$highestRow}";
  112. $sheet->getStyle($footerRange)->applyFromArray([
  113. 'fill' => [
  114. 'fillType' => Fill::FILL_SOLID,
  115. 'startColor' => ['argb' => 'FFF2F2F2'], // 灰色背景区分
  116. ],
  117. 'borders' => [
  118. 'allBorders' => ['borderStyle' => Border::BORDER_THIN],
  119. ],
  120. ]);
  121. $sheet->getStyle("A{$highestRow}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
  122. // --- 8. 添加会计主管 (合计行下方空一行) ---
  123. $supervisorRow = $highestRow + 1;
  124. $sheet->setCellValue("A{$supervisorRow}", '会计主管:');
  125. $sheet->getStyle("A{$supervisorRow}")->applyFromArray([
  126. 'font' => ['size' => 10],
  127. 'alignment' => ['horizontal' => Alignment::HORIZONTAL_LEFT]
  128. ]);
  129. // 设置行高
  130. $sheet->getRowDimension('2')->setRowHeight(35);
  131. $sheet->getRowDimension('6')->setRowHeight(45);
  132. },
  133. ];
  134. }
  135. public function styles(Worksheet $sheet)
  136. {
  137. $highestRow = $sheet->getHighestRow();
  138. // 这里的总列数计算也同步保持一致逻辑
  139. $totalCol = 6 + count($this->dynamicHeaders) + 2;
  140. $highestCol = Coordinate::stringFromColumnIndex($totalCol);
  141. // 数据区域边框 (从第7行开始)
  142. $sheet->getStyle("A7:{$highestCol}{$highestRow}")->applyFromArray([
  143. 'borders' => [
  144. 'allBorders' => ['borderStyle' => Border::BORDER_THIN],
  145. ],
  146. 'alignment' => [
  147. 'vertical' => Alignment::VERTICAL_CENTER,
  148. 'horizontal' => Alignment::HORIZONTAL_CENTER,
  149. ],
  150. 'font' => ['name' => '宋体', 'size' => 10],
  151. ]);
  152. return [];
  153. }
  154. }