Browse Source

产品导入导出到excel

master
李可松 4 years ago
parent
commit
f21eb13cc8
  1. 59
      app/AdminSupplier/Actions/Grid/ExportToExcelButton.php
  2. 59
      app/AdminSupplier/Actions/Grid/ImportFromExcelButton.php
  3. 21
      app/AdminSupplier/Controllers/ProductController.php
  4. 18
      app/AdminSupplier/Extensions/ProductToExcelExporter.php
  5. 52
      app/AdminSupplier/Forms/ExcelImport.php
  6. 100
      app/Exports/ProductExport.php
  7. 176
      app/Imports/ProductImport.php
  8. 63
      app/Imports/ProductSpecImport.php
  9. 45
      app/Jobs/ExportProductToExcel.php
  10. 63
      app/Jobs/ImportProductFromExcel.php
  11. 4
      composer.json
  12. 520
      composer.lock
  13. 328
      config/excel.php
  14. 14
      tests/Feature/ExampleTest.php

59
app/AdminSupplier/Actions/Grid/ExportToExcelButton.php

@ -0,0 +1,59 @@
<?php
namespace App\AdminSupplier\Actions\Grid;
use App\Jobs\ExportProductToExcel;
use Dcat\Admin\Grid\Tools\AbstractTool;
use Dcat\Admin\Traits\HasPermissions;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
class ExportToExcelButton extends AbstractTool
{
/**
* @return string
*/
protected $title = '导入所有产品到excel';
public function html()
{
$this->appendHtmlAttribute('class', 'btn btn-primary btn-outline');
return <<<HTML
<button {$this->formatHtmlAttributes()}>{$this->title()}</button>
HTML;
}
public function handle(Request $request)
{
ExportProductToExcel::dispatch(\Admin::user()->id);
return $this->response()->success('导出成功,稍后到导出列表下载');
}
/**
* @return string|array|void
*/
public function confirm()
{
// return ['Confirm?', 'contents'];
}
/**
* @param Model|Authenticatable|HasPermissions|null $user
*
* @return bool
*/
protected function authorize($user): bool
{
return true;
}
/**
* @return array
*/
protected function parameters()
{
return [];
}
}

59
app/AdminSupplier/Actions/Grid/ImportFromExcelButton.php

@ -0,0 +1,59 @@
<?php
namespace App\AdminSupplier\Actions\Grid;
use Dcat\Admin\Grid\Tools\AbstractTool;
use Dcat\Admin\Traits\HasPermissions;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
class ImportFromExcelButton extends AbstractTool
{
/**
* @return string
*/
protected $title = '从Excel导入';
public function html()
{
$this->appendHtmlAttribute('class', 'btn btn-primary btn-outline');
return <<<HTML
<a {$this->formatHtmlAttributes()}>{$this->title()}</a>
HTML;
}
/**
* @return string|void
*/
protected function href()
{
return admin_url('product/import');
}
/**
* @return string|array|void
*/
public function confirm()
{
// return ['Confirm?', 'contents'];
}
/**
* @param Model|Authenticatable|HasPermissions|null $user
*
* @return bool
*/
protected function authorize($user): bool
{
return true;
}
/**
* @return array
*/
protected function parameters()
{
return [];
}
}

21
app/AdminSupplier/Controllers/ProductController.php

