diff --git a/app/AdminSupplier/Controllers/ProductExportLogController.php b/app/AdminSupplier/Controllers/ProductExportLogController.php index d37c1ad..dd7ec25 100644 --- a/app/AdminSupplier/Controllers/ProductExportLogController.php +++ b/app/AdminSupplier/Controllers/ProductExportLogController.php @@ -26,12 +26,12 @@ class ProductExportLogController extends AdminController $grid->model()->where('supplier_id', \Admin::user()->id)->orderByDesc('id'); - $grid->header(Alert::make('提示:只要导出全部才会在此列表显示。导出全部产品可能需要几分钟,请稍后再刷新查看!')->info()); + $grid->header(Alert::make('提示:导出产品及规格可能需要几分钟,请稍后再刷新查看!')->info()); \Admin::style('.alert.alert-info{margin-top:1rem;}'); $grid->column('id')->sortable(); $grid->column('filename')->downloadable(); - $grid->column('created_at'); + $grid->column('created_at', '导出时间'); $grid->filter(function (Grid\Filter $filter) { $filter->equal('id')->width(2); diff --git a/app/AdminSupplier/Extensions/ProductToExcelExporter.php b/app/AdminSupplier/Extensions/ProductToExcelExporter.php index 839feee..18f2ff6 100644 --- a/app/AdminSupplier/Extensions/ProductToExcelExporter.php +++ b/app/AdminSupplier/Extensions/ProductToExcelExporter.php @@ -1,11 +1,8 @@ id); - return redirect(admin_url('product/export'))->send(); + $_export_ = 'all'; } else if (str_starts_with($export_type, 'selected:')) { - $ids = explode(',', substr($export_type, strlen('selected:'))); - $ids = array_filter($ids, fn($v) => preg_match('/^\d+$/', $v)); - - return Excel::download(new ProductExport(Admin::user()->id, $ids), '导出选择产品-' . date('Y-m-d H:i:s') . '.xlsx')->send(); + $_export_ = explode(',', substr($export_type, strlen('selected:'))); + $_export_ = array_filter($_export_, fn($v) => preg_match('/^\d+$/', $v)); + } else { + return false; } + + ExportProductToExcel::dispatch(\Admin::user()->id, $_export_); + return redirect(admin_url('product/export'))->send(); } } diff --git a/app/Exports/ProductExport.php b/app/Exports/ProductExport.php index 8ccd662..4679e55 100644 --- a/app/Exports/ProductExport.php +++ b/app/Exports/ProductExport.php @@ -4,6 +4,7 @@ namespace App\Exports; use App\Common\ProductStatus; use App\Models\Product; +use Illuminate\Support\Facades\Storage; use Maatwebsite\Excel\Concerns\FromQuery; use Maatwebsite\Excel\Concerns\WithColumnFormatting; use Maatwebsite\Excel\Concerns\WithHeadings; @@ -12,12 +13,14 @@ use PhpOffice\PhpSpreadsheet\Style\NumberFormat; class ProductExport implements FromQuery, WithHeadings, WithColumnFormatting { private int $supplier_id; - private string|array|null $_export_ = null; + private string|array|null $_export_; + private string $export_dir; - public function __construct(int $supplier_id, string|array $_export_ = null) + public function __construct(int $supplier_id, string|array $_export_, string $export_dir) { $this->supplier_id = $supplier_id; $this->_export_ = $_export_; + $this->export_dir = $export_dir; } public function query() @@ -27,13 +30,24 @@ class ProductExport implements FromQuery, WithHeadings, WithColumnFormatting } else if (!empty($this->_export_) && is_array($this->_export_)) { return Product::with('category:id,name')->where('supplier_id', $this->supplier_id)->whereIn('id', $this->_export_); } else { - return Product::with('category:id,name')->where('supplier_id', $this->supplier_id)->limit(15); + return Product::with('category:id,name')->where('supplier_id', $this->supplier_id)->limit(20); } } public function prepareRows($rows) { return $rows->transform(function ($row) { + # 复制图片到导出目录 + if (is_array($row['pictures']) && !empty($row['pictures'])) { + foreach ($row['pictures'] as $key => $picture) { + try { + Storage::disk('public')->copy($picture, $this->export_dir . '产品主图/' . $row->id . '/' . ($key + 1) . '.' . pathinfo($picture)['extension']); + } catch (\Exception $exception) { + continue; + } + } + } + return [ 'id' => $row->id, 'status' => ProductStatus::array()[$row->status] ?? '', @@ -76,25 +90,13 @@ class ProductExport implements FromQuery, WithHeadings, WithColumnFormatting }); } - public function map($row): array - { - return [ - $row->id, - $row->category->name, - - ]; - } - public function headings(): array { return [ - 'ID', '状态', '分类', '产品标题', '销售价', '市场价', '库存', '旅客须知', '产品详情', '核销手机号', '信息收集表单ID', + '产品ID', '状态', '分类', '产品标题', '销售价', '市场价', '库存', '旅客须知', '产品详情', '核销手机号', '信息收集表单ID', '出发地', '出发地经度', '出发地纬度', '目的地', '目的地经度', '目的地纬度', '行程起始时间', '行程结束时间', - '酒店名', '酒店地址', '酒店经度', '酒店纬度', - '景区名', '景区地址', '景区经度', '景区纬度', - '餐厅名', '餐厅地址', '餐厅经度', '餐厅纬度', - '交通地址', '交通经度', '交通纬度', - '购物地址', '购物经度', '购物纬度', + '酒店名', '酒店地址', '酒店经度', '酒店纬度', '景区名', '景区地址', '景区经度', '景区纬度', + '餐厅名', '餐厅地址', '餐厅经度', '餐厅纬度', '交通地址', '交通经度', '交通纬度', '购物地址', '购物经度', '购物纬度', ]; } diff --git a/app/Exports/ProductSpecExport.php b/app/Exports/ProductSpecExport.php new file mode 100644 index 0000000..59d6ca6 --- /dev/null +++ b/app/Exports/ProductSpecExport.php @@ -0,0 +1,57 @@ +product_id = $product_id; + } + + /** + * @return Collection + */ + public function query() + { + return ProductSpec::where([ + ['product_id', '=', $this->product_id], + ['date', '>=', date('Y-m-d')], + ])->orderBy('name')->orderBy('date'); + } + + public function prepareRows($rows) + { + return $rows->transform(function ($row) { + return [ + '规格名称' => $row['name'], + '日期' => $row['date'], + '库存' => $row['stock'], + '市场价' => $row['original_price'], + '销售价' => $row['price'], + '成本价' => $row['cost_price'], + ]; + }); + } + + public function headings(): array + { + return [ + '规格名称', '日期', '库存', '市场价', '销售价', '成本价', + ]; + } + + public function columnFormats(): array + { + return [ + 'B' => 'yyyy/m/d', + ]; + } +} diff --git a/app/Imports/ProductImport.php b/app/Imports/ProductImport.php index a7dc5c2..3419654 100644 --- a/app/Imports/ProductImport.php +++ b/app/Imports/ProductImport.php @@ -48,6 +48,8 @@ class ProductImport implements ToCollection $category = Category::where(['agent_id' => 0, 'name' => trim($row[$keys['分类']])])->first(); if (!$category) continue; + $index = !empty($row[$keys['产品ID']]) ? $row[$keys['产品ID']] : $product_index + 1; + $insert_data = [ 'supplier_id' => $this->supplier_id, 'category_id' => $category->id, @@ -60,7 +62,7 @@ class ProductImport implements ToCollection 'content' => $row[$keys['产品详情']] ?? '', 'verify_mobile' => $row[$keys['核销手机号']] ?? '', 'diy_form_id' => $row[$keys['信息收集表单ID']] ?? '', - 'pictures' => $this->get_pictures($product_index), + 'pictures' => $this->get_pictures($index), ]; # 扩展字段 @@ -78,7 +80,7 @@ class ProductImport implements ToCollection $product = Product::create($insert_data); if ($product->id) { - $this->insert_spec($product_index, $product->id); + $this->insert_spec($index, $product->id); } } } diff --git a/app/Imports/ProductSpecImport.php b/app/Imports/ProductSpecImport.php index 05f8c38..789ef8e 100644 --- a/app/Imports/ProductSpecImport.php +++ b/app/Imports/ProductSpecImport.php @@ -42,7 +42,12 @@ class ProductSpecImport implements ToCollection 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)); + $time = strtotime($row[$keys['日期']]); + if ($time === false) { + $row[$keys['日期']] = date('Y-m-d', strtotime('+ ' . ($row[$keys['日期']] - 25569) . 'day', 0)); + } else { + $row[$keys['日期']] = date('Y-m-d', $time); + } ProductSpec::updateOrCreate([ 'product_id' => $this->product_id, diff --git a/app/Jobs/ExportProductToExcel.php b/app/Jobs/ExportProductToExcel.php index a1465d7..62576d8 100644 --- a/app/Jobs/ExportProductToExcel.php +++ b/app/Jobs/ExportProductToExcel.php @@ -3,14 +3,21 @@ namespace App\Jobs; use App\Exports\ProductExport; +use App\Exports\ProductSpecExport; +use App\Models\Product; use App\Models\ProductExportLog; +use App\Models\ProductSpec; 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 RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use ZipArchive; /** * 导出全部产品 @@ -20,15 +27,17 @@ class ExportProductToExcel implements ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private int $supplier_id; + private string|array|null $_export_; /** * Create a new job instance. * * @return void */ - public function __construct(int $supplier_id) + public function __construct(int $supplier_id, string|array $_export_ = null) { $this->supplier_id = $supplier_id; + $this->_export_ = $_export_; } /** @@ -38,11 +47,50 @@ class ExportProductToExcel implements ShouldQueue */ 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, 'all'), $filePath, 'public'); + $export_dir = "supplier/export/{$this->supplier_id}/" . date('Y-m') . '/'; + $product_file = $export_dir . '产品.xlsx'; + + Storage::disk('public')->deleteDirectory($export_dir); + if (!Excel::store(new ProductExport($this->supplier_id, $this->_export_, $export_dir), $product_file, 'public')) { + return; + } + + # 导出规格 + $ids = $this->_export_ == 'all' ? + Product::where('supplier_id', $this->supplier_id)->get('id')->pluck('id') : + Product::whereIn('id', $this->_export_)->where('supplier_id', $this->supplier_id)->get('id')->pluck('id'); + + foreach ($ids as $id) { + if (ProductSpec::where([['product_id', '=', $id], ['date', '>=', date('Y-m-d')]])->exists()) { + Excel::store(new ProductSpecExport($id), $export_dir . "产品规格{$id}.xlsx", 'public'); + } + } + + # 生成zip文件 + $zip = new ZipArchive(); + $zip_file = substr(rtrim($export_dir, '/'), 0, strrpos(rtrim($export_dir, '/'), '/')) . '/export_' . date('Y-m-d_H-i-s') . '.zip'; + if ($zip->open(Storage::disk('public')->path($zip_file),ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) { + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator(Storage::disk('public')->path($export_dir)), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($files as $file) { + if (!$file->isDir()) { + $filePath = $file->getRealPath(); + $relativePath = substr($filePath, strlen(Storage::disk('public')->path($export_dir))); + $zip->addFile($filePath, $relativePath); + } + } + + $zip->close(); + } + Storage::disk('public')->deleteDirectory($export_dir); + + # 生成导出记录 ProductExportLog::create([ 'supplier_id' => $this->supplier_id, - 'filename' => $filePath, + 'filename' => $zip_file, ]); } } diff --git a/app/Jobs/ImportProductFromExcel.php b/app/Jobs/ImportProductFromExcel.php index bd7c40a..5336a24 100644 --- a/app/Jobs/ImportProductFromExcel.php +++ b/app/Jobs/ImportProductFromExcel.php @@ -46,7 +46,10 @@ class ImportProductFromExcel implements ShouldQueue throw new Exception('未指定要导入的zip文件'); } - $extract_path = Storage::path('excel/extract/' . basename($this->zip_file, '.zip')); + $extract_dir = "excel/extract/{$this->supplier_id}/"; + Storage::deleteDirectory($extract_dir); + Storage::makeDirectory($extract_dir); + $extract_path = Storage::path($extract_dir . basename($this->zip_file, '.zip')); $zip = new ZipArchive; if ($zip->open($this->zip_file) === TRUE && $zip->extractTo($extract_path)) {