ExcelHelper.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <?php
  2. namespace app\common\util;
  3. use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
  4. use PhpOffice\PhpSpreadsheet\Reader\Xls;
  5. use PhpOffice\PhpSpreadsheet\IOFactory;
  6. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  7. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  8. use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
  9. use PhpOffice\PhpSpreadsheet\Cell\DataType;
  10. use PhpOffice\PhpSpreadsheet\Style\Fill;
  11. use PhpOffice\PhpSpreadsheet\Style\Color;
  12. use PhpOffice\PhpSpreadsheet\Style\Alignment;
  13. use PhpOffice\PhpSpreadsheet\Style\Border;
  14. use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
  15. use think\facade\Log;
  16. class ExcelHelper extends SingleObjectClass
  17. {
  18. public $columnNameArray = [];
  19. /**
  20. * 使用PhpSpreadsheet导入
  21. *
  22. * @param string $file 文件地址
  23. * @param int $sheet 工作表sheet(传0则获取第一个sheet)
  24. * @param int $columnCnt 列数(传0则自动获取最大列)
  25. * @param array $options 操作选项
  26. * array mergeCells 合并单元格数组
  27. * array formula 公式数组
  28. * array format 单元格格式数组
  29. *
  30. * @return array
  31. * @throws Exception
  32. */
  33. public function importExecl(string $file = '', int $sheet = 0, int $columnCnt = 0, &$options = [])
  34. {
  35. try {
  36. /* 转码 */
  37. $file = iconv("utf-8", "gb2312", $file);
  38. if (empty($file) or !file_exists($file)) {
  39. throw new \Exception('文件不存在!');
  40. }
  41. /** @var Xlsx $objRead */
  42. $objRead = IOFactory::createReader('Xlsx');
  43. if (!$objRead->canRead($file)) {
  44. /** @var Xls $objRead */
  45. $objRead = IOFactory::createReader('Xls');
  46. if (!$objRead->canRead($file)) {
  47. throw new \Exception('只支持导入Excel文件!');
  48. }
  49. }
  50. /* 如果不需要获取特殊操作,则只读内容,可以大幅度提升读取Excel效率 */
  51. empty($options) && $objRead->setReadDataOnly(true);
  52. /* 建立excel对象 */
  53. $obj = $objRead->load($file);
  54. /* 获取指定的sheet表 */
  55. $currSheet = $obj->getSheet($sheet);
  56. if (isset($options['mergeCells'])) {
  57. /* 读取合并行列 */
  58. $options['mergeCells'] = $currSheet->getMergeCells();
  59. }
  60. if (0 == $columnCnt) {
  61. /* 取得最大的列号 */
  62. $columnH = $currSheet->getHighestColumn();
  63. /* 兼容原逻辑,循环时使用的是小于等于 */
  64. $columnCnt = Coordinate::columnIndexFromString($columnH);
  65. }
  66. /* 获取总行数 */
  67. $rowCnt = $currSheet->getHighestRow();
  68. $data = [];
  69. /* 读取内容 */
  70. for ($_row = 1; $_row <= $rowCnt; $_row++) {
  71. $isNull = true;
  72. for ($_column = 1; $_column <= $columnCnt; $_column++) {
  73. $cellName = Coordinate::stringFromColumnIndex($_column);
  74. $cellId = $cellName . $_row;
  75. $cell = $currSheet->getCell($cellId);
  76. if (isset($options['format'])) {
  77. /* 获取格式 */
  78. $format = $cell->getStyle()->getNumberFormat()->getFormatCode();
  79. /* 记录格式 */
  80. $options['format'][$_row][$cellName] = $format;
  81. }
  82. if (isset($options['formula'])) {
  83. /* 获取公式,公式均为=号开头数据 */
  84. $formula = $currSheet->getCell($cellId)->getValue();
  85. if (0 === strpos($formula, '=')) {
  86. $options['formula'][$cellName . $_row] = $formula;
  87. }
  88. }
  89. if (isset($format) && 'm/d/yyyy' == $format) {
  90. /* 日期格式翻转处理 */
  91. $cell->getStyle()->getNumberFormat()->setFormatCode('yyyy/mm/dd');
  92. }
  93. $data[$_row][$cellName] = trim($currSheet->getCell($cellId)->getFormattedValue());
  94. if (!empty($data[$_row][$cellName])) {
  95. $isNull = false;
  96. }
  97. }
  98. /* 判断是否整行数据为空,是的话删除该行数据 */
  99. if ($isNull) {
  100. unset($data[$_row]);
  101. }
  102. }
  103. return $data;
  104. } catch (\Exception $e) {
  105. throw $e;
  106. }
  107. }
  108. /**
  109. * PhpSpreadsheet Excel导出,
  110. *
  111. * @param array $datas 导出数据,格式['A1' => 'XXXX公司报表', 'B1' => '序号']
  112. * @param string $fileName 导出文件名称
  113. * @param array $options 操作选项,例如:
  114. * bool print 设置打印格式
  115. * bool defaultWrap 默认换行,true或false
  116. * bool defaultAlignCenter 默认居中,true或false
  117. * number defaultFontSize 默认字体大小
  118. * string freezePane 锁定行数,例如表头为第一行,则锁定表头输入A2
  119. * array setARGB 设置背景色,例如['A1', 'C1']
  120. * array setWidth 设置宽度,例如['A' => 30, 'C' => 20]
  121. * bool|string setBorder 设置单元格边框 A1:B5
  122. * array mergeCells 设置合并单元格,例如['A1:J1' => 'A1:J1']
  123. * array formula 设置公式,例如['F2' => '=IF(D2>0,E42/D2,0)']
  124. * array format 设置格式,整列设置,例如['A' => 'General','B'=>'number']
  125. * array alignCenter 设置居中样式,例如['A1', 'A2']
  126. * array bold 设置加粗样式,例如['A1', 'A2']
  127. * array wrap 设置换行,例如['A1', 'A2']
  128. * array rowHeight 设置行高,例如['9'=>"20"]
  129. * array fontSize 设置行高,例如['A1'=>"20"]
  130. * string savePath 保存路径,设置后则文件保存到服务器,不通过浏览器下载
  131. */
  132. public function exportExcel(array $datas, string $fileName = '', array $options = []): bool
  133. {
  134. try {
  135. if (empty($datas)) {
  136. return false;
  137. }
  138. set_time_limit(0);
  139. /** @var Spreadsheet $objSpreadsheet */
  140. $objSpreadsheet = app(Spreadsheet::class);
  141. /* 设置默认文字居左,上下居中 */
  142. $styleArray = [
  143. 'alignment' => [
  144. 'horizontal' => Alignment::HORIZONTAL_LEFT,
  145. 'vertical' => Alignment::VERTICAL_CENTER,
  146. ],
  147. ];
  148. $objSpreadsheet->getDefaultStyle()->applyFromArray($styleArray);
  149. /* 设置Excel Sheet */
  150. $activeSheet = $objSpreadsheet->setActiveSheetIndex(0);
  151. /* 打印设置 */
  152. if (isset($options['print']) && $options['print']) {
  153. /* 设置打印为A4效果 */
  154. $activeSheet->getPageSetup()->setPaperSize(PageSetup:: PAPERSIZE_A4);
  155. /* 设置打印时边距 */
  156. $pValue = 1 / 2.54;
  157. $activeSheet->getPageMargins()->setTop($pValue / 2);
  158. $activeSheet->getPageMargins()->setBottom($pValue * 2);
  159. $activeSheet->getPageMargins()->setLeft($pValue / 2);
  160. $activeSheet->getPageMargins()->setRight($pValue / 2);
  161. }
  162. /* 行数据处理 */
  163. foreach ($datas as $sKey => $sItem) {
  164. /* 默认文本格式 */
  165. $pDataType = DataType::TYPE_STRING;
  166. /* 设置单元格格式 */
  167. if (isset($options['format']) && !empty($options['format'])) {
  168. $colRow = Coordinate::coordinateFromString($sKey);
  169. /* 存在该列格式并且有特殊格式 */
  170. if (isset($options['format'][$colRow[0]]) &&
  171. NumberFormat::FORMAT_GENERAL != $options['format'][$colRow[0]]) {
  172. $activeSheet->getStyle($sKey)->getNumberFormat()
  173. ->setFormatCode($options['format'][$colRow[0]]);
  174. if (false !== strpos($options['format'][$colRow[0]], '0.00') &&
  175. is_numeric(str_replace(['¥', ','], '', $sItem))) {
  176. /* 数字格式转换为数字单元格 */
  177. $pDataType = DataType::TYPE_NUMERIC;
  178. $sItem = str_replace(['¥', ','], '', $sItem);
  179. }
  180. } elseif (is_int($sItem) || is_numeric($sItem)) {
  181. $pDataType = DataType::TYPE_NUMERIC;
  182. }
  183. }
  184. $activeSheet->setCellValueExplicit($sKey, $sItem, $pDataType);
  185. /* 存在:形式的合并行列,列入A1:B2,则对应合并 */
  186. if (false !== strstr($sKey, ":")) {
  187. $options['mergeCells'][$sKey] = $sKey;
  188. }
  189. /* 设置换行 */
  190. if (isset($options['defaultWrap']) && !empty($options['defaultWrap'])) {
  191. $activeSheet->getStyle($sKey)->getAlignment()->setWrapText(true);
  192. }
  193. /* 设置字体 */
  194. if (isset($options['defaultFontSize']) && !empty($options['defaultFontSize'])) {
  195. $activeSheet->getStyle($sKey)->getFont()->setSize($options["defaultFontSize"]);
  196. }
  197. /* 设置居中 */
  198. if (isset($options['defaultAlignCenter']) && !empty($options['defaultAlignCenter'])) {
  199. $styleArray = [
  200. 'alignment' => [
  201. 'horizontal' => Alignment::HORIZONTAL_CENTER,
  202. 'vertical' => Alignment::VERTICAL_CENTER,
  203. ],
  204. ];
  205. $activeSheet->getStyle($sKey)->applyFromArray($styleArray);
  206. }
  207. }
  208. unset($datas);
  209. /* 设置锁定行 */
  210. if (isset($options['freezePane']) && !empty($options['freezePane'])) {
  211. $activeSheet->freezePane($options['freezePane']);
  212. unset($options['freezePane']);
  213. }
  214. /* 设置宽度 */
  215. if (isset($options['setWidth']) && !empty($options['setWidth'])) {
  216. foreach ($options['setWidth'] as $swKey => $swItem) {
  217. $activeSheet->getColumnDimension($swKey)->setWidth($swItem);
  218. }
  219. unset($options['setWidth']);
  220. }
  221. /* 设置背景色 */
  222. if (isset($options['setARGB']) && !empty($options['setARGB'])) {
  223. foreach ($options['setARGB'] as $sItem) {
  224. $activeSheet->getStyle($sItem)
  225. ->getFill()->setFillType(Fill::FILL_SOLID)
  226. ->getStartColor()->setARGB(Color::COLOR_YELLOW);
  227. }
  228. unset($options['setARGB']);
  229. }
  230. /* 设置公式 */
  231. if (isset($options['formula']) && !empty($options['formula'])) {
  232. foreach ($options['formula'] as $fKey => $fItem) {
  233. $activeSheet->setCellValue($fKey, $fItem);
  234. }
  235. unset($options['formula']);
  236. }
  237. /* 合并行列处理 */
  238. if (isset($options['mergeCells']) && !empty($options['mergeCells'])) {
  239. $activeSheet->setMergeCells($options['mergeCells']);
  240. unset($options['mergeCells']);
  241. }
  242. /* 设置居中 */
  243. if (isset($options['alignCenter']) && !empty($options['alignCenter'])) {
  244. $styleArray = [
  245. 'alignment' => [
  246. 'horizontal' => Alignment::HORIZONTAL_CENTER,
  247. 'vertical' => Alignment::VERTICAL_CENTER,
  248. ],
  249. ];
  250. foreach ($options['alignCenter'] as $acItem) {
  251. $activeSheet->getStyle($acItem)->applyFromArray($styleArray);
  252. }
  253. unset($options['alignCenter']);
  254. }
  255. /* 设置加粗 */
  256. if (isset($options['bold']) && !empty($options['bold'])) {
  257. foreach ($options['bold'] as $bItem) {
  258. $activeSheet->getStyle($bItem)->getFont()->setBold(true);
  259. }
  260. unset($options['bold']);
  261. }
  262. /* 设置换行 */
  263. if (isset($options['wrap']) && !empty($options['wrap'])) {
  264. foreach ($options['wrap'] as $acItem) {
  265. $activeSheet->getStyle($acItem)->getAlignment()->setWrapText(true);
  266. }
  267. unset($options['wrap']);
  268. }
  269. //设置行高 rowHeight
  270. if (isset($options['rowHeight']) && !empty($options['rowHeight'])) {
  271. foreach ($options['rowHeight'] as $key => $item) {
  272. $activeSheet->getRowDimension($key)->setRowHeight($item);
  273. }
  274. unset($options['rowHeight']);
  275. }
  276. /* 设置字体大小 */
  277. if (isset($options['fontSize']) && !empty($options['fontSize'])) {
  278. foreach ($options['fontSize'] as $key => $value) {
  279. $activeSheet->getStyle($key)->getFont()->setSize($value);
  280. }
  281. unset($options['fontSize']);
  282. }
  283. /* 设置单元格边框,整个表格设置即可,必须在数据填充后才可以获取到最大行列 */
  284. if (isset($options['setBorder']) && $options['setBorder']) {
  285. $border = [
  286. 'borders' => [
  287. 'allBorders' => [
  288. 'borderStyle' => Border::BORDER_THIN, // 设置border样式
  289. 'color' => ['argb' => 'FF000000'], // 设置border颜色
  290. ],
  291. ],
  292. ];
  293. if ($options['setBorder'] === true) {
  294. $setBorder = 'A1:' . $activeSheet->getHighestColumn() . $activeSheet->getHighestRow();
  295. } else {
  296. $setBorder = $options['setBorder'];
  297. }
  298. $activeSheet->getStyle($setBorder)->applyFromArray($border);
  299. unset($options['setBorder']);
  300. }
  301. $fileName = !empty($fileName) ? $fileName : (date('YmdHis') . '.xlsx');
  302. if (!isset($options['savePath'])) {
  303. /* 直接导出Excel,无需保存到本地,输出07Excel文件 */
  304. header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  305. header(
  306. "Content-Disposition:attachment;filename=" . iconv(
  307. "utf-8", "GB2312//TRANSLIT", $fileName
  308. )
  309. );
  310. header('Cache-Control: max-age=0');//禁止缓存
  311. $savePath = 'php://output';
  312. } else {
  313. $savePath = $options['savePath'];
  314. }
  315. if (ob_get_length() > 0) {
  316. ob_clean();
  317. ob_start();
  318. }
  319. $objWriter = IOFactory::createWriter($objSpreadsheet, 'Xlsx');
  320. $objWriter->save($savePath);
  321. /* 释放内存 */
  322. $objSpreadsheet->disconnectWorksheets();
  323. unset($objSpreadsheet);
  324. if (ob_get_length() > 0) {
  325. ob_end_flush();
  326. }
  327. return true;
  328. } catch (Exception $e) {
  329. return false;
  330. }
  331. }
  332. public function __construct()
  333. {
  334. $this->setColumnName();
  335. }
  336. private function setColumnName()
  337. {
  338. $columnNameArr = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
  339. $columnNameArrLen = count($columnNameArr);
  340. $firstWord = "";
  341. for ($i = 0; $i < 500; $i++) {
  342. if ($i % $columnNameArrLen == 0 && $i != 0) {
  343. $firstWord = $columnNameArr[$i / $columnNameArrLen - 1];
  344. }
  345. $this->columnNameArray[] = $firstWord . $columnNameArr[$i % $columnNameArrLen];
  346. }
  347. }
  348. }