@ -2,27 +2,36 @@
namespace App\AdminSupplier\Controllers;
use App\AdminSupplier\Actions\Grid\ExportToExcelButton;
use App\AdminSupplier\Actions\Grid\ImportFromExcelButton;
use App\AdminSupplier\Extensions\Form\Spec;
use App\AdminSupplier\Forms\ExcelImport;
use App\AdminSupplier\Repositories\Product;
use App\Common\OrderStatus;
use App\Common\ProductStatus;
use App\Jobs\ProductSpecSync;
use App\Models\AgentProduct;
use App\Models\AgentProductItem;
use App\Models\AgentProductSpec;
use App\Models\Category;
use App\Models\DiyForm;
use App\Models\Order;
use App\Models\ProductSpec;
use Dcat\Admin\Admin;
use Dcat\Admin\Form;
use Dcat\Admin\Form\NestedForm;
use Dcat\Admin\Grid;
use Dcat\Admin\Layout\Content;
use Dcat\Admin\Show;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Widgets\Card;
class ProductController extends AdminController
{
protected $title = '产品';
public function import(Content $content): Content
{
return $content->title('从Excel导入产品')->body(new Card(new ExcelImport()));
}
/**
* Make a grid builder.
*
@ -33,6 +42,10 @@ class ProductController extends AdminController
return Grid::make(new Product(['category:id,name']), function (Grid $grid) {
$grid->model()->where('supplier_id', Admin::user()->id);
$grid->tools(new ImportFromExcelButton());
$grid->tools(new ExportToExcelButton());
// $grid->export(new ProductToExcelExporter);
$category_id = request()->input('cid');
if ($category_id) {
$grid->model()->whereIn('category_id', [$category_id, ...$this->get_category_child_ids($category_id)]);
@ -139,7 +152,9 @@ class ProductController extends AdminController
protected function form()
{
Admin::user()->publish_type = json_decode(Admin::user()->publish_type, true);
// Form::extend('spec', Spec::class);
return Form::make(new Product(['spec']), function (Form $form) {
// $form->spec('spec', '产品规格')->required();
//不允许编辑非自己数据
if ($form->isEditing() && $form->model()->supplier_id != Admin::user()->id) {
return $form->response()->error('数据不存在');

18
app/AdminSupplier/Extensions/ProductToExcelExporter.php

@ -0,0 +1,18 @@
<?php
namespace App\AdminSupplier\Extensions;
use App\Exports\ProductExport;
use Dcat\Admin\Admin;
use Dcat\Admin\Grid\Exporters\AbstractExporter;
use Maatwebsite\Excel\Facades\Excel;
/**
* Excel导出功能
*/
class ProductToExcelExporter extends AbstractExporter
{
public function export()
{
return Excel::download(new ProductExport(Admin::user()->id), '导出产品-' . date('Y-m-d H:i:s') . '.xlsx')->send();
}
}

52
app/AdminSupplier/Forms/ExcelImport.php

@ -0,0 +1,52 @@
<?php
namespace App\AdminSupplier\Forms;
use App\Jobs\ImportProductFromExcel;
use Dcat\Admin\Widgets\Form;
use Illuminate\Support\Facades\Storage;
class ExcelImport extends Form
{
/**
* Handle the form request.
*
* @param array $input
*
* @return mixed
*/
public function handle(array $input)
{
foreach ($input['zip_file'] as $zip_file) {
/*# 使用md5_file来重命名文件,保证唯一
$to_path = str_replace(basename($zip_file), '', $zip_file);
$to_path .= md5_file(Storage::disk('public')->path($zip_file)) . '.zip';
Storage::disk('public')->move($zip_file, $to_path);*/
ImportProductFromExcel::dispatch(\Admin::user()->id, Storage::disk('public')->path($zip_file));
}
return $this->response()->success('操作成功,导入产品可能需要几分钟,请勿重复导入,请耐心等待')->refresh();
}
/**
* Build a form here.
*/
public function form()
{
$this->multipleFile('zip_file', 'ZIP文件')
->help('注意:请上传zip文件,上传文件大小不能超过' . ini_get('upload_max_filesize') .
'。<a href="' . Storage::disk('public')->url('import.zip') . '" target="_blank">文件导入模板</a>')
->accept('zip')
->uniqueName()->autoUpload()->required();
}
/**
* The data of the form.
*
* @return array
*/
public function default()
{
return [];
}
}

100
app/Exports/ProductExport.php

@ -0,0 +1,100 @@
<?php
namespace App\Exports;
use App\Common\ProductStatus;
use App\Models\Product;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithHeadings;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class ProductExport implements FromQuery, WithHeadings, WithColumnFormatting
{
private int $supplier_id;
public function __construct(int $supplier_id)
{
$this->supplier_id = $supplier_id;
}
public function query()
{
return Product::with('category:id,name')->where('supplier_id', $this->supplier_id);
}
public function prepareRows($rows)
{
return $rows->transform(function ($row) {
return [
'id' => $row->id,
'status' => ProductStatus::array()[$row->status] ?? '',
'category_name' => $row->category?->name ?? '',
'title' => $row->title,
'price' => $row->price,
'original_price' => $row->original_price,
'stock' => $row->stock,
'know' => $row->know,
'content' => $row->content,
'verify_mobile' => $row->verify_mobile,
'diy_form_id' => $row->diy_form_id,
'出发地' => $row->extends['field_0_departure_place'] ?? '',
'出发地经度' => $row->extends['field_0_departure_place_longitude'] ?? '',
'出发地纬度' => $row->extends['field_0_departure_place_latitude'] ?? '',
'目的地' => $row->extends['field_0_destination'] ?? '',
'目的地经度' => $row->extends['field_0_destination_longitude'] ?? '',
'目的地纬度' => $row->extends['field_0_destination_latitude'] ?? '',
'行程起始时间' => $row->extends['field_0_date?->start'] ?? '',
'行程结束时间' => $row->extends['field_0_date?->end'] ?? '',
'酒店名' => $row->extends['field_1_name'] ?? '',
'酒店地址' => $row->extends['field_1_address'] ?? '',
'酒店经度' => $row->extends['field_1_longitude'] ?? '',
'酒店纬度' => $row->extends['field_1_latitude'] ?? '',
'景区名' => $row->extends['field_2_name'] ?? '',
'景区地址' => $row->extends['field_2_address'] ?? '',
'景区经度' => $row->extends['field_2_longitude'] ?? '',
'景区纬度' => $row->extends['field_2_latitude'] ?? '',
'餐厅名' => $row->extends['field_3_name'] ?? '',
'餐厅地址' => $row->extends['field_3_address'] ?? '',
'餐厅经度' => $row->extends['field_3_longitude'] ?? '',
'餐厅纬度' => $row->extends['field_3_latitude'] ?? '',
'交通地址' => $row->extends['field_4_address'] ?? '',
'交通经度' => $row->extends['field_4_longitude'] ?? '',
'交通纬度' => $row->extends['field_4_latitude'] ?? '',
'购物地址' => $row->extends['field_5_address'] ?? '',
'购物经度' => $row->extends['field_5_longitude'] ?? '',
'购物纬度' => $row->extends['field_5_latitude'] ?? '',
];
});
}
public function map($row): array
{
return [
$row->id,
$row->category->name,
];
}
public function headings(): array
{
return [
'ID', '状态', '分类', '产品标题', '销售价', '市场价', '库存', '旅客须知', '产品详情', '核销手机号', '信息收集表单ID',
'出发地', '出发地经度', '出发地纬度', '目的地', '目的地经度', '目的地纬度', '行程起始时间', '行程结束时间',
'酒店名', '酒店地址', '酒店经度', '酒店纬度',
'景区名', '景区地址', '景区经度', '景区纬度',
'餐厅名', '餐厅地址', '餐厅经度', '餐厅纬度',
'交通地址', '交通经度', '交通纬度',
'购物地址', '购物经度', '购物纬度',
];
}
public function columnFormats(): array
{
for ($i = 65; $i <= 90; $i++) {
$format[chr($i)] = NumberFormat::FORMAT_TEXT;
}
return $format;
}
}

176
app/Imports/ProductImport.php

@ -0,0 +1,176 @@
<?php
namespace App\Imports;
use App\Models\Category;
use App\Models\Product;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Facades\Excel;
class ProductImport implements ToCollection
{
private int $supplier_id;
private string $extract_path;
private array $keys = [];
public function __construct(int $supplier_id, $extract_path)
{
$this->supplier_id = $supplier_id;
$this->extract_path = $extract_path;
}
public function collection(Collection $collection)
{
if ($collection->isEmpty()) {
return;
}
//校验数组count()是否正确
if (count($collection[0]) < 35) {
throw new \Exception('Excel产品信息格式不正确');
}
$this->keys = array_flip($collection[0]->toArray());
//去除第一行的标题
unset($collection[0]);
//$collection
$this->createData($collection);
}
private function createData($rows)
{
$keys = $this->keys;
foreach ($rows as $product_index => $row) {
$category = Category::where(['agent_id' => 0, 'name' => trim($row[$keys['分类']])])->first();
if (!$category) continue;
$insert_data = [
'supplier_id' => $this->supplier_id,
'category_id' => $category->id,
'type' => $category->publish_type,
'title' => $row[$keys['产品标题']] ?? '',
'price' => $row[$keys['销售价']] ?? 0,
'original_price' => $row[$keys['市场价']] ?? 0,
'stock' => $row[$keys['库存']] ?? 0,
'know' => $row[$keys['旅客须知']] ?? '',
'content' => $row[$keys['产品详情']] ?? '',
'verify_mobile' => $row[$keys['核销手机号']] ?? '',
'diy_form_id' => $row[$keys['信息收集表单ID']] ?? '',
'pictures' => $this->get_pictures($product_index),
];
# 扩展字段
$insert_data['extends'] = $this->get_extends($category->publish_type, $row);
if ($category->publish_type == 0) {
$insert_data['longitude'] = $insert_data['extends']['field_0_departure_place_longitude'] ?? 0;
$insert_data['latitude'] = $insert_data['extends']['field_0_departure_place_latitude'] ?? 0;
$insert_data['address'] = $insert_data['extends']['field_0_departure_place'] ?? '';
} else {
$insert_data['longitude'] = $insert_data['extends']['field_'.$category->publish_type.'_longitude'] ?? 0;
$insert_data['latitude'] = $insert_data['extends']['field_'.$category->publish_type.'_latitude'] ?? 0;
$insert_data['address'] = $insert_data['extends']['field_'.$category->publish_type.'_address'] ?? '';
}
$product = Product::create($insert_data);
if ($product->id) {
$this->insert_spec($product_index, $product->id);
}
}
}
# 插入规格
private function insert_spec($product_index, $product_id)
{
$spec_file = $this->extract_path . "/产品规格{$product_index}.xlsx";
if (file_exists($spec_file)) {
Excel::import(new ProductSpecImport($product_id), $spec_file);
}
}
# 遍历图片文件,并移动到storage/app/public/supplier/import目录下
private function get_pictures($product_index): array
{
$storage_disk = Storage::disk('public');
$http_path = 'supplier/import/' . $this->supplier_id . '/' . date('Y-m');
if (!$storage_disk->exists($http_path)) {
$storage_disk->makeDirectory($http_path); //Storage的makeDirectory才能递归创建
}
$image_path = $this->extract_path . "/产品主图/$product_index";
chdir($image_path);
$pictures = [];
foreach (glob('*') as $file) {
$ext = strtolower(pathinfo($file)['extension']);
if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'jfif'])) {
continue;
}
$filename = '/' . md5_file($file) . '.' . $ext;
$move_file = realpath($file);
# Storage::move只适用于storage/app目录下的文件,且是相对路径
if (rename($move_file, $storage_disk->path($http_path . $filename))) {
$pictures[] = $http_path . $filename;
}
}
return $pictures;
}
# 获取扩展字段
private function get_extends(int $publish_type, $row): array
{
$keys = $this->keys;
return match ($publish_type) {
0 => [
'field_0_departure_place' => $row[$keys['出发地']] ?? '',
'field_0_departure_place_longitude' => $row[$keys['出发地经度']] ?? 0,
'field_0_departure_place_latitude' => $row[$keys['出发地纬度']] ?? 0,
'field_0_destination' => $row[$keys['目的地']] ?? '',
'field_0_destination_longitude' => $row[$keys['目的地经度']] ?? 0,
'field_0_destination_latitude' => $row[$keys['目的地纬度']] ?? 0,
'field_0_date' => [
# Excel日期是从1900-01-01起,PHP日期是1970-01-01起,所以要减去25569天数得到正确日期
'start' => !empty($row[$keys['行程起始时间']]) ? date('Y-m-d', strtotime('+ ' . ($row[$keys['行程起始时间']] - 25569) . 'day', 0)) : '',
'end' => !empty($row[$keys['行程结束时间']]) ? date('Y-m-d', strtotime('+ ' . ($row[$keys['行程结束时间']] - 25569) . 'day', 0)) : '',
],
],
1 => [
'field_1_name' => $row[$keys['酒店名']] ?? '',
'field_1_address' => $row[$keys['酒店地址']] ?? '',
'field_1_longitude' => $row[$keys['酒店经度']] ?? 0,
'field_1_latitude' => $row[$keys['酒店纬度']] ?? 0,
],
2 => [
'field_2_name' => $row[$keys['景区名']] ?? '',
'field_2_address' => $row[$keys['景区地址']] ?? '',
'field_2_longitude' => $row[$keys['景区经度']] ?? 0,
'field_2_latitude' => $row[$keys['景区纬度']] ?? 0,
],
3 => [
'field_3_name' => $row[$keys['餐厅名']] ?? '',
'field_3_address' => $row[$keys['餐厅地址']] ?? '',
'field_3_longitude' => $row[$keys['餐厅经度']] ?? 0,
'field_3_latitude' => $row[$keys['餐厅纬度']] ?? 0,
],
4 => [
'field_4_address' => $row[$keys['交通地址']] ?? '',
'field_4_longitude' => $row[$keys['交通经度']] ?? 0,
'field_4_latitude' => $row[$keys['交通纬度']] ?? 0,
],
5 => [
'field_5_address' => $row[$keys['购物地址']] ?? '',
'field_5_longitude' => $row[$keys['购物经度']] ?? 0,
'field_5_latitude' => $row[$keys['购物纬度']] ?? 0,
],
default => [],
};
}
}

