├── .gitignore ├── composer.json ├── LICENSE ├── README.md └── src └── Excel.php /.gitignore: -------------------------------------------------------------------------------- 1 | # phpstorm project files 2 | .idea -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jianyan74/php-excel", 3 | "description": "php excel 导入导出", 4 | "keywords": ["excel", "csv", "xlsx", "xls", "html", "jianyan74"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "jianyan74" 9 | } 10 | ], 11 | "type": "extension", 12 | "require": { 13 | "php": ">=7.0", 14 | "phpoffice/phpspreadsheet": "^1.3" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "jianyan\\excel\\": "./src" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Max.wen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-excel 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/jianyan74/php-excel/v/stable)](https://packagist.org/packages/jianyan74/php-excel) 4 | [![Total Downloads](https://poser.pugx.org/jianyan74/php-excel/downloads)](https://packagist.org/packages/jianyan74/php-excel) 5 | [![License](https://poser.pugx.org/jianyan74/php-excel/license)](https://packagist.org/packages/jianyan74/php-excel) 6 | 7 | ## 安装 8 | 9 | ``` 10 | composer require jianyan74/php-excel 11 | ``` 12 | 13 | 引入 14 | 15 | ``` 16 | use jianyan\excel\Excel; 17 | ``` 18 | 19 | ## Demo 20 | 21 | ``` 22 | // [名称, 字段名, 类型, 类型规则] 23 | $header = [ 24 | ['ID', 'id', 'text'], 25 | ['手机号码', 'mobile'], // 规则不填默认text 26 | ['openid', 'fans.openid', 'text'], 27 | ['昵称', 'fans.nickname', 'text'], 28 | ['关注/扫描', 'type', 'selectd', [1 => '关注', 2 => '扫描']], 29 | ['性别', 'sex', 'function', function($model){ 30 | return $model['sex'] == 1 ? '男' : '女'; 31 | }], 32 | ['创建时间', 'created_at', 'date', 'Y-m-d'], 33 | ['图片', 'image', 'text'],// 本地图片 ./images/765456898612.jpg 34 | ]; 35 | 36 | $list = [ 37 | [ 38 | 'id' => 1, 39 | 'type' => 1, 40 | 'mobile' => '18888888888', 41 | 'fans' => [ 42 | 'openid' => '123', 43 | 'nickname' => '昵称', 44 | ], 45 | 'sex' => 1, 46 | 'create_at' => time(), 47 | ] 48 | ]; 49 | ``` 50 | 51 | ### 导出 52 | 53 | ``` 54 | // 简单使用 55 | return Excel::exportData($list, $header); 56 | 57 | // 定制 默认导出xlsx 支持 : xlsx/xls/html/csv, 支持写入绝对路径 58 | return Excel::exportData($list, $header, '测试', 'xlsx', '/www/data/'); 59 | 60 | // 另外一种导出csv方式 61 | return Excel::exportCsvData($list, $header); 62 | 63 | // 带图片的 64 | * @param array $list 数据 65 | * @param array $header 数据处理格式 66 | * @param string $filename 导出的文件名 67 | * @param string $suffix 导出的格式 68 | * @param string $path 导出的存放地址 无则不在服务器存放 69 | * @param string $image 导出的格式 可以用 大写字母 或者 数字 标识 哪一列 70 | Excel::exportData($list, $header,date('Y-m-d h:i:s'),'xlsx','',['D','E']); 71 | Excel::exportData($list, $header,date('Y-m-d h:i:s'),'xlsx','',[4,5]); 72 | 73 | 74 | ``` 75 | 76 | ### 导入 77 | 78 | ``` 79 | /** 80 | * 导入 81 | * 82 | * @param $filePath excel的服务器存放地址 可以取临时地址 83 | * @param int $startRow 开始和行数 默认1 84 | * @param bool $hasImg 导出的时候是否有图片 85 | * @param string $suffix 格式 86 | * @param string $imageFilePath 作为临时使用的 图片存放的地址 87 | * @return array|bool|mixed 88 | */ 89 | $data = Excel::import($filePath, $startRow = 1,$hasImg = false,$suffix = 'Xlsx',$imageFilePath = null); 90 | ``` 91 | 92 | ### 问题反馈 93 | 94 | 在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流 95 | 96 | QQ群:[655084090](https://jq.qq.com/?_wv=1027&k=4BeVA2r) 97 | 98 | -------------------------------------------------------------------------------- /src/Excel.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class Excel 24 | { 25 | /** 26 | * 导出Excel 27 | * 28 | * @param array $list 数据 29 | * @param array $header 数据处理格式 30 | * @param string $filename 导出的文件名 31 | * @param string $suffix 导出的格式 32 | * @param string $path 导出的存放地址 无则不在服务器存放 33 | * @param string $image 导出的格式 可以用 大写字母 或者 数字 标识 哪一列 34 | * @return bool 35 | * @throws \PhpOffice\PhpSpreadsheet\Exception 36 | * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception 37 | */ 38 | public static function exportData( 39 | $list = [], 40 | $header = [], 41 | $filename = '', 42 | $suffix = 'xlsx', 43 | $path = '', 44 | $image = [] 45 | ) { 46 | if (!is_array($list) || !is_array($header)) { 47 | return false; 48 | } 49 | 50 | // 清除之前的错误输出 51 | ob_end_clean(); 52 | ob_start(); 53 | 54 | !$filename && $filename = time(); 55 | 56 | // 初始化 57 | $spreadsheet = new Spreadsheet(); 58 | $sheet = $spreadsheet->getActiveSheet(); 59 | // 写入头部 60 | $hk = 1; 61 | foreach ($header as $k => $v) { 62 | $sheet->setCellValue(Coordinate::stringFromColumnIndex($hk) . '1', $v[0]); 63 | $hk += 1; 64 | } 65 | 66 | // 开始写入内容 67 | $column = 2; 68 | $size = ceil(count($list) / 500); 69 | for ($i = 0; $i < $size; $i++) { 70 | $buffer = array_slice($list, $i * 500, 500); 71 | 72 | foreach ($buffer as $k => $row) { 73 | $span = 1; 74 | 75 | foreach ($header as $key => $value) { 76 | // 解析字段 77 | $realData = self::formatting($header[$key], trim(self::formattingField($row, $value[1])), $row); 78 | // 写入excel 79 | $rowR = Coordinate::stringFromColumnIndex($span); 80 | $sheet->getColumnDimension($rowR)->setWidth(20); 81 | if (in_array($span, $image) || in_array($rowR, $image)) { // 如果这一列应该是图片 82 | if (file_exists($realData)) { // 本地文件 83 | $drawing = new Drawing(); 84 | $drawing->setName('image'); 85 | $drawing->setDescription('image'); 86 | try { 87 | $drawing->setPath($realData); 88 | } catch (\Exception $e) { 89 | echo $e->getMessage(); 90 | echo '
可能是图片丢失了或者无权限'; 91 | die; 92 | } 93 | 94 | $drawing->setWidth(80); 95 | $drawing->setHeight(80); 96 | $drawing->setCoordinates($rowR . $column);//A1 97 | $drawing->setOffsetX(12); 98 | $drawing->setOffsetY(12); 99 | $drawing->setWorksheet($spreadsheet->getActiveSheet()); 100 | } else { // 可能是 网络文件 101 | $img = self::curlGet($realData); 102 | $file_info = pathinfo($realData); 103 | $extension = $file_info['extension'];// 文件后缀 104 | $dir = '.' . DIRECTORY_SEPARATOR . 'execlImg' . DIRECTORY_SEPARATOR . \date('Y-m-d') . DIRECTORY_SEPARATOR;// 文件夹名 105 | $basename = time() . mt_rand(1000, 9999) . '.' . $extension;// 文件名 106 | is_dir($dir) or mkdir($dir, 0777, true); //进行检测文件夹是否存在 107 | file_put_contents($dir . $basename, $img); 108 | $drawing = new Drawing(); 109 | $drawing->setName('image'); 110 | $drawing->setDescription('image'); 111 | try { 112 | $drawing->setPath($dir . $basename); 113 | } catch (\Exception $e) { 114 | echo $e->getMessage(); 115 | echo '
可能是图片丢失了或者无权限'; 116 | die; 117 | } 118 | 119 | $drawing->setWidth(80); 120 | $drawing->setHeight(80); 121 | $drawing->setCoordinates($rowR . $column);//A1 122 | $drawing->setOffsetX(12); 123 | $drawing->setOffsetY(12); 124 | $drawing->setWorksheet($spreadsheet->getActiveSheet()); 125 | } 126 | } else { 127 | // $sheet->setCellValue($rowR . $column, $realData); 128 | // 写入excel 129 | $sheet->setCellValueExplicit(Coordinate::stringFromColumnIndex($span) . $column, $realData, DataType::TYPE_STRING); 130 | } 131 | 132 | 133 | $span++; 134 | } 135 | 136 | $column++; 137 | unset($buffer[$k]); 138 | } 139 | } 140 | 141 | // 直接输出下载 142 | switch ($suffix) { 143 | case 'xlsx' : 144 | $writer = new Xlsx($spreadsheet); 145 | if (!empty($path)) { 146 | $writer->save($path); 147 | } else { 148 | header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8;"); 149 | header("Content-Disposition: inline;filename=\"{$filename}.xlsx\""); 150 | header('Cache-Control: max-age=0'); 151 | $writer->save('php://output'); 152 | } 153 | exit(); 154 | 155 | break; 156 | case 'xls' : 157 | $writer = new Xls($spreadsheet); 158 | if (!empty($path)) { 159 | $writer->save($path); 160 | } else { 161 | header("Content-Type:application/vnd.ms-excel;charset=utf-8;"); 162 | header("Content-Disposition:inline;filename=\"{$filename}.xls\""); 163 | header('Cache-Control: max-age=0'); 164 | $writer->save('php://output'); 165 | } 166 | exit(); 167 | 168 | break; 169 | case 'csv' : 170 | $writer = new Csv($spreadsheet); 171 | if (!empty($path)) { 172 | $writer->save($path); 173 | } else { 174 | header("Content-type:text/csv;charset=utf-8;"); 175 | header("Content-Disposition:attachment; filename={$filename}.csv"); 176 | header('Cache-Control: max-age=0'); 177 | $writer->save('php://output'); 178 | } 179 | exit(); 180 | 181 | break; 182 | case 'html' : 183 | $writer = new Html($spreadsheet); 184 | if (!empty($path)) { 185 | $writer->save($path); 186 | } else { 187 | header("Content-Type:text/html;charset=utf-8;"); 188 | header("Content-Disposition:attachment;filename=\"{$filename}.{$suffix}\""); 189 | header('Cache-Control: max-age=0'); 190 | $writer->save('php://output'); 191 | } 192 | exit(); 193 | 194 | break; 195 | } 196 | 197 | return true; 198 | } 199 | 200 | /** 201 | * 导出的另外一种形式(不建议使用) 202 | * 203 | * @param array $list 204 | * @param array $header 205 | * @param string $filename 206 | * @return bool 207 | */ 208 | public static function exportCsvData($list = [], $header = [], $filename = '') 209 | { 210 | if (!is_array($list) || !is_array($header)) { 211 | return false; 212 | } 213 | 214 | // 清除之前的错误输出 215 | ob_end_clean(); 216 | ob_start(); 217 | 218 | !$filename && $filename = time(); 219 | 220 | $html = "\xEF\xBB\xBF"; 221 | foreach ($header as $k => $v) { 222 | $html .= $v[0] . "\t ,"; 223 | } 224 | 225 | $html .= "\n"; 226 | 227 | if (!empty($list)) { 228 | $info = []; 229 | $size = ceil(count($list) / 500); 230 | 231 | for ($i = 0; $i < $size; $i++) { 232 | $buffer = array_slice($list, $i * 500, 500); 233 | 234 | foreach ($buffer as $k => $row) { 235 | $data = []; 236 | 237 | foreach ($header as $key => $value) { 238 | // 解析字段 239 | $realData = self::formatting($header[$key], trim(self::formattingField($row, $value[1])), $row); 240 | $data[] = str_replace(PHP_EOL, '', $realData); 241 | } 242 | 243 | $info[] = implode("\t ,", $data) . "\t ,"; 244 | unset($data, $buffer[$k]); 245 | } 246 | } 247 | 248 | $html .= implode("\n", $info); 249 | } 250 | 251 | header("Content-type:text/csv"); 252 | header("Content-Disposition:attachment; filename={$filename}.csv"); 253 | echo $html; 254 | exit(); 255 | } 256 | 257 | /** 258 | * 导入 259 | * 260 | * @param $filePath excel的服务器存放地址 可以取临时地址 261 | * @param int $startRow 开始和行数 262 | * @param bool $hasImg 导出的时候是否有图片 263 | * @param string $suffix 格式 264 | * @param string $imageFilePath 作为临时使用的 图片存放的地址 265 | * @return array|mixed 266 | * @throws Exception 267 | * @throws \PhpOffice\PhpSpreadsheet\Exception 268 | * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception 269 | */ 270 | public static function import($filePath, $startRow = 1, $hasImg = false, $suffix = 'Xlsx', $imageFilePath = null) 271 | { 272 | if ($hasImg) { 273 | if ($imageFilePath == null) { 274 | $imageFilePath = '.' . DIRECTORY_SEPARATOR . 'execlImg' . DIRECTORY_SEPARATOR . \date('Y-m-d') . DIRECTORY_SEPARATOR; 275 | } 276 | if (!file_exists($imageFilePath)) { 277 | //如果目录不存在则递归创建 278 | mkdir($imageFilePath, 0777, true); 279 | } 280 | } 281 | $reader = IOFactory::createReader($suffix); 282 | if (!$reader->canRead($filePath)) { 283 | throw new Exception('不能读取Excel'); 284 | } 285 | 286 | $spreadsheet = $reader->load($filePath); 287 | $sheetCount = $spreadsheet->getSheetCount();// 获取sheet(工作表)的数量 288 | 289 | // 获取所有的sheet表格数据 290 | $excleDatas = []; 291 | $emptyRowNum = 0; 292 | for ($i = 0; $i < $sheetCount; $i++) { 293 | $objWorksheet = $spreadsheet->getSheet($i); // 读取excel文件中的第一个工作表 294 | $data = $objWorksheet->toArray(); 295 | if ($hasImg) { 296 | foreach ($objWorksheet->getDrawingCollection() as $drawing) { 297 | list($startColumn, $startRow) = Coordinate::coordinateFromString($drawing->getCoordinates()); 298 | $imageFileName = $drawing->getCoordinates() . mt_rand(1000, 9999); 299 | $imageFileName .= '.' . $drawing->getExtension(); 300 | $source = imagecreatefromjpeg($drawing->getPath()); 301 | imagejpeg($source, $imageFilePath . $imageFileName); 302 | 303 | $startColumn = self::ABC2decimal($startColumn); 304 | $data[$startRow - 1][$startColumn] = $imageFilePath . $imageFileName; 305 | } 306 | } 307 | $excleDatas[$i] = $data; // 多个sheet的数组的集合 308 | } 309 | 310 | // 这里我只需要用到第一个sheet的数据,所以只返回了第一个sheet的数据 311 | $returnData = $excleDatas ? array_shift($excleDatas) : []; 312 | 313 | // 第一行数据就是空的,为了保留其原始数据,第一行数据就不做array_fiter操作; 314 | $returnData = $returnData && isset($returnData[$startRow]) && !empty($returnData[$startRow]) ? array_filter($returnData) : $returnData; 315 | 316 | return $returnData; 317 | } 318 | 319 | private static function ABC2decimal($abc) 320 | { 321 | $ten = 0; 322 | $len = strlen($abc); 323 | for ($i = 1; $i <= $len; $i++) { 324 | $char = substr($abc, 0 - $i, 1);//反向获取单个字符 325 | 326 | $int = ord($char); 327 | $ten += ($int - 65) * pow(26, $i - 1); 328 | } 329 | 330 | return $ten; 331 | } 332 | 333 | /** 334 | * 格式化内容 335 | * 336 | * @param array $array 头部规则 337 | * @return false|mixed|null|string 内容值 338 | */ 339 | protected static function formatting(array $array, $value, $row) 340 | { 341 | !isset($array[2]) && $array[2] = 'text'; 342 | 343 | switch ($array[2]) { 344 | // 文本 345 | case 'text' : 346 | return $value; 347 | break; 348 | // 日期 349 | case 'date' : 350 | return !empty($value) ? date($array[3], $value) : null; 351 | break; 352 | // 选择框 353 | case 'selectd' : 354 | return $array[3][$value] ?? null; 355 | break; 356 | // 匿名函数 357 | case 'function' : 358 | return isset($array[3]) ? call_user_func($array[3], $row) : null; 359 | break; 360 | // 默认 361 | default : 362 | 363 | break; 364 | } 365 | 366 | return null; 367 | } 368 | 369 | /** 370 | * 解析字段 371 | * 372 | * @param $row 373 | * @param $field 374 | * @return mixed 375 | */ 376 | protected static function formattingField($row, $field) 377 | { 378 | $newField = explode('.', $field); 379 | if (count($newField) == 1) { 380 | if (isset($row[$field])) { 381 | return $row[$field]; 382 | } else { 383 | return false; 384 | } 385 | } 386 | 387 | foreach ($newField as $item) { 388 | if (isset($row[$item])) { 389 | $row = $row[$item]; 390 | } else { 391 | break; 392 | } 393 | } 394 | 395 | return is_array($row) ? false : $row; 396 | } 397 | 398 | public static function curlGet($url) 399 | { 400 | $ch = \curl_init(); 401 | \curl_setopt($ch, CURLOPT_URL, $url); 402 | \curl_setopt($ch, CURLOPT_HEADER, 0); 403 | \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 404 | \curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 这个是重点 请求https。 405 | $data = \curl_exec($ch); 406 | \curl_close($ch); 407 | 408 | return $data; 409 | } 410 | } --------------------------------------------------------------------------------