1 | <?php |
||||||
2 | /** |
||||||
3 | * @name: TWorklSheet |
||||||
4 | * @author: JiaMeng <[email protected]> |
||||||
5 | * @file: Export.php |
||||||
6 | * @Date: 2024/03/04 10:15 |
||||||
7 | */ |
||||||
8 | namespace tinymeng\spreadsheet\Excel; |
||||||
9 | |||||||
10 | use PhpOffice\PhpSpreadsheet\Cell\DataType; |
||||||
11 | use tinymeng\spreadsheet\Util\ConstCode; |
||||||
12 | use tinymeng\tools\exception\StatusCode; |
||||||
13 | use tinymeng\tools\exception\TinymengException; |
||||||
14 | |||||||
15 | trait TWorkSheet{ |
||||||
16 | |||||||
17 | /** |
||||||
18 | * sheet名称 |
||||||
19 | * @var |
||||||
20 | */ |
||||||
21 | private $sheetName; |
||||||
22 | /** |
||||||
23 | * 查询数据 |
||||||
24 | * @var |
||||||
25 | */ |
||||||
26 | private $data; |
||||||
27 | |||||||
28 | /** |
||||||
29 | * 定义默认列数 |
||||||
30 | * @var int |
||||||
31 | */ |
||||||
32 | private $_col = 0; |
||||||
33 | /** |
||||||
34 | * 定义当前行数 |
||||||
35 | * @var int |
||||||
36 | */ |
||||||
37 | private $_row = 1; |
||||||
38 | /** |
||||||
39 | * 定义所有字段 |
||||||
40 | * @var array |
||||||
41 | */ |
||||||
42 | private $field = []; |
||||||
43 | |||||||
44 | /** |
||||||
45 | * 文件信息 |
||||||
46 | * @var array |
||||||
47 | */ |
||||||
48 | private $fileTitle=[]; |
||||||
49 | |||||||
50 | |||||||
51 | /** |
||||||
52 | * 左侧分组字段 |
||||||
53 | * @var array |
||||||
54 | */ |
||||||
55 | private $group_left = []; |
||||||
56 | |||||||
57 | |||||||
58 | /** |
||||||
59 | * 获取sheet表格数目 |
||||||
60 | * @var |
||||||
61 | */ |
||||||
62 | private $sheetCount = 1; |
||||||
63 | |||||||
64 | /** |
||||||
65 | * 字段映射方式 |
||||||
66 | * @var int |
||||||
67 | */ |
||||||
68 | private $fieldMappingMethod = ConstCode::FIELD_MAPPING_METHOD_FIELD_CORRESPONDING_NAME; |
||||||
69 | |||||||
70 | /** |
||||||
71 | * 需要自动合并的字段 |
||||||
72 | * @var array |
||||||
73 | */ |
||||||
74 | private $mergeColumns = []; |
||||||
75 | |||||||
76 | /** |
||||||
77 | * 小计行样式 |
||||||
78 | * @var array |
||||||
79 | */ |
||||||
80 | private $subtotalStyle = []; |
||||||
81 | /** |
||||||
82 | * 全表样式 |
||||||
83 | * @var array |
||||||
84 | */ |
||||||
85 | private $sheetStyle = []; |
||||||
86 | |||||||
87 | /** |
||||||
88 | * 用户自定义表格操作回调 |
||||||
89 | * @var callable|null |
||||||
90 | */ |
||||||
91 | private $complexFormatCallback = null; |
||||||
92 | /** |
||||||
93 | * @var array |
||||||
94 | */ |
||||||
95 | private $titleConfig = []; |
||||||
96 | |||||||
97 | /** |
||||||
98 | * @param $data |
||||||
99 | * @return $this |
||||||
100 | */ |
||||||
101 | public function setData($data){ |
||||||
102 | $this->data = $data; |
||||||
103 | return $this; |
||||||
104 | } |
||||||
105 | |||||||
106 | /** |
||||||
107 | * @param $data |
||||||
108 | * @return $this |
||||||
109 | */ |
||||||
110 | public function getData(){ |
||||||
111 | return $this->data; |
||||||
112 | } |
||||||
113 | |||||||
114 | |||||||
115 | /** |
||||||
116 | * @return void |
||||||
117 | * @throws \PhpOffice\PhpSpreadsheet\Exception |
||||||
118 | */ |
||||||
119 | public function initWorkSheet() |
||||||
120 | { |
||||||
121 | $this->_col = 0; |
||||||
122 | $this->_row = 1; |
||||||
123 | $this->fileTitle = []; |
||||||
124 | $this->data = []; |
||||||
125 | $this->field = []; |
||||||
126 | if($this->freezePane) $this->workSheet->freezePane($this->freezePane); //冻结窗格 |
||||||
127 | } |
||||||
128 | |||||||
129 | /** |
||||||
130 | * @param $fileTitle |
||||||
131 | * @param $data |
||||||
132 | * @return $this |
||||||
133 | * @throws TinymengException |
||||||
134 | * @throws \PhpOffice\PhpSpreadsheet\Exception |
||||||
135 | */ |
||||||
136 | public function setWorkSheetData($titleConfig,$data) |
||||||
137 | { |
||||||
138 | $this->titleConfig = $titleConfig; |
||||||
139 | if(isset($titleConfig['title_row']) || isset($titleConfig['group_left'])){ |
||||||
140 | /** |
||||||
141 | * $titleConfig = [ |
||||||
142 | * 'title_row'=>1, |
||||||
143 | * 'group_left'=>[], |
||||||
144 | * 'title'=>[ |
||||||
145 | * '姓名'=>'name' |
||||||
146 | * ], |
||||||
147 | * ]; |
||||||
148 | */ |
||||||
149 | $this->title_row = $titleConfig['title_row']??1; |
||||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||||
150 | $this->group_left = $titleConfig['group_left']??[]; |
||||||
151 | $titleData = $titleConfig['title']??[]; |
||||||
152 | // 新增:读取mergeColumns配置 |
||||||
153 | if (isset($titleConfig['mergeColumns'])) { |
||||||
154 | $this->mergeColumns = $titleConfig['mergeColumns']; |
||||||
155 | } |
||||||
156 | }else{ |
||||||
157 | /** |
||||||
158 | * $titleConfig = [ |
||||||
159 | * '姓名'=>'name', |
||||||
160 | * ]; |
||||||
161 | */ |
||||||
162 | $titleData = $titleConfig; |
||||||
163 | } |
||||||
164 | // 根据字段映射方式处理 title |
||||||
165 | if ($this->fieldMappingMethod === ConstCode::FIELD_MAPPING_METHOD_FIELD_CORRESPONDING_NAME) { |
||||||
166 | $this->fileTitle = array_flip($titleData);// 字段对应名称方式 - 需要将键值对调 |
||||||
167 | }else{ |
||||||
168 | $this->fileTitle = $titleData;// 名称对应字段方式 - 保持原样 |
||||||
169 | } |
||||||
170 | $this->data = $data; |
||||||
171 | |||||||
172 | /** 设置第一行格式 */ |
||||||
173 | if(!empty($this->mainTitle)){ |
||||||
174 | $this->excelHeader(); |
||||||
175 | $this->_row ++;//当前行数 |
||||||
176 | } |
||||||
177 | |||||||
178 | /** 设置表头 **/ |
||||||
179 | $this->excelTitle(); |
||||||
180 | |||||||
181 | /** 获取列表里所有字段 **/ |
||||||
182 | foreach ($this->fileTitle as $key => $val){ |
||||||
183 | if(is_array($val)){ |
||||||
184 | foreach ($val as $k => $v){ |
||||||
185 | $this->field[] = $v; |
||||||
186 | } |
||||||
187 | }else{ |
||||||
188 | $this->field[] = $val; |
||||||
189 | } |
||||||
190 | } |
||||||
191 | /** 查询结果赋值 **/ |
||||||
192 | if(!empty($this->data)){ |
||||||
193 | $this->excelSetValue(); |
||||||
194 | } |
||||||
195 | // 新增:应用全表样式 |
||||||
196 | $this->applySheetStyle(); |
||||||
197 | // 新增:调用自定义表格操作回调 |
||||||
198 | if (is_callable($this->complexFormatCallback)) { |
||||||
199 | call_user_func($this->complexFormatCallback, $this->workSheet); |
||||||
0 ignored issues
–
show
It seems like
$this->complexFormatCallback can also be of type null ; however, parameter $callback of call_user_func() does only seem to accept callable , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
200 | } |
||||||
201 | return $this; |
||||||
202 | } |
||||||
203 | |||||||
204 | /** |
||||||
205 | * excelSetValue |
||||||
206 | * @author: Tinymeng <[email protected]> |
||||||
207 | * @time: 2022/2/22 11:43 |
||||||
208 | */ |
||||||
209 | public function excelSetValue(){ |
||||||
210 | if(!empty($this->titleConfig['data_start_row'])){ |
||||||
211 | $this->_row = $this->titleConfig['data_start_row']; |
||||||
212 | } |
||||||
213 | |||||||
214 | if(empty($this->group_left)){ //判断左侧是否分组 |
||||||
215 | $rowStart = $this->_row; |
||||||
216 | foreach ($this->data as $key => $val){ |
||||||
217 | $this->excelSetCellValue($val); |
||||||
218 | } |
||||||
219 | // 新增:处理mergeColumns自动合并 |
||||||
220 | if (!empty($this->mergeColumns)) { |
||||||
221 | $this->autoMergeColumns($rowStart, $this->_row - 1); |
||||||
222 | } |
||||||
223 | }else{ //根据设置分组字段进行分组 |
||||||
224 | /** 数据分组 **/ |
||||||
225 | $data = []; |
||||||
226 | $group_left_count = count($this->group_left); |
||||||
227 | if($group_left_count == 1){ |
||||||
228 | foreach ($this->data as $k => $v){ |
||||||
229 | if(isset($v[$this->group_left[0]])){ |
||||||
230 | $data[$v[$this->group_left[0]]][] = $v; |
||||||
231 | } |
||||||
232 | } |
||||||
233 | foreach ($data as $k =>$v){ |
||||||
234 | $data[$k] = [ |
||||||
235 | 'data' => $v, |
||||||
236 | 'count' => count($v) |
||||||
237 | ]; |
||||||
238 | } |
||||||
239 | $this->excelGroupLeft($data, $group_left_count); |
||||||
240 | }elseif($group_left_count == 2){ |
||||||
241 | foreach ($this->data as $v) { |
||||||
242 | if(isset($v[$this->group_left[0]]) && isset($v[$this->group_left[1]])){ |
||||||
243 | $data[$v[$this->group_left[0]]][$v[$this->group_left[1]]][] = $v; |
||||||
244 | } |
||||||
245 | } |
||||||
246 | $this->data = $this->arrayCount($data); |
||||||
247 | $this->excelGroupLeft($this->data, $group_left_count); |
||||||
248 | }else{ |
||||||
249 | throw new TinymengException(StatusCode::COMMON_PARAM_INVALID, |
||||||
250 | '左侧分组过多,导出失败!' |
||||||
251 | ); |
||||||
252 | } |
||||||
253 | } |
||||||
254 | } |
||||||
255 | |||||||
256 | /** |
||||||
257 | * @return void |
||||||
258 | * @throws \PhpOffice\PhpSpreadsheet\Exception |
||||||
259 | */ |
||||||
260 | public function excelHeader(){ |
||||||
261 | $row = 1; |
||||||
262 | if(!empty($this->mainTitle)){ |
||||||
263 | $this->workSheet->setCellValue('A'.$row, $this->mainTitle); |
||||||
264 | } |
||||||
265 | |||||||
266 | // 计算实际的标题列数 |
||||||
267 | $titleCount = 0; |
||||||
268 | foreach ($this->fileTitle as $val) { |
||||||
269 | if (is_array($val)) { |
||||||
270 | $titleCount += count($val); // 如果是数组,加上子项的数量 |
||||||
271 | } else { |
||||||
272 | $titleCount++; // 如果是单个标题,加1 |
||||||
273 | } |
||||||
274 | } |
||||||
275 | |||||||
276 | // 使用实际的标题列数来合并单元格 |
||||||
277 | $this->workSheet->mergeCells('A'.$row.':'.$this->cellName($titleCount-1).$row); |
||||||
0 ignored issues
–
show
It seems like
cellName() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
278 | } |
||||||
279 | |||||||
280 | /** |
||||||
281 | * @return void |
||||||
282 | * @throws \PhpOffice\PhpSpreadsheet\Exception |
||||||
283 | */ |
||||||
284 | private function excelTitle(){ |
||||||
285 | if(!empty($this->titleConfig['title_start_row'])){ |
||||||
286 | $this->_row = $this->titleConfig['title_start_row']; |
||||||
287 | } |
||||||
288 | |||||||
289 | $_merge = $this->cellName($this->_col); |
||||||
290 | foreach ($this->fileTitle as $key => $val) { |
||||||
291 | if(!empty($this->titleHeight)) { |
||||||
292 | $this->workSheet->getRowDimension($this->_col)->setRowHeight($this->titleHeight);//行高度 |
||||||
293 | } |
||||||
294 | $rowName = $this->cellName($this->_col); |
||||||
295 | $this->workSheet->getStyle($rowName . $this->_row)->getAlignment()->setWrapText(true);//自动换行 |
||||||
296 | if (is_array($val)) { |
||||||
297 | $num = 1; |
||||||
298 | $_cols = $this->_col; |
||||||
299 | foreach ($val as $k => $v) { |
||||||
300 | if(!isset($this->titleConfig['title_show']) || $this->titleConfig['title_show']!==false) { |
||||||
301 | $this->workSheet->setCellValue($this->cellName($_cols) . ($this->_row+1), $k); |
||||||
302 | } |
||||||
303 | if(!empty($this->titleWidth)) { |
||||||
304 | $this->workSheet->getColumnDimension($this->cellName($_cols))->setWidth($this->titleWidth); //列宽度 |
||||||
305 | }else{ |
||||||
306 | $this->workSheet->getColumnDimension($this->cellName($_cols))->setAutoSize(true); //自动计算宽度 |
||||||
307 | } |
||||||
308 | if ($num < count($val)) { |
||||||
309 | $this->_col++; |
||||||
310 | $num++; |
||||||
311 | } |
||||||
312 | $_cols++; |
||||||
313 | } |
||||||
314 | $this->workSheet->mergeCells($_merge . $this->_row.':' . $this->cellName($this->_col) .$this->_row); |
||||||
315 | if(!isset($this->titleConfig['title_show']) || $this->titleConfig['title_show']!==false) { |
||||||
316 | $this->workSheet->setCellValue($_merge . $this->_row, $key);//设置值 |
||||||
317 | } |
||||||
318 | } else { |
||||||
319 | if ($this->title_row != 1) { |
||||||
320 | $this->workSheet->mergeCells($rowName . $this->_row.':' . $rowName . ($this->_row + $this->title_row - 1)); |
||||||
321 | } |
||||||
322 | if(!isset($this->titleConfig['title_show']) || $this->titleConfig['title_show']!==false) { |
||||||
323 | $this->workSheet->setCellValue($rowName . $this->_row, $key);//设置值 |
||||||
324 | } |
||||||
325 | if(!empty($this->titleWidth)){ |
||||||
326 | $this->workSheet->getColumnDimension($rowName)->setWidth($this->titleWidth); //列宽度 |
||||||
327 | }else{ |
||||||
328 | $this->workSheet->getColumnDimension($rowName)->setAutoSize(true); //自动计算宽度 |
||||||
329 | } |
||||||
330 | } |
||||||
331 | $this->_col++; |
||||||
332 | $_merge = $this->cellName($this->_col); |
||||||
333 | } |
||||||
334 | $this->_row += $this->title_row;//当前行数 |
||||||
335 | } |
||||||
336 | |||||||
337 | /** |
||||||
338 | * excel单元格赋值 |
||||||
339 | * @author tinymeng |
||||||
340 | * @param array $val 数据 |
||||||
341 | */ |
||||||
342 | private function excelSetCellValue($val) |
||||||
343 | { |
||||||
344 | //设置单元格行高 |
||||||
345 | if(!empty($this->height)){ |
||||||
346 | $this->workSheet->getRowDimension($this->_row)->setRowHeight($this->height); |
||||||
347 | } |
||||||
348 | $_lie = 0; |
||||||
349 | foreach ($this->field as $v){ |
||||||
350 | $rowName = $this->cellName($_lie); |
||||||
351 | |||||||
352 | if(strpos($v,'.') !== false){ |
||||||
353 | $v = explode('.',$v); |
||||||
354 | $content = $val; |
||||||
355 | for ($i=0;$i<count($v);$i++){ |
||||||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||||||
356 | $content = $content[$v[$i]]??''; |
||||||
357 | } |
||||||
358 | }elseif($v == '_id'){ |
||||||
359 | $content = $this->_row-$this->title_row;//自增序号列 |
||||||
360 | }else{ |
||||||
361 | $content = ($val[$v]??''); |
||||||
362 | } |
||||||
363 | if(is_array($content) && isset($content['type']) && isset($content['content'])){ |
||||||
364 | if($content['type'] == 'image'){ |
||||||
365 | $path = $this->verifyFile($content['content']); |
||||||
0 ignored issues
–
show
It seems like
verifyFile() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
366 | $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); |
||||||
367 | $drawing->setPath($path); |
||||||
368 | if(!empty($content['height'])) { |
||||||
369 | $drawing->setHeight($content['height']); |
||||||
370 | } |
||||||
371 | if(!empty($content['width'])) { |
||||||
372 | $drawing->setWidth($content['width']);//只设置高,宽会自适应,如果设置宽后,高则失效 |
||||||
373 | } |
||||||
374 | if(!empty($content['offsetX'])) { |
||||||
375 | $drawing->setOffsetX($content['offsetX']);//设置X方向偏移量 |
||||||
376 | } |
||||||
377 | if(!empty($content['offsetY'])) { |
||||||
378 | $drawing->setOffsetY($content['offsetY']);//设置Y方向偏移量 |
||||||
379 | } |
||||||
380 | |||||||
381 | $drawing->setCoordinates($rowName.$this->_row); |
||||||
382 | $drawing->setWorksheet($this->workSheet); |
||||||
383 | } |
||||||
384 | }elseif(is_array($content) && isset($content['formula'])){ |
||||||
385 | // 新增:支持 ['formula' => '公式'] 写法 |
||||||
386 | $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content['formula'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_FORMULA); |
||||||
387 | }else { |
||||||
388 | $content = $this->formatValue($content);//格式化数据 |
||||||
0 ignored issues
–
show
It seems like
formatValue() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
389 | if (is_numeric($content)){ |
||||||
390 | if($this->autoDataType && strlen($content)<11){ |
||||||
391 | $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content,DataType::TYPE_NUMERIC); |
||||||
392 | }else{ |
||||||
393 | $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content,DataType::TYPE_STRING2); |
||||||
394 | } |
||||||
395 | }else{ |
||||||
396 | $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content,DataType::TYPE_STRING2); |
||||||
397 | } |
||||||
398 | } |
||||||
399 | $_lie ++; |
||||||
400 | } |
||||||
401 | $this->_row ++; |
||||||
402 | } |
||||||
403 | |||||||
404 | /** |
||||||
405 | * 单元格合并并赋值 |
||||||
406 | * @param array $data 数据 |
||||||
407 | * @param $group_left_count |
||||||
408 | * @author tinymeng |
||||||
409 | */ |
||||||
410 | private function excelGroupLeft(array $data, $group_left_count) |
||||||
411 | { |
||||||
412 | // 获取分组字段在field中的实际位置 |
||||||
413 | $group_field_positions = []; |
||||||
414 | foreach($this->group_left as $group_field){ |
||||||
415 | $position = array_search($group_field, $this->field); |
||||||
416 | if($position !== false){ |
||||||
417 | $group_field_positions[] = $position; |
||||||
418 | } |
||||||
419 | } |
||||||
420 | |||||||
421 | if(empty($group_field_positions)){ |
||||||
422 | throw new TinymengException(StatusCode::COMMON_PARAM_INVALID, '分组字段未在标题中定义'); |
||||||
423 | } |
||||||
424 | |||||||
425 | $group_start = $this->_row; |
||||||
426 | foreach ($data as $key => $val){ |
||||||
427 | // 第一级分组的合并单元格 |
||||||
428 | $rowName = $this->cellName($group_field_positions[0]); // 使用第一个分组字段的实际位置 |
||||||
429 | $coordinate = $rowName.$this->_row.':'.$rowName.($this->_row+$val['count']-1); |
||||||
430 | $this->workSheet->mergeCells($coordinate); |
||||||
431 | $this->workSheet->setCellValue($rowName.$this->_row, $key); |
||||||
432 | |||||||
433 | // 新增:合并mergeColumns指定的其它列 |
||||||
434 | if (!empty($this->mergeColumns)) { |
||||||
435 | foreach ($this->mergeColumns as $field) { |
||||||
436 | // 跳过分组字段本身 |
||||||
437 | if (in_array($field, $this->group_left)) continue; |
||||||
438 | $colIdx = array_search($field, $this->field); |
||||||
439 | if ($colIdx !== false) { |
||||||
440 | $colLetter = $this->cellName($colIdx); |
||||||
441 | $this->workSheet->mergeCells($colLetter.$this->_row.':'.$colLetter.($this->_row+$val['count']-1)); |
||||||
442 | // 取本组第一个数据的值 |
||||||
443 | $this->workSheet->setCellValue($colLetter.$this->_row, $val['data'][0][$field] ?? ''); |
||||||
444 | } |
||||||
445 | } |
||||||
446 | } |
||||||
447 | |||||||
448 | if($group_left_count == 1){ |
||||||
449 | foreach ($val['data'] as $dataRow){ |
||||||
450 | $this->excelSetCellValue($dataRow); |
||||||
451 | } |
||||||
452 | }else{ |
||||||
453 | $sub_group_start = $this->_row; |
||||||
454 | $rowName = $this->cellName($group_field_positions[1]); // 使用第二个分组字段的实际位置 |
||||||
455 | |||||||
456 | foreach ($val['data'] as $k => $v){ |
||||||
457 | $coordinate = $rowName.$sub_group_start.':'.$rowName.($sub_group_start+$v['count']-1); |
||||||
458 | $this->workSheet->mergeCells($coordinate); |
||||||
459 | $this->workSheet->setCellValue($rowName.$sub_group_start, $k); |
||||||
460 | |||||||
461 | foreach ($v['data'] as $data){ |
||||||
462 | $this->excelSetCellValue($data); |
||||||
463 | } |
||||||
464 | |||||||
465 | $sub_group_start = $sub_group_start + $v['count']; |
||||||
466 | } |
||||||
467 | } |
||||||
468 | |||||||
469 | $this->_row = $group_start + $val['count']; |
||||||
470 | $group_start = $this->_row; |
||||||
471 | } |
||||||
472 | } |
||||||
473 | |||||||
474 | /** |
||||||
475 | * 二位数组获取每一级别数量 |
||||||
476 | * @author tinymeng |
||||||
477 | * @param array $data 二维数组原始数据 |
||||||
478 | * @return array |
||||||
479 | */ |
||||||
480 | private function arrayCount($data=[]) |
||||||
481 | { |
||||||
482 | foreach ($data as $key => $val){ |
||||||
483 | $num = 0; |
||||||
484 | foreach ($val as $k => $v){ |
||||||
485 | $sub_num = count($v); |
||||||
486 | $num = $num+$sub_num; |
||||||
487 | $val[$k] = [ |
||||||
488 | 'count' => $sub_num, |
||||||
489 | 'data' => $v |
||||||
490 | ]; |
||||||
491 | } |
||||||
492 | $data[$key] = [ |
||||||
493 | 'count' => $num, |
||||||
494 | 'data' => $val |
||||||
495 | ]; |
||||||
496 | } |
||||||
497 | return $data; |
||||||
498 | } |
||||||
499 | |||||||
500 | /** |
||||||
501 | * 自动合并指定字段相同值的单元格 |
||||||
502 | * @param int $rowStart 数据起始行 |
||||||
503 | * @param int $rowEnd 数据结束行 |
||||||
504 | */ |
||||||
505 | private function autoMergeColumns($rowStart, $rowEnd) |
||||||
506 | { |
||||||
507 | if ($rowEnd <= $rowStart) return; |
||||||
508 | foreach ($this->mergeColumns as $fieldName) { |
||||||
509 | $colIdx = array_search($fieldName, $this->field); |
||||||
510 | if ($colIdx === false) continue; |
||||||
511 | $colLetter = $this->cellName($colIdx); |
||||||
512 | $lastValue = null; |
||||||
513 | $mergeStart = $rowStart; |
||||||
514 | for ($row = $rowStart; $row <= $rowEnd; $row++) { |
||||||
515 | $cellValue = $this->workSheet->getCell($colLetter . $row)->getValue(); |
||||||
516 | if ($lastValue !== null && $cellValue !== $lastValue) { |
||||||
517 | if ($row - $mergeStart > 1) { |
||||||
518 | $this->workSheet->mergeCells($colLetter . $mergeStart . ':' . $colLetter . ($row - 1)); |
||||||
519 | } |
||||||
520 | $mergeStart = $row; |
||||||
521 | } |
||||||
522 | $lastValue = $cellValue; |
||||||
523 | } |
||||||
524 | // 处理最后一组 |
||||||
525 | if ($rowEnd - $mergeStart + 1 > 1) { |
||||||
526 | $this->workSheet->mergeCells($colLetter . $mergeStart . ':' . $colLetter . $rowEnd); |
||||||
527 | } |
||||||
528 | } |
||||||
529 | } |
||||||
530 | |||||||
531 | /** |
||||||
532 | * 应用全表样式 |
||||||
533 | */ |
||||||
534 | private function applySheetStyle() |
||||||
535 | { |
||||||
536 | if (empty($this->sheetStyle)) return; |
||||||
537 | // 计算数据区范围 |
||||||
538 | $startCol = 'A'; |
||||||
539 | $endCol = $this->cellName(count($this->field) - 1); |
||||||
540 | $startRow = 1; |
||||||
541 | $endRow = $this->_row - 1; |
||||||
542 | $cellRange = $startCol . $startRow . ':' . $endCol . $endRow; |
||||||
543 | $this->workSheet->getStyle($cellRange)->applyFromArray($this->sheetStyle); |
||||||
544 | } |
||||||
545 | |||||||
546 | /** |
||||||
547 | * 设置自定义表格操作回调 |
||||||
548 | * @param callable $fn |
||||||
549 | * @return $this |
||||||
550 | */ |
||||||
551 | public function complexFormat(callable $fn) { |
||||||
552 | $this->complexFormatCallback = $fn; |
||||||
553 | return $this; |
||||||
554 | } |
||||||
555 | |||||||
556 | |||||||
557 | } |
||||||
558 |