63
app/Imports/ProductSpecImport.php

@ -0,0 +1,63 @@
<?php
namespace App\Imports;
use App\Models\ProductSpec;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Concerns\ToCollection;
class ProductSpecImport implements ToCollection
{
private int $product_id;
private array $keys;
public function __construct(int $product_id)
{
$this->product_id = $product_id;
}
public function collection(Collection $collection)
{
if ($collection->isEmpty()) {
return;
}
//校验数组count()是否正确
if (count($collection[0]) < 6) {
throw new \Exception('Excel产品规格格式不正确');
}
$this->keys = array_flip($collection[0]->toArray());
//去除第一行的标题
unset($collection[0]);
$this->createData($collection);
}
private function createData($rows)
{
$keys = $this->keys;
DB::beginTransaction();
foreach ($rows as $row) {
# Excel日期是从1900-01-01起的天数,PHP日期是1970-01-01起,所以要减去25569天数得到正确日期
$row[$keys['日期']] = date('Y-m-d', strtotime('+ ' . ($row[$keys['日期']] - 25569) . 'day', 0));
ProductSpec::updateOrCreate([
'product_id' => $this->product_id,
'name' => $row[$keys['规格名称']],
'date' => $row[$keys['日期']],
], [
'product_id' => $this->product_id,
'name' => $row[$keys['规格名称']],
'date' => $row[$keys['日期']],
'stock' => $row[$keys['库存']],
'original_price' => $row[$keys['市场价']],
'price' => $row[$keys['销售价']],
'cost_price' => $row[$keys['成本价']],
]);
}
DB::commit();
}
}

