SpreadsheetExcelService.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2020 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace crmeb\services;
  12. use think\exception\ValidateException;
  13. class SpreadsheetExcelService
  14. {
  15. //
  16. private static $instance = null;
  17. //PHPSpreadsheet实例化对象
  18. private static $spreadsheet = null;
  19. //sheet实例化对象
  20. private static $sheet = null;
  21. private static $createsheet = null;
  22. //表头计数
  23. protected static $count;
  24. //表头占行数
  25. protected static $topNumber = 3;
  26. //表能占据表行的字母对应self::$cellkey
  27. protected static $cells;
  28. //表头数据
  29. protected static $data = [];
  30. //文件名
  31. protected static $title = '订单导出';
  32. //行宽
  33. protected static $width = 20;
  34. //行高
  35. protected static $height = 50;
  36. //保存文件目录
  37. protected static $path = 'phpExcel/';
  38. //总行数
  39. protected static $colum = 3;
  40. //设置style
  41. private static $styleArray = [
  42. // 'borders' => [
  43. // 'allBorders' => [
  44. // // PHPExcel_Style_Border里面有很多属性,想要其他的自己去看
  45. // // 'style' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THICK,//边框是粗的
  46. // // 'style' => \PHPExcel_Style_Border::BORDER_DOUBLE,//双重的
  47. // // 'style' => \PHPExcel_Style_Border::BORDER_HAIR,//虚线
  48. // // 'style' => \PHPExcel_Style_Border::BORDER_MEDIUM,//实粗线
  49. // // 'style' => \PHPExcel_Style_Border::BORDER_MEDIUMDASHDOT,//虚粗线
  50. // // 'style' => \PHPExcel_Style_Border::BORDER_MEDIUMDASHDOTDOT,//点虚粗线
  51. // 'style' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,//细边框
  52. // // 'color' => ['argb' => 'FFFF0000'],
  53. // ],
  54. // ],
  55. 'font' => [
  56. 'bold' => true
  57. ],
  58. 'alignment' => [
  59. 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
  60. 'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER
  61. ]
  62. ];
  63. private function __construct(){}
  64. private function __clone(){}
  65. public static function instance()
  66. {
  67. if (self::$instance === null) {
  68. self::$instance = new self();
  69. self::$spreadsheet = $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
  70. }
  71. return self::$instance;
  72. }
  73. public function createOrActive($i = null)
  74. {
  75. if($i){
  76. self::$sheet = self::$spreadsheet->createSheet();
  77. }else{
  78. self::$sheet = self::$spreadsheet->getActiveSheet();
  79. }
  80. return $this;
  81. }
  82. /**
  83. *设置字体格式
  84. * @param $title string 必选
  85. * return string
  86. */
  87. public static function setUtf8($title)
  88. {
  89. return iconv('utf-8', 'gb2312', $title);
  90. }
  91. /**
  92. * 创建保存excel目录
  93. * return string
  94. */
  95. public static function savePath()
  96. {
  97. if(!is_dir(self::$path)){
  98. if (mkdir(self::$path, 0700) == false) {
  99. return false;
  100. }
  101. }
  102. //年月一级目录
  103. $mont_path = self::$path.date('Ym');
  104. if(!is_dir($mont_path)){
  105. if (mkdir($mont_path, 0700) == false) {
  106. return false;
  107. }
  108. }
  109. //日二级目录
  110. $day_path = $mont_path.'/'.date('d');
  111. if(!is_dir($day_path)){
  112. if (mkdir($day_path, 0700) == false) {
  113. return false;
  114. }
  115. }
  116. return $day_path;
  117. }
  118. /**
  119. * 设置标题
  120. * @param $title string || array ['title'=>'','name'=>'','info'=>[]]
  121. * @param $Name string
  122. * @param $info string || array;
  123. * @param $funName function($style,$A,$A2) 自定义设置头部样式
  124. * @return $this
  125. */
  126. public function setExcelTile(array $data)
  127. {
  128. //设置参数
  129. if (is_array($data)) {
  130. if (isset($data['title'])) $title = $data['title'];
  131. if (isset($data['sheets'])) $sheets = $data['sheets'];
  132. }
  133. empty($title) ? $title = self::$title : self::$title = $title;
  134. if (empty($sheets)) $sheets = time();
  135. //设置Excel属性
  136. self::$spreadsheet->getProperties()
  137. ->setCreator("Neo")
  138. ->setLastModifiedBy("Neo")
  139. ->setTitle(self::setUtf8($title))
  140. ->setSubject($sheets)
  141. ->setDescription("")
  142. ->setKeywords($sheets)
  143. ->setCategory("");
  144. self::$sheet->setTitle($sheets);
  145. self::$sheet->mergeCells('A1:' . self::$cells . '1'); //合并表头单元格
  146. self::$sheet->getRowDimension('A')->setRowHeight(40); //设置行高
  147. self::$sheet->setCellValue('A1', $title); //负值
  148. self::$sheet->getStyle('A1')->getFont()->setName('黑体');
  149. self::$sheet->getStyle('A1')->getFont()->setSize(20);
  150. self::$sheet->getStyle('A1')->getFont()->setBold(true);
  151. self::$sheet->getStyle('A1')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER); //设置左对齐
  152. if(isset($data['mark']) && !empty($data['mark'])){
  153. foreach ($data['mark'] as $k => $v){
  154. $i = $k + 2;
  155. self::$sheet->mergeCells('A'.$i.':' . self::$cells . $i);
  156. self::$sheet->setCellValue('A'.$i, $v);
  157. self::$sheet->getStyle('A'.$i)->getFont()->setName('宋体');
  158. self::$sheet->getStyle('A'.$i)->getFont()->setSize(16);
  159. self::$sheet->getStyle('A'.$i)->getFont()->setBold(true);
  160. self::$sheet->getStyle('A'.$i)->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT);
  161. }
  162. }
  163. return $this;
  164. }
  165. /**
  166. * 设置第二行标题内容
  167. * @param $info array (['name'=>'','site'=>'','phone'=>123] || ['我是表名','我是地址','我是手机号码'] ) || string 自定义
  168. * @return string
  169. */
  170. private static function setCellInfo($info)
  171. {
  172. $content = ['操作者:', '导出日期:' . date('Y-m-d', time()), '地址:', '电话:'];
  173. if (is_array($info) && !empty($info)) {
  174. if (isset($info['name'])) {
  175. $content[0] .= $info['name'];
  176. } else {
  177. $content[0] .= isset($info[0]) ? $info[0] : '';
  178. }
  179. if (isset($info['site'])) {
  180. $content[2] .= $info['site'];
  181. } else {
  182. $content[2] .= isset($info[1]) ? $info[1] : '';
  183. }
  184. if (isset($info['phone'])) {
  185. $content[3] .= $info['phone'];
  186. } else {
  187. $content[3] .= isset($info[2]) ? $info[2] : '';
  188. }
  189. return implode(' ', $content);
  190. } else if (is_string($info)) {
  191. return empty($info) ? implode(' ', $content) : $info;
  192. }
  193. }
  194. /**
  195. * 设置头部信息
  196. * @param $data array
  197. * @return $this
  198. */
  199. public function setExcelHeader($data,$topNumber)
  200. {
  201. $span = 'A';
  202. self::$topNumber = $topNumber;
  203. foreach ($data as $key => $value) {
  204. self::$sheet->getColumnDimension($span)->setWidth(self::$width);
  205. self::$sheet->setCellValue($span.self::$topNumber, $value);
  206. self::$sheet->getStyle($span.self::$topNumber)->getFont()->setSize(16);
  207. $span++;
  208. }
  209. $span = chr(ord($span) -1);
  210. self::$sheet->getRowDimension(self::$topNumber)->setRowHeight(25);
  211. self::$sheet->getStyle('A1:' . $span.self::$topNumber)->getBorders()->getAllBorders()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
  212. self::$cells = $span;
  213. return $this;
  214. }
  215. /**
  216. *
  217. * execl数据导出
  218. * @param $data 需要导出的数据 格式和以前的可是一样
  219. *
  220. * 特殊处理:合并单元格需要先对数据进行处理
  221. */
  222. public function setExcelContent($data = [])
  223. {
  224. if (!empty($data) && is_array($data)) {
  225. $column = self::$topNumber+1;
  226. // 行写入
  227. foreach ($data as $key => $rows) {
  228. $span = 'A';
  229. // 列写入
  230. foreach ($rows as $keyName => $value) {
  231. self::$sheet->setCellValue($span . $column, $value);
  232. $span++;
  233. }
  234. $column++;
  235. }
  236. $span = chr(ord($span) -1);
  237. self::$colum = $column;
  238. self::$sheet->getDefaultRowDimension()->setRowHeight(self::$height);
  239. //设置内容字体样式
  240. self::$sheet->getStyle('A'.self::$topNumber .':'. $span.$column)->applyFromArray(self::$styleArray);
  241. //设置边框
  242. self::$sheet->getStyle('A1:' . $span.$column )->getBorders()->getAllBorders()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
  243. //设置自动换行
  244. self::$sheet->getStyle('A4:' . $span.$column)->getAlignment()->setWrapText(true);
  245. }
  246. return $this;
  247. }
  248. public function setExcelEnd(array $data)
  249. {
  250. if(!empty($data)){
  251. foreach ($data as $key => $value){
  252. $i = self::$colum + $key ;
  253. self::$sheet->mergeCells('A'.$i.':' . self::$cells.$i);
  254. self::$sheet->setCellValue('A'.$i, $value);
  255. self::$sheet->getStyle('A'.$i)->getFont()->setName('宋体');
  256. self::$sheet->getStyle('A'.$i)->getFont()->setSize(16);
  257. self::$sheet->getStyle('A'.$i)->getFont()->setBold(true);
  258. self::$sheet->getStyle('A'.$i)->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT);
  259. }
  260. self::$sheet->getStyle('A1:' .self::$cells.$i)->getBorders()->getAllBorders()->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN);
  261. }
  262. return $this;
  263. }
  264. /**
  265. * 保存表格数据
  266. * @param $filename 文件名称
  267. * @param $suffix 文件后缀名
  268. * @param $path 是否保存文件文件
  269. * @return 保存文件:return string
  270. */
  271. public function excelSave($fileName = '',$suffix = 'xlsx',$path)
  272. {
  273. if(empty($fileName)) $fileName = date('YmdHis').time();
  274. if(empty($suffix)) $suffix = 'xlsx';
  275. // 重命名表(UTF8编码不需要这一步)
  276. if (mb_detect_encoding($fileName) != "UTF-8") $fileName = iconv("utf-8", "gbk//IGNORE", $fileName);
  277. $save_path = self::$path.$path;
  278. $root_path = app()->getRootPath().'public/'.$save_path;
  279. if(!is_dir($root_path)) mkdir($root_path, 0700,true);
  280. $spreadsheet = self::$spreadsheet;
  281. $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
  282. $writer->save($root_path.'/'.$fileName.'.'.$suffix);
  283. return $save_path.'/'.$fileName.'.'.$suffix;
  284. }
  285. /**
  286. * TODO
  287. * @param $filePath 文件路径
  288. * @param array $sql 需要入库的字段 => excel表的列 例 [order_sn => 'B']
  289. * @param array $where 每条入库的条件 同上
  290. * @param int $startRow 有效数据从第几行开始
  291. * @return array
  292. * @author Qinii
  293. * @day 3/15/21
  294. */
  295. public function _import($filePath,array $sql,$where = [],$startRow = 1)
  296. {
  297. if(!file_exists($filePath)) return ;
  298. $ext = ucfirst(pathinfo($filePath, PATHINFO_EXTENSION));
  299. $ret = [];
  300. if(in_array($ext,['Xlsx','Xls'])){
  301. $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($ext);
  302. $spreadsheet = $reader->load($filePath);
  303. $sheet = $spreadsheet->getActiveSheet();
  304. $row_count = $sheet->getHighestDataRow();//取得总行数
  305. if(!empty($check)){
  306. foreach ($check as $s => $c){
  307. $_c = $sheet->getCell($s)->getValue();
  308. if($_c !== $c) throw new ValidateException($s.'不是'.$s.'不可打入');
  309. }
  310. }
  311. for ($row = $startRow; $row <= $row_count; $row++){
  312. $_where = [];
  313. $item = [];
  314. if(!empty($where)){
  315. foreach ($where as $k => $v){
  316. $_w = $sheet->getCell($v.$row)->getValue();
  317. if(!$_w) continue;
  318. $_where[$k] = $_w;
  319. }
  320. }
  321. if(!empty($sql)){
  322. foreach ($sql as $key => $value){
  323. $item[$key] = $sheet->getCell($value.$row)->getValue();
  324. }
  325. }
  326. $ret[] = ['where' => $_where, 'value' => $item];
  327. }
  328. }
  329. return $ret;
  330. }
  331. /**
  332. * TODO 检测导入格式
  333. * @param $filePath
  334. * @param array $check
  335. * @return bool
  336. * @author Qinii
  337. * @day 5/7/21
  338. */
  339. public function checkImport($filePath,$check = [])
  340. {
  341. $ext = ucfirst(pathinfo($filePath, PATHINFO_EXTENSION));
  342. $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($ext);
  343. $spreadsheet = $reader->load($filePath);
  344. $sheet = $spreadsheet->getActiveSheet();
  345. if(!empty($check)){
  346. foreach ($check as $s => $c){
  347. $_c = $sheet->getCell($s)->getValue();
  348. if($_c !== $c) throw new ValidateException('表格"'.$s.'"不是"'.$c.'"不可导入');
  349. }
  350. }
  351. return true;
  352. }
  353. }