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 | * @param $data |
||||||
72 | * @return $this |
||||||
73 | */ |
||||||
74 | public function setData($data){ |
||||||
75 | $this->data = $data; |
||||||
76 | return $this; |
||||||
77 | } |
||||||
78 | |||||||
79 | /** |
||||||
80 | * @param $data |
||||||
81 | * @return $this |
||||||
82 | */ |
||||||
83 | public function getData(){ |
||||||
84 | return $this->data; |
||||||
85 | } |
||||||
86 | |||||||
87 | |||||||
88 | /** |
||||||
89 | * @return void |
||||||
90 | * @throws \PhpOffice\PhpSpreadsheet\Exception |
||||||
91 | */ |
||||||
92 | public function initWorkSheet() |
||||||
93 | { |
||||||
94 | $this->_col = 0; |
||||||
95 | $this->_row = 1; |
||||||
96 | $this->fileTitle = []; |
||||||
97 | $this->data = []; |
||||||
98 | $this->field = []; |
||||||
99 | if($this->freezePane) $this->workSheet->freezePane($this->freezePane); //冻结窗格 |
||||||
100 | } |
||||||
101 | |||||||
102 | /** |
||||||
103 | * @param $fileTitle |
||||||
104 | * @param $data |
||||||
105 | * @return $this |
||||||
106 | * @throws TinymengException |
||||||
107 | * @throws \PhpOffice\PhpSpreadsheet\Exception |
||||||
108 | */ |
||||||
109 | public function setWorkSheetData($fileTitle,$data) |
||||||
110 | { |
||||||
111 | if(isset($fileTitle['title_row']) || isset($fileTitle['group_left'])){ |
||||||
112 | /** |
||||||
113 | * $fileTitle = [ |
||||||
114 | * 'title_row'=>1, |
||||||
115 | * 'group_left'=>[], |
||||||
116 | * 'title'=>[ |
||||||
117 | * '姓名'=>'name' |
||||||
118 | * ], |
||||||
119 | * ]; |
||||||
120 | */ |
||||||
121 | $this->title_row = $fileTitle['title_row']??1; |
||||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||||
122 | $this->group_left = $fileTitle['group_left']??[]; |
||||||
123 | $titleData = $fileTitle['title']??[]; |
||||||
124 | }else{ |
||||||
125 | /** |
||||||
126 | * $fileTitle = [ |
||||||
127 | * '姓名'=>'name', |
||||||
128 | * ]; |
||||||
129 | */ |
||||||
130 | $titleData = $fileTitle; |
||||||
131 | } |
||||||
132 | // 根据字段映射方式处理 title |
||||||
133 | if ($this->fieldMappingMethod === ConstCode::FIELD_MAPPING_METHOD_FIELD_CORRESPONDING_NAME) { |
||||||
134 | $this->fileTitle = array_flip($titleData);// 字段对应名称方式 - 需要将键值对调 |
||||||
135 | }else{ |
||||||
136 | $this->fileTitle = $titleData;// 名称对应字段方式 - 保持原样 |
||||||
137 | } |
||||||
138 | $this->data = $data; |
||||||
139 | |||||||
140 | /** 设置第一行格式 */ |
||||||
141 | if($this->mainTitleLine === true){ |
||||||
142 | $this->excelHeader(); |
||||||
143 | } |
||||||
144 | |||||||
145 | /** 设置表头 **/ |
||||||
146 | $this->excelTitle(); |
||||||
147 | |||||||
148 | /** 获取列表里所有字段 **/ |
||||||
149 | foreach ($this->fileTitle as $key => $val){ |
||||||
150 | if(is_array($val)){ |
||||||
151 | foreach ($val as $k => $v){ |
||||||
152 | $this->field[] = $v; |
||||||
153 | } |
||||||
154 | }else{ |
||||||
155 | $this->field[] = $val; |
||||||
156 | } |
||||||
157 | } |
||||||
158 | /** 查询结果赋值 **/ |
||||||
159 | if(!empty($this->data)){ |
||||||
160 | $this->excelSetValue(); |
||||||
161 | } |
||||||
162 | return $this; |
||||||
163 | } |
||||||
164 | |||||||
165 | /** |
||||||
166 | * excelSetValue |
||||||
167 | * @author: Tinymeng <[email protected]> |
||||||
168 | * @time: 2022/2/22 11:43 |
||||||
169 | */ |
||||||
170 | public function excelSetValue(){ |
||||||
171 | if(empty($this->group_left)){ //判断左侧是否分组 |
||||||
172 | foreach ($this->data as $key => $val){ |
||||||
173 | $this->excelSetCellValue($val); |
||||||
174 | } |
||||||
175 | }else{ //根据设置分组字段进行分组 |
||||||
176 | /** 数据分组 **/ |
||||||
177 | $data = []; |
||||||
178 | $group_left_count = count($this->group_left); |
||||||
179 | if($group_left_count == 1){ |
||||||
180 | foreach ($this->data as $k => $v){ |
||||||
181 | $data[$v[$this->group_left[0]]][] = $v; |
||||||
182 | } |
||||||
183 | foreach ($data as $k =>$v){ |
||||||
184 | $data[$k] = [ |
||||||
185 | 'data' => $v, |
||||||
186 | 'count' => count($v) |
||||||
187 | ]; |
||||||
188 | } |
||||||
189 | $this->excelGroupLeft($data, $group_left_count); |
||||||
190 | }elseif($group_left_count == 2){ |
||||||
191 | foreach ($this->data as $v) { |
||||||
192 | $data[$v[$this->group_left[0]]][$v[$this->group_left[1]]][] = $v; |
||||||
193 | } |
||||||
194 | $this->data = $this->arrayCount($data); |
||||||
195 | $this->excelGroupLeft($this->data, $group_left_count); |
||||||
196 | }else{ |
||||||
197 | throw new TinymengException(StatusCode::COMMON_PARAM_INVALID, |
||||||
198 | '左侧分组过多,导出失败!' |
||||||
199 | ); |
||||||
200 | } |
||||||
201 | } |
||||||
202 | } |
||||||
203 | |||||||
204 | /** |
||||||
205 | * @return void |
||||||
206 | * @throws \PhpOffice\PhpSpreadsheet\Exception |
||||||
207 | */ |
||||||
208 | public function excelHeader(){ |
||||||
209 | $row = 1; |
||||||
210 | $this->workSheet->setCellValue('A'.$row, $this->mainTitle); |
||||||
211 | |||||||
212 | // 计算实际的标题列数 |
||||||
213 | $titleCount = 0; |
||||||
214 | foreach ($this->fileTitle as $key => $val) { |
||||||
215 | if (is_array($val)) { |
||||||
216 | $titleCount += count($val); // 如果是数组,加上子项的数量 |
||||||
217 | } else { |
||||||
218 | $titleCount++; // 如果是单个标题,加1 |
||||||
219 | } |
||||||
220 | } |
||||||
221 | |||||||
222 | // 使用实际的标题列数来合并单元格 |
||||||
223 | $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
![]() |
|||||||
224 | } |
||||||
225 | |||||||
226 | /** |
||||||
227 | * @return void |
||||||
228 | * @throws \PhpOffice\PhpSpreadsheet\Exception |
||||||
229 | */ |
||||||
230 | private function excelTitle(){ |
||||||
231 | if($this->mainTitleLine === true){ |
||||||
232 | $this->_row ++;//当前行数 |
||||||
233 | } |
||||||
234 | |||||||
235 | $_merge = $this->cellName($this->_col); |
||||||
236 | foreach ($this->fileTitle as $key => $val) { |
||||||
237 | if(!empty($this->titleHeight)) { |
||||||
238 | $this->workSheet->getRowDimension($this->_col)->setRowHeight($this->titleHeight);//行高度 |
||||||
239 | } |
||||||
240 | $rowName = $this->cellName($this->_col); |
||||||
241 | $this->workSheet->getStyle($rowName . $this->_row)->getAlignment()->setWrapText(true);//自动换行 |
||||||
242 | if (is_array($val)) { |
||||||
243 | $num = 1; |
||||||
244 | $_cols = $this->_col; |
||||||
245 | foreach ($val as $k => $v) { |
||||||
246 | $this->workSheet->setCellValue($this->cellName($_cols) . ($this->_row+1), $k); |
||||||
247 | if(!empty($this->titleWidth)) { |
||||||
248 | $this->workSheet->getColumnDimension($this->cellName($_cols))->setWidth($this->titleWidth); //列宽度 |
||||||
249 | }else{ |
||||||
250 | $this->workSheet->getColumnDimension($this->cellName($_cols))->setAutoSize(true); //自动计算宽度 |
||||||
251 | } |
||||||
252 | if ($num < count($val)) { |
||||||
253 | $this->_col++; |
||||||
254 | $num++; |
||||||
255 | } |
||||||
256 | $_cols++; |
||||||
257 | } |
||||||
258 | $this->workSheet->mergeCells($_merge . $this->_row.':' . $this->cellName($this->_col) .$this->_row); |
||||||
259 | $this->workSheet->setCellValue($_merge . $this->_row, $key);//设置值 |
||||||
260 | } else { |
||||||
261 | if ($this->title_row != 1) { |
||||||
262 | $this->workSheet->mergeCells($rowName . $this->_row.':' . $rowName . ($this->_row + $this->title_row - 1)); |
||||||
263 | } |
||||||
264 | $this->workSheet->setCellValue($rowName . $this->_row, $key);//设置值 |
||||||
265 | if(!empty($this->titleWidth)){ |
||||||
266 | $this->workSheet->getColumnDimension($rowName)->setWidth($this->titleWidth); //列宽度 |
||||||
267 | }else{ |
||||||
268 | $this->workSheet->getColumnDimension($rowName)->setAutoSize(true); //自动计算宽度 |
||||||
269 | } |
||||||
270 | } |
||||||
271 | $this->_col++; |
||||||
272 | $_merge = $this->cellName($this->_col); |
||||||
273 | } |
||||||
274 | $this->_row += $this->title_row;//当前行数 |
||||||
275 | } |
||||||
276 | |||||||
277 | /** |
||||||
278 | * excel单元格赋值 |
||||||
279 | * @author tinymeng |
||||||
280 | * @param array $val 数据 |
||||||
281 | */ |
||||||
282 | private function excelSetCellValue($val) |
||||||
283 | { |
||||||
284 | //设置单元格行高 |
||||||
285 | if(!empty($this->height)){ |
||||||
286 | $this->workSheet->getRowDimension($this->_row)->setRowHeight($this->height); |
||||||
287 | } |
||||||
288 | $_lie = 0; |
||||||
289 | foreach ($this->field as $v){ |
||||||
290 | $rowName = $this->cellName($_lie); |
||||||
291 | |||||||
292 | if(strpos($v,'.') !== false){ |
||||||
293 | $v = explode('.',$v); |
||||||
294 | $content = $val; |
||||||
295 | 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
}
![]() |
|||||||
296 | $content = $content[$v[$i]]??''; |
||||||
297 | } |
||||||
298 | }elseif($v == '_id'){ |
||||||
299 | $content = $this->_row-$this->title_row;//自增序号列 |
||||||
300 | }else{ |
||||||
301 | $content = ($val[$v]??''); |
||||||
302 | } |
||||||
303 | if(is_array($content) && isset($content['type']) && isset($content['content'])){ |
||||||
304 | if($content['type'] == 'image'){ |
||||||
305 | $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
![]() |
|||||||
306 | $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); |
||||||
307 | $drawing->setPath($path); |
||||||
308 | if(!empty($content['height'])) { |
||||||
309 | $drawing->setHeight($content['height']); |
||||||
310 | } |
||||||
311 | if(!empty($content['width'])) { |
||||||
312 | $drawing->setWidth($content['width']);//只设置高,宽会自适应,如果设置宽后,高则失效 |
||||||
313 | } |
||||||
314 | if(!empty($content['offsetX'])) { |
||||||
315 | $drawing->setOffsetX($content['offsetX']);//设置X方向偏移量 |
||||||
316 | } |
||||||
317 | if(!empty($content['offsetY'])) { |
||||||
318 | $drawing->setOffsetY($content['offsetY']);//设置Y方向偏移量 |
||||||
319 | } |
||||||
320 | |||||||
321 | $drawing->setCoordinates($rowName.$this->_row); |
||||||
322 | $drawing->setWorksheet($this->workSheet); |
||||||
323 | } |
||||||
324 | }else { |
||||||
325 | $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
![]() |
|||||||
326 | if (is_numeric($content)){ |
||||||
327 | if($this->autoDataType && strlen($content)<11){ |
||||||
328 | $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content,DataType::TYPE_NUMERIC); |
||||||
329 | }else{ |
||||||
330 | $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content,DataType::TYPE_STRING2); |
||||||
331 | } |
||||||
332 | }else{ |
||||||
333 | $this->workSheet->setCellValueExplicit($rowName.$this->_row, $content,DataType::TYPE_STRING2); |
||||||
334 | } |
||||||
335 | } |
||||||
336 | $_lie ++; |
||||||
337 | } |
||||||
338 | $this->_row ++; |
||||||
339 | } |
||||||
340 | |||||||
341 | /** |
||||||
342 | * 单元格合并并赋值 |
||||||
343 | * @param array $data 数据 |
||||||
344 | * @param $group_left_count |
||||||
345 | * @author tinymeng |
||||||
346 | */ |
||||||
347 | private function excelGroupLeft(array $data, $group_left_count) |
||||||
348 | { |
||||||
349 | // 获取分组字段在field中的实际位置 |
||||||
350 | $group_field_positions = []; |
||||||
351 | foreach($this->group_left as $group_field){ |
||||||
352 | $position = array_search($group_field, $this->field); |
||||||
353 | if($position !== false){ |
||||||
354 | $group_field_positions[] = $position; |
||||||
355 | } |
||||||
356 | } |
||||||
357 | |||||||
358 | if(empty($group_field_positions)){ |
||||||
359 | throw new TinymengException(StatusCode::COMMON_PARAM_INVALID, '分组字段未在标题中定义'); |
||||||
360 | } |
||||||
361 | |||||||
362 | $group_start = $this->_row; |
||||||
363 | foreach ($data as $key => $val){ |
||||||
364 | // 第一级分组的合并单元格 |
||||||
365 | $rowName = $this->cellName($group_field_positions[0]); // 使用第一个分组字段的实际位置 |
||||||
366 | $coordinate = $rowName.$this->_row.':'.$rowName.($this->_row+$val['count']-1); |
||||||
367 | $this->workSheet->mergeCells($coordinate); |
||||||
368 | $this->workSheet->setCellValue($rowName.$this->_row, $key); |
||||||
369 | |||||||
370 | if($group_left_count == 1){ |
||||||
371 | foreach ($val['data'] as $data){ |
||||||
372 | $this->excelSetCellValue($data); |
||||||
373 | } |
||||||
374 | }else{ |
||||||
375 | $sub_group_start = $this->_row; |
||||||
376 | $rowName = $this->cellName($group_field_positions[1]); // 使用第二个分组字段的实际位置 |
||||||
377 | |||||||
378 | foreach ($val['data'] as $k => $v){ |
||||||
379 | $coordinate = $rowName.$sub_group_start.':'.$rowName.($sub_group_start+$v['count']-1); |
||||||
380 | $this->workSheet->mergeCells($coordinate); |
||||||
381 | $this->workSheet->setCellValue($rowName.$sub_group_start, $k); |
||||||
382 | |||||||
383 | foreach ($v['data'] as $data){ |
||||||
384 | $this->excelSetCellValue($data); |
||||||
385 | } |
||||||
386 | |||||||
387 | $sub_group_start = $sub_group_start + $v['count']; |
||||||
388 | } |
||||||
389 | } |
||||||
390 | |||||||
391 | $this->_row = $group_start + $val['count']; |
||||||
392 | $group_start = $this->_row; |
||||||
393 | } |
||||||
394 | } |
||||||
395 | |||||||
396 | /** |
||||||
397 | * 二位数组获取每一级别数量 |
||||||
398 | * @author tinymeng |
||||||
399 | * @param array $data 二维数组原始数据 |
||||||
400 | * @return array |
||||||
401 | */ |
||||||
402 | private function arrayCount($data=[]) |
||||||
403 | { |
||||||
404 | foreach ($data as $key => $val){ |
||||||
405 | $num = 0; |
||||||
406 | foreach ($val as $k => $v){ |
||||||
407 | $sub_num = count($v); |
||||||
408 | $num = $num+$sub_num; |
||||||
409 | $val[$k] = [ |
||||||
410 | 'count' => $sub_num, |
||||||
411 | 'data' => $v |
||||||
412 | ]; |
||||||
413 | } |
||||||
414 | $data[$key] = [ |
||||||
415 | 'count' => $num, |
||||||
416 | 'data' => $val |
||||||
417 | ]; |
||||||
418 | } |
||||||
419 | return $data; |
||||||
420 | } |
||||||
421 | |||||||
422 | |||||||
423 | } |
||||||
424 |