├── .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 | [](https://packagist.org/packages/jianyan74/php-excel)
4 | [](https://packagist.org/packages/jianyan74/php-excel)
5 | [](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 | }
--------------------------------------------------------------------------------