Excel.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 海豚PHP框架 [ DolphinPHP ]
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2016~2017 河源市卓锐科技有限公司 [ http://www.zrthink.com ]
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: http://dolphinphp.com
  8. // +----------------------------------------------------------------------
  9. // | 开源协议 ( http://www.apache.org/licenses/LICENSE-2.0 )
  10. // +----------------------------------------------------------------------
  11. namespace plugins\Excel\controller;
  12. use app\common\controller\Common;
  13. use think\Db;
  14. require_once dirname(dirname(__FILE__)) . '/PHPExcel/PHPExcel.php';
  15. require_once dirname(dirname(__FILE__)) . '/PHPExcel/PHPExcel/IOFactory.php';
  16. /**
  17. * Excel控制器
  18. * @package plugins\Excel\controller
  19. */
  20. class Excel extends Common
  21. {
  22. /**
  23. * 导出Excel
  24. * @param string $expTitle 文件名称
  25. * @param array $expCellName 表头
  26. * @param array $expTableData 数据
  27. * @param array $mergeCells 合并单元格
  28. * @author HongPing <hongping626@qq.com>
  29. * @alter 蔡伟明 <314013107@qq.com>
  30. */
  31. public function export($expTitle = '', $expCellName = [], $expTableData = [], $mergeCells = [])
  32. {
  33. $expTitle == '' && $this->error('请填写文件名');
  34. empty($expCellName) && $this->error('请填写表头');
  35. $file_type = 'xls';
  36. if (strpos($expTitle, '.')) {
  37. $file_name = explode('.', $expTitle);
  38. if (strtolower(end($file_name)) != 'xls' && strtolower(end($file_name)) != 'xlsx') {
  39. $file_name = $expTitle.'.xls';
  40. } else {
  41. $file_type = end($file_name);
  42. $file_name = $expTitle;
  43. }
  44. } else {
  45. $file_name = $expTitle.'.xls';
  46. }
  47. $file_type = strtolower($file_type) == 'xls' ? 'Excel5' : 'Excel2007';
  48. if (ob_get_length()) ob_end_clean();
  49. $fileName = $file_name; //or $xlsTitle 文件名称可根据自己情况设定
  50. $cellNum = count($expCellName);
  51. $dataNum = count($expTableData);
  52. $objPHPExcel = new \PHPExcel();
  53. $cellName = ['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','AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ','BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ','CA','CB','CC','CD','CE','CF','CG','CH','CI','CJ','CK','CL','CM','CN','CO','CP','CQ','CR','CS','CT','CU','CV','CW','CX','CY','CZ','DA','DB','DC','DD','DE','DF','DG','DH','DI','DJ','DK','DL','DM','DN','DO','DP','DQ','DR','DS','DT','DU','DV','DW','DX','DY','DZ','EA','EB','EC','ED','EE','EF','EG','EH','EI','EJ','EK','EL','EM','EN','EO','EP','EQ','ER','ES','ET','EU','EV','EW','EX','EY','EZ','FA','FB','FC','FD','FE','FF','FG','FH','FI','FJ','FK','FL','FM','FN','FO','FP','FQ','FR','FS','FT','FU','FV','FW','FX','FY','FZ','GA','GB','GC','GD','GE','GF','GG','GH','GI','GJ','GK','GL','GM','GN','GO','GP','GQ','GR','GS','GT','GU','GV','GW','GX','GY','GZ','HA','HB','HC','HD','HE','HF','HG','HH','HI','HJ','HK','HL','HM','HN','HO','HP','HQ','HR','HS','HT','HU','HV','HW','HX','HY','HZ'];
  54. //合并单元格
  55. if (!empty($mergeCells)) {
  56. foreach ($mergeCells as $mergeCell) {
  57. $objPHPExcel->getActiveSheet()->mergeCells($mergeCell);
  58. }
  59. }
  60. $objSheet = $objPHPExcel->getActiveSheet();
  61. for($i=0;$i<$cellNum;$i++){
  62. $objSheet->setCellValue($cellName[$i].'1', $expCellName[$i][2]);
  63. //根据内容设置单元格宽度
  64. $cellWidth = $expCellName[$i][1] == 'auto' ? strlen($expCellName[$i][2]) : $expCellName[$i][1];
  65. $objSheet->getColumnDimension($cellName[$i])->setWidth($cellWidth);
  66. // $objPHPExcel->getActiveSheet(0)->getColumnDimension($cellName[$i])->setWidth($expCellName[$i][1]);
  67. $objSheet->getStyle($cellName[$i])->getNumberFormat()->setFormatCode(\PHPExcel_Style_NumberFormat::FORMAT_TEXT);
  68. // if(isset($expCellName[$i][3])) {
  69. // switch ($expCellName[$i][3]) {
  70. // case 'FORMAT_NUMBER':
  71. // $objSheet->getStyle($cellName[$i])->getNumberFormat()->setFormatCode(\PHPExcel_Style_NumberFormat::FORMAT_NUMBER);
  72. // break;
  73. // case 'FORMAT_TEXT':
  74. // $objSheet->getStyle($cellName[$i])->getNumberFormat()->setFormatCode(\PHPExcel_Style_NumberFormat::FORMAT_TEXT);
  75. // break;
  76. // }
  77. // }
  78. }
  79. // Miscellaneous glyphs, UTF-8
  80. for($i=0;$i<$dataNum;$i++){
  81. for($j=0;$j<$cellNum;$j++){
  82. if (isset($expTableData[$i][$expCellName[$j][0]])) {
  83. $objSheet->setCellValueExplicit($cellName[$j].($i+2), $expTableData[$i][$expCellName[$j][0]], \PHPExcel_Cell_DataType::TYPE_STRING);
  84. }
  85. // if (isset($expCellName[$i][3]) && $expCellName[$i][3] == 'FORMAT_TEXT') {
  86. //
  87. // } else {
  88. // $objSheet->setCellValue($cellName[$j].($i+2), $expTableData[$i][$expCellName[$j][0]]);
  89. // }
  90. }
  91. }
  92. //下载
  93. header("Pragma: public");
  94. header("Expires: 0");
  95. header("Cache-Control:must-revalidate, post-check=0, pre-check=0");
  96. header("Content-Type:application/force-download");
  97. header("Content-Type:application/vnd.ms-execl");
  98. header("Content-Type:application/octet-stream");
  99. header("Content-Type:application/download");;
  100. header('Content-Disposition:attachment;filename='.$fileName);
  101. header("Content-Transfer-Encoding:binary");
  102. $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, $file_type);
  103. $objWriter->save('php://output');
  104. exit();
  105. }
  106. /**
  107. * 导入Excel
  108. * @param string $file upload file $_FILES
  109. * @param null $table 导入到指定表,如果不指定,则返回拼接的数据,不执行导入
  110. * @param null $fields 字段名 只导入指定字段名的数据,excel表中其他在字段名找不到的,将不导入
  111. * 传入格式:array('id' => 'id', '用户名' => 'username'),key为数据库字段名,value为excel表头名
  112. * @param int $type 导入模式,0默认为增量导入(导入并跳过已存在的数据),1为覆盖导入(导入并覆盖已存在的数据)
  113. * @param null $where 查询依据,做配合查询使用
  114. * @param null $main_field 作为判断导入模式依据的主要字段,比如指定为KSH,则用KSH这个字段来判断是否已存在数据库
  115. * @author HongPing <hongping626@qq.com>
  116. * @alter 蔡伟明 <314013107@qq.com>
  117. * @return array
  118. */
  119. public function import($file, $table = null, $fields = null, $type = 0, $where = null, $main_field = null)
  120. {
  121. if(!file_exists($file)){
  122. return ["error" => 1, 'message' => '文件未找到!']; //file not found!
  123. }
  124. $file_info = explode('.', $file);
  125. $file_ext = strtolower(end($file_info));
  126. if ($file_ext != 'xls' && $file_ext != 'xlsx') {
  127. return ["error" => 1, 'message' => '文件类型不正确!'];
  128. }
  129. $file_type = $file_ext == 'xls' ? 'Excel5' : 'Excel2007';
  130. $objReader = \PHPExcel_IOFactory::createReader($file_type); //需要在前面加反斜杠,因为命名空间
  131. try{
  132. $PHPReader = $objReader->load($file);
  133. }catch(\Exception $e){}
  134. if(!isset($PHPReader)) return ["error" => 1, 'message' => '读取错误!']; //read error!
  135. $allWorksheets = $PHPReader->getAllSheets(); //获取所有工作表
  136. $i = 0;
  137. $array = [];
  138. //循环读取每个工作表
  139. foreach($allWorksheets as $objWorksheet){
  140. $sheet_name = $objWorksheet->getTitle(); //获取当前工作表名
  141. $allRow = $objWorksheet->getHighestRow();//当前工作表的行数
  142. $highestColumn = $objWorksheet->getHighestColumn();//当前工作表的列数,excel以字母表示列数,比如B表示2列
  143. $allColumn = \PHPExcel_Cell::columnIndexFromString($highestColumn); //当前工作表的列数,将字母表示的列数转换为数字
  144. $array[$i]["Title"] = $sheet_name;
  145. $array[$i]["Cols"] = $allColumn;
  146. $array[$i]["Rows"] = $allRow;
  147. $arr = [];
  148. $isMergeCell = [];
  149. foreach ($objWorksheet->getMergeCells() as $cells) {//找出合并单元格
  150. foreach (\PHPExcel_Cell::extractAllCellReferencesInRange($cells) as $cellReference) {
  151. $isMergeCell[$cellReference] = true;
  152. }
  153. }
  154. //循环读取每行数据
  155. for($currentRow = 1; $currentRow <= $allRow; $currentRow++){
  156. $row = [];
  157. for($currentColumn = 0; $currentColumn < $allColumn; $currentColumn++){
  158. $cell = $objWorksheet->getCellByColumnAndRow($currentColumn, $currentRow);
  159. $afCol = \PHPExcel_Cell::stringFromColumnIndex($currentColumn+1); //后一列的索引,比如当前列为A,后一列则为B
  160. $bfCol = \PHPExcel_Cell::stringFromColumnIndex($currentColumn-1); //前一列的索引,如果当前列为第一列,则前一列索引为@
  161. $col = \PHPExcel_Cell::stringFromColumnIndex($currentColumn); //当前列的索引
  162. $address = $col.$currentRow;//当前单元格的位置
  163. $value = $objWorksheet->getCell($address)->getValue(); //当前单元格的值
  164. $value = trim($value); //去除首尾空格
  165. // 如果当前行的第一个单元格内容为空,则跳过当前行
  166. if ($currentColumn == 0 && ($value == '' || $value === NULL)) {
  167. break;
  168. }
  169. if(substr($value,0,1) == '='){//判断单元格的公式不完整的情况
  170. return ["error" => 1, 'message' => '无法使用公式!']; //can not use the formula!
  171. }
  172. //判断单元格是否为数字类型
  173. if($cell->getDataType() == \PHPExcel_Cell_DataType::TYPE_NUMERIC){
  174. // $cell_style_format=$cell->getParent()->getStyle( $cell->getCoordinate() )->getNumberFormat();
  175. $cell_style_format = $cell->getStyle($cell->getCoordinate())->getNumberFormat(); //不需要getParent
  176. $format_code = $cell_style_format->getFormatCode();
  177. if (preg_match('/^([$[A-Z]*-[0-9A-F]*])*[hmsdy]/i', $format_code)) { //判断是否为日期类型
  178. $value = gmdate("Y-m-d", \PHPExcel_Shared_Date::ExcelToPHP($value)); //格式化日期
  179. }else{
  180. $value = \PHPExcel_Style_NumberFormat::toFormattedString($value, $format_code); //格式化数字
  181. }
  182. }
  183. //处理该单元格为合并单元格的情况
  184. $temp = '';
  185. if(isset($isMergeCell[$col.$currentRow]) && isset($isMergeCell[$afCol.$currentRow]) && !empty($value)){
  186. $temp = $value;
  187. }elseif(isset($isMergeCell[$col.$currentRow]) && isset($isMergeCell[$col.($currentRow-1)]) && empty($value)){
  188. $value = $arr[$currentRow-1][$currentColumn];
  189. }elseif(isset($isMergeCell[$col.$currentRow]) && isset($isMergeCell[$bfCol.$currentRow]) && empty($value)){
  190. $value = $temp;
  191. }
  192. $row[$currentColumn] = $value;
  193. }
  194. //判断一行中的全部单元格是否都有数据
  195. //如果不是,给出提示
  196. // if (count($row) != 0 && count($row) != $allColumn) {
  197. // return ["error" => 4, 'message' => "工作表【{$sheet_name}】的第【{$currentRow}】行的数据不完整,请填写完整,并重新导入");
  198. // }
  199. $arr[$currentRow] = $row; //完整一行的数据
  200. }
  201. $array[$i]["Content"] = array_filter($arr);//过滤空行
  202. $i++;
  203. }
  204. // var_dump($array[0]);die;
  205. unset($objWorksheet);
  206. unset($PHPReader);
  207. unset($PHPExcel);
  208. // unlink($file);
  209. return ["error" => 0, 'message' => '导入成功','data'=>$array[0]];;
  210. // 没有指定表名,则直接返回拼接结果
  211. if ($table === null) {
  212. return ["error" => 0, "data" => $array];
  213. }
  214. if ($fields === null) {
  215. return ["error" => 2, 'message' => '未指定字段名!'];
  216. }
  217. if ($type != 0 && $type != 1) {
  218. return ["error" => 3, 'message' => '导入模式不正确!'];
  219. }
  220. if ($main_field === null) {
  221. return ["error" => 4, 'message' => '未指定用于判断依据的字段名!'];
  222. }
  223. if (!is_string($main_field)) {
  224. return ["error" => 5, 'message' => '用于判断依据的字段名只能为字符串类型!'];
  225. }
  226. //查询已经存在的数据,用于判断导入模式做对比
  227. $exists_list = Db::name($table)->where($where)->group($main_field)->column($main_field);
  228. //整理数据
  229. $fields = array_flip($fields); //反转键值
  230. $data_list = [];
  231. $dataAdd['list'] = [];
  232. $dataCover['list'] = [];
  233. $dataSkip['list'] = [];
  234. foreach ($array as $key => $value) { //循环每一张工作表
  235. $firstRow = [];
  236. foreach ($value['Content'] as $row => $col) { //循环每一行数据
  237. $data = [];
  238. if ($row == 1) { //处理excel表的第一行,即表头,用来获取表头与字段名的关系
  239. foreach ($col as $index => $val) { //循环每一个单元格
  240. if (isset($fields[$val])) {
  241. $firstRow[$index] = $fields[$val];
  242. }
  243. }
  244. } else { //这里开始是真正需要的数据
  245. if (empty($firstRow)) {
  246. return ["error" => 8, 'message' => '没有表头数据,无法导入!'];
  247. }
  248. foreach ($col as $index => $val) { //循环每一个单元格
  249. if (isset($firstRow[$index])) {
  250. $data[$firstRow[$index]] = trim($val);
  251. }
  252. }
  253. // 判断导入模式
  254. if ($type == 0) {//增量导入
  255. if (in_array($data[$main_field], $exists_list)) {
  256. $dataSkip['list'][] = $data[$main_field]; //记录跳过的数据
  257. continue;//跳过已存在的考生
  258. } else {
  259. $dataAdd['list'][] = $data[$main_field]; //记录新增的数据
  260. }
  261. } else {//覆盖导入
  262. if (in_array($data[$main_field], $exists_list)) {
  263. $dataCover['list'][] = $data[$main_field]; //记录覆盖的数据
  264. $map[$main_field] = $data[$main_field];
  265. Db::name($table)->where($where)->where($map)->delete();//删除已存在的数据
  266. } else {
  267. $dataAdd['list'][] = $data[$main_field]; //记录新增的数据
  268. }
  269. }
  270. $data_list[] = $data;
  271. }
  272. }
  273. }
  274. if ($data_list) {
  275. if (Db::name($table)->insertAll($data_list)) {
  276. //计算新增、覆盖、跳过的数量
  277. $dataAdd['total'] = count($dataAdd['list']);
  278. $dataCover['total'] = count($dataCover['list']);
  279. $dataSkip['total'] = count($dataSkip['list']);
  280. //将新增、覆盖、跳过的数据写入缓存
  281. cache('dataAdd', $dataAdd);
  282. cache('dataCover', $dataCover);
  283. cache('dataSkip', $dataSkip);
  284. cache('nextUrl', null);
  285. return ["error" => 0, 'message' => '成功导入 '. count($data_list). ' 条数据。'];
  286. } else {
  287. return ["error" => 9, 'message' => '导入失败!请重新导入。'];
  288. }
  289. } else {
  290. return ["error" => 10, 'message' => '上传的文件中,没有需要导入的数据!'];
  291. }
  292. }
  293. /**
  294. * 导出多工作表Excel
  295. * @param array $datas 数据,每个数组要包含'xls_name','xls_cell','data_list'
  296. * @param string $fileName 导出文件名
  297. * @author 蔡伟明 <314013107@qq.com>
  298. */
  299. public function exportMulti($datas = [], $fileName = '')
  300. {
  301. if (empty($datas)) {
  302. $this->error('找不到符合条件的数据');
  303. }
  304. $file_type = 'xls';
  305. if (strpos($fileName, '.')) {
  306. $file_name = explode('.', $fileName);
  307. if (strtolower(end($file_name)) != 'xls' && strtolower(end($file_name)) != 'xlsx') {
  308. $file_name = $fileName.'.xls';
  309. } else {
  310. $file_type = end($file_name);
  311. $file_name = $fileName;
  312. }
  313. } else {
  314. $file_name = $fileName.'.xls';
  315. }
  316. $file_type = strtolower($file_type) == 'xls' ? 'Excel5' : 'Excel2007';
  317. $objPHPExcel = new \PHPExcel();
  318. $fileName = $file_name == '' ? date('Y-m-d') : $file_name;
  319. $cellName = ['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','AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ','BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ','CA','CB','CC','CD','CE','CF','CG','CH','CI','CJ','CK','CL','CM','CN','CO','CP','CQ','CR','CS','CT','CU','CV','CW','CX','CY','CZ','DA','DB','DC','DD','DE','DF','DG','DH','DI','DJ','DK','DL','DM','DN','DO','DP','DQ','DR','DS','DT','DU','DV','DW','DX','DY','DZ','EA','EB','EC','ED','EE','EF','EG','EH','EI','EJ','EK','EL','EM','EN','EO','EP','EQ','ER','ES','ET','EU','EV','EW','EX','EY','EZ','FA','FB','FC','FD','FE','FF','FG','FH','FI','FJ','FK','FL','FM','FN','FO','FP','FQ','FR','FS','FT','FU','FV','FW','FX','FY','FZ','GA','GB','GC','GD','GE','GF','GG','GH','GI','GJ','GK','GL','GM','GN','GO','GP','GQ','GR','GS','GT','GU','GV','GW','GX','GY','GZ','HA','HB','HC','HD','HE','HF','HG','HH','HI','HJ','HK','HL','HM','HN','HO','HP','HQ','HR','HS','HT','HU','HV','HW','HX','HY','HZ'];
  320. //工作表序号
  321. $sheetIndex = 0;
  322. foreach ($datas as $key => $data) {
  323. // $xlsTitle = iconv('utf-8', 'gb2312', $data['xls_name']);//文件名称
  324. $xlsTitle = $data['xls_name'];//文件名称
  325. $cellNum = count($data['xls_cell']);
  326. $dataNum = count($data['data_list']);
  327. //如果当前工作表序号大于0,则创建新表
  328. if ($sheetIndex > 0) {
  329. $objPHPExcel->createSheet();
  330. }
  331. //设置表头
  332. for($i = 0; $i < $cellNum; $i++){
  333. //设置当前活动表,并设置值
  334. $objPHPExcel->setActiveSheetIndex($sheetIndex)->setCellValue($cellName[$i].'1', $data['xls_cell'][$i][2]);
  335. //得到当前活动的表
  336. $objActSheet = $objPHPExcel->getActiveSheet();
  337. // 给当前活动的表设置名称
  338. $objActSheet->setTitle($xlsTitle);
  339. //根据内容设置单元格宽度
  340. $cellWidth = $data['xls_cell'][$i][1] == 'auto' ? strlen($data['xls_cell'][$i][2]) : $data['xls_cell'][$i][1];
  341. $objActSheet->getColumnDimension($cellName[$i])->setWidth($cellWidth); //内容自适应
  342. //设置指定数据类型
  343. if(isset($data['xls_cell'][$i][3]) && $data['xls_cell'][$i][3] == 'FORMAT_NUMBER'){
  344. $objActSheet->getStyle($cellName[$i])->getNumberFormat()->setFormatCode(\PHPExcel_Style_NumberFormat::FORMAT_NUMBER);
  345. }
  346. }
  347. //设置具体内容
  348. for($i=0; $i < $dataNum; $i++){
  349. for($j=0; $j< $cellNum; $j++){
  350. $objActSheet->setCellValueExplicit($cellName[$j].($i+2), $data['data_list'][$i][$data['xls_cell'][$j][0]], \PHPExcel_Cell_DataType::TYPE_STRING);
  351. // $objActSheet->setCellValue($cellName[$j].($i+2), $data['data_list'][$i][$data['xls_cell'][$j][0]]);
  352. }
  353. }
  354. $sheetIndex++;
  355. }
  356. //将当前活动表设置为第一个
  357. $objPHPExcel->setActiveSheetIndex(0);
  358. header("Pragma: public");
  359. header("Expires: 0");
  360. header("Cache-Control:must-revalidate, post-check=0, pre-check=0");
  361. header("Content-Type:application/force-download");
  362. header("Content-Type:application/vnd.ms-execl");
  363. header("Content-Type:application/octet-stream");
  364. header("Content-Type:application/download");;
  365. header('Content-Disposition:attachment;filename='.$fileName);
  366. header("Content-Transfer-Encoding:binary");
  367. $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, $file_type);
  368. if (ob_get_length()) ob_end_clean();
  369. $objWriter->save('php://output');
  370. exit();
  371. }
  372. }