45
app/Jobs/ExportProductToExcel.php

@ -0,0 +1,45 @@
<?php
namespace App\Jobs;
use App\Exports\ProductExport;
use App\Models\ProductExportLog;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Maatwebsite\Excel\Facades\Excel;
class ExportProductToExcel implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private int $supplier_id;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(int $supplier_id)
{
$this->supplier_id = $supplier_id;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$filePath = "supplier/export/{$this->supplier_id}/" . date('Y-m') . "/导出产品-" . date('Y-m-d_H-i-s') . '.xlsx';
Excel::store(new ProductExport($this->supplier_id), $filePath, 'public');
ProductExportLog::create([
'supplier_id' => $this->supplier_id,
'filename' => $filePath,
]);
}
}

63
app/Jobs/ImportProductFromExcel.php

@ -0,0 +1,63 @@
<?php
namespace App\Jobs;
use App\Imports\ProductImport;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Facades\Excel;
use ZipArchive;
class ImportProductFromExcel implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private int $supplier_id;
private string $zip_file;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(int $supplier_id, string $zip_file)
{
$this->supplier_id = $supplier_id;
$this->zip_file = $zip_file;
}
/**
* Execute the job.
*
* @return void
* @throws Exception
*/
public function handle()
{
if (!$this->supplier_id) {
throw new Exception('未指定要导入的供应商');
} else if (!$this->zip_file) {
throw new Exception('未指定要导入的zip文件');
}
$extract_path = Storage::path('excel/extract/' . basename($this->zip_file, '.zip'));
$zip = new ZipArchive;
if ($zip->open($this->zip_file) === TRUE && $zip->extractTo($extract_path)) {
$zip->close();
} else {
throw new Exception("解压文件 {$this->zip_file} 失败!");
}
# 解压后删除压缩文件
unlink($this->zip_file);
Excel::import(new ProductImport($this->supplier_id, $extract_path), $extract_path . '/产品.xlsx');
}
}

