diff --git a/app/admin/controller/v1/ProductPurchaseLink.php b/app/admin/controller/v1/ProductPurchaseLink.php new file mode 100644 index 00000000..04485584 --- /dev/null +++ b/app/admin/controller/v1/ProductPurchaseLink.php @@ -0,0 +1,339 @@ +language(request()->lang_id) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->select(); + + return success('获取成功', $platforms); + } + + /** + * 购买链接分布数据 + */ + public function index() + { + $params = request()->param([ + 'name' => '', + 'spu' => '', + 'page/d' => 1, + 'size/d' => 10 + ]); + + $links = ProductModel::alias('pd') + ->field([ + 'pd.id', + 'pd.spu', + 'pd.name', + 'CASE WHEN pd.is_show = 0 THEN "已下架" WHEN pd.is_show = 1 THEN "已上架" END' => 'is_show', + 'pf.id' => 'platform_id', + 'pf.platform' => 'platform_name', + 'min(pl.id)' => 'link_id', + 'pl.link' + ]) + ->leftJoin('product_purchase_link pl', 'pl.product_id = pd.id') + ->leftJoin('product_purchase_platform pf', 'pf.id = pl.platform_id') + ->where(function ($query) use ($params) { + $query->where('pd.is_show', '=', 1); + if (!empty($params['name'])) { + $query->where('pd.name', 'like', '%' . $params['name'] . '%'); + } + if (!empty($params['spu'])) { + $query->where('pd.spu', 'like', '%' . $params['spu'] . '%'); + } + }) + ->where('pd.language_id', '=', request()->lang_id) + ->group('pd.id') + ->order(['pl.sort' => 'asc', 'pl.id' => 'desc', 'pd.id' => 'desc']) + ->paginate([ + 'list_rows' => $params['size'], + 'page' => $params['page'], + ]); + if ($links->isEmpty()) { + return success('获取成功', []); + } + + $others = ProductPurchaseLinkModel::alias('pl') + ->field([ + 'pl.product_id', + 'pl.platform_id', + 'pf.platform' => 'platform_name', + 'pl.id' => 'link_id', + 'pl.link' + ]) + ->join('product_purchase_platform pf', 'pf.id = pl.platform_id') + ->where('pl.language_id', '=', request()->lang_id) + ->where('pl.product_id', 'in', array_column($links->items(), 'id')) + ->where('pl.id', 'not in', array_column($links->items(), 'link_id')) + ->select(); + if (!$others->isEmpty()) { + $others_map = []; + $others_arr = $others->toArray(); + foreach ($others_arr as $other) { + $product_id = $other['product_id']; + unset($other['product_id']); + $others_map[$product_id][] = $other; + } + $links->each(function ($item) use ($others_map) { + if (!empty($others_map[$item['id']])) { + $item['rowspan'] = $others_map[$item['id']]; + } + return $item; + }); + } + + return success('获取成功', $links); + } + + /** + * 导入 + */ + public function import() + { + $file = request()->file('file'); + if ($file->getSize() > 20 * 1024 * 1024) { + return error('上传文件不能超过20M'); + } + + $reader = IOFactory::createReader('Xlsx'); + + // 读取文件 + $spreadsheet = $reader->load($file->getRealPath()); + + // 获取活动sheet + $sheet = $spreadsheet->getActiveSheet(); + + // 获取行数 + $rows = $sheet->getHighestRow(); + + // 从表格获取数据 + $xlsx_data = []; + for ($i=2; $i <= $rows; $i++) { + $xlsx_data[$i] = [ + 'spu' => $sheet->getCell('A' . $i)->getValue(), + 'platform' => $sheet->getCell('B' . $i)->getValue(), + 'link' => $sheet->getCell('C' . $i)->getValue(), + ]; + } + + $platforms_name = array_unique(array_column($xlsx_data, 'platform')); + $platforms_map = ProductPurchaseLinkPlatformModel::language(request()->lang_id) + ->where('platform', 'in', $platforms_name) + ->column('id', 'platform'); + + $data = []; + $errors = []; + $chunks = array_chunk($xlsx_data, 500, true); + // 分组验证每行,并组装数据 + foreach ($chunks as $chunk) { + $spus = array_unique(array_column($chunk, 'spu')); + $products_map = ProductModel::language(request()->lang_id) + ->where('spu', 'in', $spus) + ->column('id', 'spu'); + + $items = []; + foreach ($chunk as $r => $it) { + if (empty($platforms_map[$it['platform']])) { + $errors[] = sprintf("第%d行,平台名称错误", $r); + continue; + } + if (empty($products_map[$it['spu']])) { + $errors[] = sprintf("第%d行,型号不存在", $r); + continue; + } + $items[] = [ + 'language_id' => request()->lang_id, + 'product_id' => $products_map[$it['spu']], + 'platform_id' => $platforms_map[$it['platform']], + 'link' => $it['link'], + 'sort' => 0, + ]; + } + if (!empty($items)) { + $data[] = $items; + } + } + + // 组装sql,并执行 + if (!empty($data)) { + $link_model = new ProductPurchaseLinkModel(); + $sql = sprintf( + 'REPLACE INTO %s (id, language_id, product_id, platform_id, link, sort) VALUES ', + $link_model->getTable(), + ); + foreach ($data as $items) { + $products_id = array_unique(array_column($items, 'product_id')); + $links = $link_model->field([ + 'id', + 'product_id', + 'platform_id' + ]) + ->where('product_id', 'in', $products_id) + ->select(); + $links_map = []; + if (!$links->isEmpty()) { + foreach ($links as $link) { + $links_map[$link['product_id'] . '_' . $link['platform_id']] = $link['id']; + } + } + + foreach ($items as $item) { + $item['id'] = null; + if (!empty($links_map[$item['product_id'] . '_' . $item['platform_id']])) { + $item['id'] = $links_map[$item['product_id'] . '_' . $item['platform_id']]; + } + $sql .= sprintf( + '(%d, %d, %d, %d, "%s", %d),', + $item['id'], + $item['language_id'], + $item['product_id'], + $item['platform_id'], + $item['link'], + $item['sort'] + ); + } + } + Db::execute(rtrim($sql, ',')); + } + + if (!empty($errors)) { + return error(implode(";\n", $errors)); + } + + return success('导入成功'); + } + + /** + * 导出 + */ + public function export() + { + $schema = [ + 'spu' => '型号', + 'platform' => '平台', + 'link' => '链接' + ]; + + // 获取导出数据 + $data = $this->getExportLinkData(); + + // 获取Spreadsheet对象 + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // 写入表头 + $title = array_values($schema); + $title_col = 'A'; + foreach ($title as $value) { + // 单元格内容写入 + $sheet->setCellValue($title_col . '1', $value); + $title_col++; + } + + // 写入数据 + $row = 2; + $keys = array_keys($schema); + foreach ($data as $item) { + $data_col = 'A'; + foreach ($keys as $key) { + $sheet->setCellValue($data_col . $row, $item[$key] ?? ''); + $data_col++; + } + $row++; + } + + flush(); + ob_flush(); + $filename = date('YmdHms'); + header('Access-Control-Expose-Headers: Content-Disposition'); + header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; Charset=UTF-8'); + header('Content-Disposition: attachment;filename=' . $filename . '.xlsx'); + header('Cache-Control: max-age=0'); + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); + $writer->save('php://output'); + } + private function getExportLinkData() + { + $params = request()->param([ + 'name' => '', + 'spu' => '' + ]); + + return ProductModel::alias('pd') + ->field([ + 'pd.spu', + 'pf.platform', + 'pl.link' + ]) + ->leftJoin('product_purchase_link pl', 'pl.product_id = pd.id') + ->leftJoin('product_purchase_platform pf', 'pf.id = pl.platform_id') + ->where(function ($query) use ($params) { + $query->where('pd.is_show', '=', 1); + if (!empty($params['name'])) { + $query->where('pd.name', 'like', '%' . $params['name'] . '%'); + } + if (!empty($params['spu'])) { + $query->where('pd.spu', 'like', '%' . $params['spu'] . '%'); + } + }) + ->where('pd.language_id', '=', request()->lang_id) + ->order(['pl.sort' => 'asc', 'pl.id' => 'desc', 'pd.id' => 'desc']) + ->select(); + } + + /** + * 更新购买链接 + */ + public function update() + { + $id = request()->param('id'); + $put = request()->put([ + 'link', + 'platform_id' + ]); + + $data = [ + 'id' => $id, + 'link' => $put['link'], + 'platform_id' => $put['platform_id'] + ]; + $validate = new ProductPurchaseLinkValidate; + if (!$validate->scene('update')->check($data)) { + return error($validate->getError()); + } + + $link = ProductPurchaseLinkModel::bypk($data['id'])->find(); + if (empty($link)) { + return error('请确认操作对象是否存在'); + } + + if (!$link->save($data)) { + return error('操作失败'); + } + return success('操作成功'); + } +} diff --git a/app/admin/model/v1/ProductPurchaseLinkModel.php b/app/admin/model/v1/ProductPurchaseLinkModel.php new file mode 100644 index 00000000..3f37196e --- /dev/null +++ b/app/admin/model/v1/ProductPurchaseLinkModel.php @@ -0,0 +1,31 @@ +belongsTo(ProductModel::class, 'product_id', 'id'); + } + + // 关联平台 + public function platform() + { + return $this->belongsTo(ProductPurchaseLinkPlatformModel::class, 'platform_id', 'id'); + } + + // 根据语言查询 + public function scopeLanguage($query, $value) + { + $query->where('language_id', '=', $value); + } +} diff --git a/app/admin/model/v1/ProductPurchaseLinkPlatformModel.php b/app/admin/model/v1/ProductPurchaseLinkPlatformModel.php new file mode 100644 index 00000000..718be547 --- /dev/null +++ b/app/admin/model/v1/ProductPurchaseLinkPlatformModel.php @@ -0,0 +1,19 @@ +where('language_id', '=', $value); + } +} diff --git a/app/admin/route/v1.php b/app/admin/route/v1.php index 742469d1..cef47886 100644 --- a/app/admin/route/v1.php +++ b/app/admin/route/v1.php @@ -184,6 +184,24 @@ Route::group('v1', function () { Route::delete('delete/:id', 'ProductCategory/delete'); }); + // 产品购买链接 + Route::group('buypass', function () { + // 购买链接平台列表 + Route::get('platforms', 'ProductPurchaseLink/platforms'); + + // 购买链接列表 + Route::get('index', 'ProductPurchaseLink/index'); + + // 购买链接导入 + Route::post('import', 'ProductPurchaseLink/import'); + + // 购买链接导出 + Route::get('export', 'ProductPurchaseLink/export'); + + // 购买链接更新 + Route::put('update/:id', 'ProductPurchaseLink/update'); + }); + // 产品回收站 Route::group('trash', function () { // 产品回收站列表 diff --git a/app/admin/validate/v1/ProductPurchaseLinkValidate.php b/app/admin/validate/v1/ProductPurchaseLinkValidate.php new file mode 100644 index 00000000..7fde46e4 --- /dev/null +++ b/app/admin/validate/v1/ProductPurchaseLinkValidate.php @@ -0,0 +1,45 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = [ + 'id' => 'require|integer', + 'link' => 'url|max:255', + 'platform_id' => 'integer' + ]; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = [ + 'id.require' => 'id不能为空', + 'id.integer' => 'id字段类型错误', + 'link.url' => '链接格式不正确', + 'link.max' => '链接不能超过255个字符', + 'platform_id.integer' => '平台id类型错误' + + ]; + + /** + * 更新场景 + */ + public function sceneUpdate() + { + return $this->only(['id', 'link', 'platform_id'])->remove('id', 'require'); + } +}