4
composer.json

@ -12,10 +12,12 @@
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^8.40",
"laravel/tinker": "^2.5",
"maatwebsite/excel": "^3.1",
"overtrue/wechat": "~5.0",
"sentry/sentry-laravel": "^2.8",
"super-eggs/dcat-distpicker": "^2.0",
"tencentcloud/tencentcloud-sdk-php": "^3.0"
"tencentcloud/tencentcloud-sdk-php": "^3.0",
"ext-zip": "*"
},
"require-dev": {
"facade/ignition": "^2.5",

520
composer.lock

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "53437ceb8091fc69e865f45c680e7d34",
"content-hash": "696ba1a4ceafd58323ab85db5ed92218",
"packages": [
{
"name": "asm89/stack-cors",
@ -1192,6 +1192,66 @@
],
"time": "2020-12-29T14:50:06+00:00"
},
{
"name": "ezyang/htmlpurifier",
"version": "v4.13.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "08e27c97e4c6ed02f37c5b2b20488046c8d90d75"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/08e27c97e4c6ed02f37c5b2b20488046c8d90d75",
"reference": "08e27c97e4c6ed02f37c5b2b20488046c8d90d75",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.2"
},
"require-dev": {
"simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd"
},
"type": "library",
"autoload": {
"psr-0": {
"HTMLPurifier": "library/"
},
"files": [
"library/HTMLPurifier.composer.php"
],
"exclude-from-classmap": [
"/library/HTMLPurifier/Language/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Edward Z. Yang",
"email": "admin@htmlpurifier.org",
"homepage": "http://ezyang.com"
}
],
"description": "Standards compliant HTML filter written in PHP",
"homepage": "http://htmlpurifier.org/",
"keywords": [
"html"
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/master"
},
"time": "2020-06-29T00:56:53+00:00"
},
{
"name": "fideloper/proxy",
"version": "4.4.1",
@ -2463,6 +2523,286 @@
],
"time": "2021-01-18T20:58:21+00:00"
},
{
"name": "maatwebsite/excel",
"version": "3.1.34",
"source": {
"type": "git",
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
"reference": "d7446f0e808d83be128835c4b403c9e4a65b20f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/d7446f0e808d83be128835c4b403c9e4a65b20f3",
"reference": "d7446f0e808d83be128835c4b403c9e4a65b20f3",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-json": "*",
"illuminate/support": "5.8.*|^6.0|^7.0|^8.0",
"php": "^7.0|^8.0",
"phpoffice/phpspreadsheet": "^1.18"
},
"require-dev": {
"orchestra/testbench": "^6.0",
"predis/predis": "^1.1"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Maatwebsite\\Excel\\ExcelServiceProvider"
],
"aliases": {
"Excel": "Maatwebsite\\Excel\\Facades\\Excel"
}
}
},
"autoload": {
"psr-4": {
"Maatwebsite\\Excel\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Patrick Brouwers",
"email": "patrick@spartner.nl"
}
],
"description": "Supercharged Excel exports and imports in Laravel",
"keywords": [
"PHPExcel",
"batch",
"csv",
"excel",
"export",
"import",
"laravel",
"php",
"phpspreadsheet"
],
"support": {
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.34"
},
"funding": [
{
"url": "https://laravel-excel.com/commercial-support",
"type": "custom"
},
{
"url": "https://github.com/patrickbrouwers",
"type": "github"
}
],
"time": "2021-12-02T16:17:16+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58",
"reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"myclabs/php-enum": "^1.5",
"php": ">= 7.1",
"psr/http-message": "^1.0",
"symfony/polyfill-mbstring": "^1.0"
},
"require-dev": {
"ext-zip": "*",
"guzzlehttp/guzzle": ">= 6.3",
"mikey179/vfsstream": "^1.6",
"phpunit/phpunit": ">= 7.5"
},
"type": "library",
"autoload": {
"psr-4": {
"ZipStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Duncan",
"email": "pabs@pablotron.org"
},
{
"name": "Jonatan Männchen",
"email": "jonatan@maennchen.ch"
},
{
"name": "Jesse Donat",
"email": "donatj@gmail.com"
},
{
"name": "András Kolesár",
"email": "kolesar@kolesar.hu"
}
],
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
"keywords": [
"stream",
"zip"
],
"support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
"source": "https://github.com/maennchen/ZipStream-PHP/tree/master"
},
"funding": [
{
"url": "https://opencollective.com/zipstream",
"type": "open_collective"
}
],
"time": "2020-05-30T13:11:16+00:00"
},
{
"name": "markbaker/complex",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "ab8bc271e404909db09ff2d5ffa1e538085c0f22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/ab8bc271e404909db09ff2d5ffa1e538085c0f22",
"reference": "ab8bc271e404909db09ff2d5ffa1e538085c0f22",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"phpcompatibility/php-compatibility": "^9.0",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.3",
"squizlabs/php_codesniffer": "^3.4"
},
"type": "library",
"autoload": {
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.1"
},
"time": "2021-06-29T15:32:53+00:00"
},
{
"name": "markbaker/matrix",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "c66aefcafb4f6c269510e9ac46b82619a904c576"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/c66aefcafb4f6c269510e9ac46b82619a904c576",
"reference": "c66aefcafb4f6c269510e9ac46b82619a904c576",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"phpcompatibility/php-compatibility": "^9.0",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.3",
"sebastian/phpcpd": "^4.0",
"squizlabs/php_codesniffer": "^3.4"
},
"type": "library",
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.0"
},
"time": "2021-07-01T19:01:15+00:00"
},
{
"name": "mockery/mockery",
"version": "1.4.3",
@ -2643,6 +2983,72 @@
],
"time": "2021-07-23T07:42:52+00:00"
},
{
"name": "myclabs/php-enum",
"version": "1.8.3",
"source": {
"type": "git",
"url": "https://github.com/myclabs/php-enum.git",
"reference": "b942d263c641ddb5190929ff840c68f78713e937"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/php-enum/zipball/b942d263c641ddb5190929ff840c68f78713e937",
"reference": "b942d263c641ddb5190929ff840c68f78713e937",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-json": "*",
"php": "^7.3 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "1.*",
"vimeo/psalm": "^4.6.2"
},
"type": "library",
"autoload": {
"psr-4": {
"MyCLabs\\Enum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP Enum contributors",
"homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
}
],
"description": "PHP Enum implementation",
"homepage": "http://github.com/myclabs/php-enum",
"keywords": [
"enum"
],
"support": {
"issues": "https://github.com/myclabs/php-enum/issues",
"source": "https://github.com/myclabs/php-enum/tree/1.8.3"
},
"funding": [
{
"url": "https://github.com/mnapoli",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum",
"type": "tidelift"
}
],
"time": "2021-07-05T08:18:36+00:00"
},
{
"name": "nesbot/carbon",
"version": "2.53.1",
@ -3721,6 +4127,116 @@
},
"time": "2020-07-07T09:29:14+00:00"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "1.20.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "44436f270bb134b4a94670f3d020a85dfa0a3c02"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/44436f270bb134b4a94670f3d020a85dfa0a3c02",
"reference": "44436f270bb134b4a94670f3d020a85dfa0a3c02",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"ezyang/htmlpurifier": "^4.13",
"maennchen/zipstream-php": "^2.1",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": "^7.3 || ^8.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/simple-cache": "^1.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"dompdf/dompdf": "^1.0",
"friendsofphp/php-cs-fixer": "^3.2",
"jpgraph/jpgraph": "^4.0",
"mpdf/mpdf": "^8.0",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5 || ^9.0",
"squizlabs/php_codesniffer": "^3.6",
"tecnickcom/tcpdf": "^6.4"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)",
"jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.20.0"
},
"time": "2021-11-23T15:23:42+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.8.0",
@ -11179,7 +11695,7 @@
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^7.4|^8.0"
"php": "^8.0"
},
"platform-dev": [],
"plugin-api-version": "2.1.0"

328
config/excel.php

@ -0,0 +1,328 @@
<?php
use Maatwebsite\Excel\Excel;
return [
'exports' => [
/*
|--------------------------------------------------------------------------
| Chunk size
|--------------------------------------------------------------------------
|
| When using FromQuery, the query is automatically chunked.
| Here you can specify how big the chunk should be.
|
*/
'chunk_size' => 1000,
/*
|--------------------------------------------------------------------------
| Pre-calculate formulas during export
|--------------------------------------------------------------------------
*/
'pre_calculate_formulas' => false,
/*
|--------------------------------------------------------------------------
| Enable strict null comparison
|--------------------------------------------------------------------------
|
| When enabling strict null comparison empty cells ('') will
| be added to the sheet.
*/
'strict_null_comparison' => false,
/*
|--------------------------------------------------------------------------
| CSV Settings
|--------------------------------------------------------------------------
|
| Configure e.g. delimiter, enclosure and line ending for CSV exports.
|
*/
'csv' => [
'delimiter' => ',',
'enclosure' => '"',
'line_ending' => PHP_EOL,
'use_bom' => false,
'include_separator_line' => false,
'excel_compatibility' => false,
],
/*
|--------------------------------------------------------------------------
| Worksheet properties
|--------------------------------------------------------------------------
|
| Configure e.g. default title, creator, subject,...
|
*/
'properties' => [
'creator' => '',
'lastModifiedBy' => '',
'title' => '',
'description' => '',
'subject' => '',
'keywords' => '',
'category' => '',
'manager' => '',
'company' => '',
],
],
'imports' => [
/*
|--------------------------------------------------------------------------
| Read Only
|--------------------------------------------------------------------------
|
| When dealing with imports, you might only be interested in the
| data that the sheet exists. By default we ignore all styles,
| however if you want to do some logic based on style data
| you can enable it by setting read_only to false.
|
*/
'read_only' => true,
/*
|--------------------------------------------------------------------------
| Ignore Empty
|--------------------------------------------------------------------------
|
| When dealing with imports, you might be interested in ignoring
| rows that have null values or empty strings. By default rows
| containing empty strings or empty values are not ignored but can be
| ignored by enabling the setting ignore_empty to true.
|
*/
'ignore_empty' => false,
/*
|--------------------------------------------------------------------------
| Heading Row Formatter
|--------------------------------------------------------------------------
|
| Configure the heading row formatter.
| Available options: none|slug|custom
|
*/
'heading_row' => [
'formatter' => 'slug',
],
/*
|--------------------------------------------------------------------------
| CSV Settings
|--------------------------------------------------------------------------
|
| Configure e.g. delimiter, enclosure and line ending for CSV imports.
|
*/
'csv' => [
'delimiter' => ',',
'enclosure' => '"',
'escape_character' => '\\',
'contiguous' => false,
'input_encoding' => 'UTF-8',
],
/*
|--------------------------------------------------------------------------
| Worksheet properties
|--------------------------------------------------------------------------
|
| Configure e.g. default title, creator, subject,...
|
*/
'properties' => [
'creator' => '',
'lastModifiedBy' => '',
'title' => '',
'description' => '',
'subject' => '',
'keywords' => '',
'category' => '',
'manager' => '',
'company' => '',
],
],
/*
|--------------------------------------------------------------------------
| Extension detector
|--------------------------------------------------------------------------
|
| Configure here which writer/reader type should be used when the package
| needs to guess the correct type based on the extension alone.
|
*/
'extension_detector' => [
'xlsx' => Excel::XLSX,
'xlsm' => Excel::XLSX,
'xltx' => Excel::XLSX,
'xltm' => Excel::XLSX,
'xls' => Excel::XLS,
'xlt' => Excel::XLS,
'ods' => Excel::ODS,
'ots' => Excel::ODS,
'slk' => Excel::SLK,
'xml' => Excel::XML,
'gnumeric' => Excel::GNUMERIC,
'htm' => Excel::HTML,
'html' => Excel::HTML,
'csv' => Excel::CSV,
'tsv' => Excel::TSV,
/*
|--------------------------------------------------------------------------
| PDF Extension
|--------------------------------------------------------------------------
|
| Configure here which Pdf driver should be used by default.
| Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF
|
*/
'pdf' => Excel::DOMPDF,
],
/*
|--------------------------------------------------------------------------
| Value Binder
|--------------------------------------------------------------------------
|
| PhpSpreadsheet offers a way to hook into the process of a value being
| written to a cell. In there some assumptions are made on how the
| value should be formatted. If you want to change those defaults,
| you can implement your own default value binder.
|
| Possible value binders:
|
| [x] Maatwebsite\Excel\DefaultValueBinder::class
| [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class
| [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class
|
*/
'value_binder' => [
'default' => Maatwebsite\Excel\DefaultValueBinder::class,
],
'cache' => [
/*
|--------------------------------------------------------------------------
| Default cell caching driver
|--------------------------------------------------------------------------
|
| By default PhpSpreadsheet keeps all cell values in memory, however when
| dealing with large files, this might result into memory issues. If you
| want to mitigate that, you can configure a cell caching driver here.
| When using the illuminate driver, it will store each value in a the
| cache store. This can slow down the process, because it needs to
| store each value. You can use the "batch" store if you want to
| only persist to the store when the memory limit is reached.
|
| Drivers: memory|illuminate|batch
|
*/
'driver' => 'memory',
/*
|--------------------------------------------------------------------------
| Batch memory caching
|--------------------------------------------------------------------------
|
| When dealing with the "batch" caching driver, it will only
| persist to the store when the memory limit is reached.
| Here you can tweak the memory limit to your liking.
|
*/
'batch' => [
'memory_limit' => 60000,
],
/*
|--------------------------------------------------------------------------
| Illuminate cache
|--------------------------------------------------------------------------
|
| When using the "illuminate" caching driver, it will automatically use
| your default cache store. However if you prefer to have the cell
| cache on a separate store, you can configure the store name here.
| You can use any store defined in your cache config. When leaving
| at "null" it will use the default store.
|
*/
'illuminate' => [
'store' => null,
],
],
/*
|--------------------------------------------------------------------------
| Transaction Handler
|--------------------------------------------------------------------------
|
| By default the import is wrapped in a transaction. This is useful
| for when an import may fail and you want to retry it. With the
| transactions, the previous import gets rolled-back.
|
| You can disable the transaction handler by setting this to null.
| Or you can choose a custom made transaction handler here.
|
| Supported handlers: null|db
|
*/
'transactions' => [
'handler' => 'db',
],
'temporary_files' => [
/*
|--------------------------------------------------------------------------
| Local Temporary Path
|--------------------------------------------------------------------------
|
| When exporting and importing files, we use a temporary file, before
| storing reading or downloading. Here you can customize that path.
|
*/
'local_path' => storage_path('framework/cache/laravel-excel'),
/*
|--------------------------------------------------------------------------
| Remote Temporary Disk
|--------------------------------------------------------------------------
|
| When dealing with a multi server setup with queues in which you
| cannot rely on having a shared local temporary path, you might
| want to store the temporary file on a shared disk. During the
| queue executing, we'll retrieve the temporary file from that
| location instead. When left to null, it will always use
| the local path. This setting only has effect when using
| in conjunction with queued imports and exports.
|
*/
'remote_disk' => null,
'remote_prefix' => null,
/*
|--------------------------------------------------------------------------
| Force Resync
|--------------------------------------------------------------------------
|
| When dealing with a multi server setup as above, it's possible
| for the clean up that occurs after entire queue has been run to only
| cleanup the server that the last AfterImportJob runs on. The rest of the server
| would still have the local temporary file stored on it. In this case your
| local storage limits can be exceeded and future imports won't be processed.
| To mitigate this you can set this config value to be true, so that after every
| queued chunk is processed the local temporary file is deleted on the server that
| processed it.
|
*/
'force_resync_remote' => null,
],
];

14
tests/Feature/ExampleTest.php

@ -2,11 +2,19 @@
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Exports\ProductExport;
use App\Imports\ProductSpecImport;
use App\Jobs\ExportProductToExcel;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Facades\Excel;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use Tests\TestCase;
class ExampleTest extends TestCase
{
private string $extract_path = 'G:\WEB\vhosts\hainan.com\storage\app\excel/extract/2_import';
private int $supplier_id = 2;
/**
* A basic test example.
*
@ -14,8 +22,6 @@ class ExampleTest extends TestCase
*/
public function test_example()
{
$response = $this->get('/');
$response->assertStatus(200);
ExportProductToExcel::dispatch($this->supplier_id);
}
}
Loading…
Cancel
Save