diff --git a/app/command/DataMigration.php b/app/command/DataMigration.php index 1b48d496..648ff1d3 100644 --- a/app/command/DataMigration.php +++ b/app/command/DataMigration.php @@ -45,7 +45,13 @@ class DataMigration extends Command // $this->migrateProductAttr(); // 迁移产品 - $this->migrateProduct(); + // $this->migrateProduct(); + + // 迁移产品关联产品数据 + // $this->migrateProductRelated(); + + // 迁移产品购买链接 + // $this->migrateProductPurchaseLinks(); // 迁移文章 // $this->migrateArticle([ @@ -168,18 +174,25 @@ class DataMigration extends Command ->select() ->toArray(); + $tree = array_to_tree($category, 0, 'pid', 1); // 处理数据 - $this->handlerProductCategory(array_to_tree($category, 0, 'pid', 1), $tco_category_map); + $this->handlerProductCategory($tree, $tco_category_map); } - private function handlerProductCategory($category, $map) { + private function handlerProductCategory($category, $map, $path = []) { foreach ($category as $val) { $key = sprintf("%s_%s", $val['id'], $val['country_code']); + if ($val['pid'] == 0) { + $path = []; + } else { + if (!in_array($val['pid'], $path)) $path[] = $val['pid']; + } $item = [ 'id' => $val['id'], 'language_id' => $val['country_code'] == 'ZH' ? 1 : 2, 'unique_id' => uniqid('PRO_CATE_'), 'pid' => $val['pid'], 'name' => $val['name'], + 'path' => implode(',', $path), 'icon' => $val['icon'], 'desc' => $val['description'], 'related_tco_category' => isset($map[$key]) ? implode(',', $map[$key]) : '', @@ -189,7 +202,7 @@ class DataMigration extends Command ]; Db::name('product_category')->insert($item); if (isset($val['children'])) { - $this->handlerProductCategory($val['children'], $map); + $this->handlerProductCategory($val['children'], $map, $path); } } } @@ -245,15 +258,27 @@ class DataMigration extends Command $old_db = Db::connect('old'); + $success_map = []; + $success_arr = include_once(runtime_path() . 'product_success.php'); + foreach ($success_arr as $so) { + $success_map['p_' . $so['cod_product_id']] = $so; + } + $products = $old_db->name('product') - ->where('id', '=', 128) ->where('country_code', 'in', ['ZH', 'US']) + ->where('id', '>', 0) ->order(['id' => 'asc']) ->cursor(); + $total = 0; $uploadMgr = new UploadMannager(); foreach ($products as $v) { + $start = microtime(true); + if (isset($success_map['p_' . $v['id']])) { + continue; + } + Db::startTrans(); try { // 处理封面图片 @@ -262,7 +287,9 @@ class DataMigration extends Command if ($image_ret['code'] == 0) { $image = $image_ret['data']['path']; } else { - $image = $image_ret['msg']; + if ($image_ret['code'] != 400) { + $image = $image_ret['msg']; + } } // 处理视频 @@ -271,7 +298,9 @@ class DataMigration extends Command if ($video_ret['code'] == 0) { $video = $video_ret['data']['path']; } else { - $video = $video_ret['msg']; + if ($video_ret['code'] != 400) { + $video = $video_ret['msg']; + } } // 处理详情中图片 @@ -287,11 +316,11 @@ class DataMigration extends Command return ''; } return ''; - }, $v['ld_md_content']); + }, $v['ld_md_content']??''); $item = [ 'language_id' => $v['country_code'] == 'ZH' ? 1 : 2, - 'category_id' => '', + 'category_id' => $v['cid'], 'spu' => $v['brand_id'], 'name' => $v['name'], 'short_name' => $v['shortname'], @@ -302,31 +331,33 @@ class DataMigration extends Command 'is_sale' => $v['is_onsale'], 'is_new' => $v['isnew'], 'is_hot' => $v['ishot'], - 'is_show' => $v['is_show'], - 'sort' => $v['sort'], + 'is_show' => $v['is_show'] == 0 ? 1 : 0, + 'sort' => $v['sort'] == 9999 ? 0 : $v['sort'], 'detail' => $content, 'status' => $v['stat'] == -1 ? -1 : 1, 'seo_title' => $v['seo_title'], 'seo_keywords' => $v['seo_keyword'], 'seo_desc' => $v['seo_description'], 'created_at' => date('Y-m-d H:i:s', $v['createtime']), - 'updated_at' => date('Y-m-d H:i:s', $v['updatetime']), + 'updated_at' => $v['updatetime'] == 0 ? date('Y-m-d H:i:s', $v['createtime']) : date('Y-m-d H:i:s', $v['updatetime']), 'deleted_at' => $v['stat'] == -1 ? date('Y-m-d H:i:s') : null, ]; // 保存产品数据 $id = Db::name('product')->insertGetId($item); // 保存产品参数数据 - $prarms = []; - $views = unserialize($v['product_view']); - foreach ($views as $p) { - $prarms[] = [ - 'product_id' => $id, - 'name' => $p['desc_title'], - 'value' => $p['desc_desc'] - ]; + if (!empty($v['product_view'])) { + $prarms = []; + $views = unserialize($v['product_view']); + foreach ($views as $p) { + $prarms[] = [ + 'product_id' => $id, + 'name' => $p['desc_title'], + 'value' => $p['desc_desc'] + ]; + } + Db::name('product_params')->insertAll($prarms); } - Db::name('product_params')->insertAll($prarms); // 保存sku数据 $skus = []; @@ -350,6 +381,7 @@ class DataMigration extends Command if (!isset($images[$pkey])) { $images[$pkey] = []; $images[$pkey]['sku'] = $im['sku']; + $images[$pkey]['attrs'] = []; } // 处理图册 @@ -365,6 +397,10 @@ class DataMigration extends Command $photo_album[$pkey] = []; } $photo_album[$pkey][] = $photos_ret['data']['path']; + } else { + if ($photos_ret['code'] != 400) { + $photo_album[$pkey][] = json_encode($photos_ret); + } } } } @@ -372,17 +408,19 @@ class DataMigration extends Command // 处理属性 $attrs = json_decode($im['image_color'], true); if (!empty($attrs)) { - foreach ($attrs as $k => $at) { - if ($k != 'sort') { - $attr_value = $at; - if (in_array($k, ['颜色', 'Color'])) { - if ($k == 'Color') $k = '颜色'; - $attr_value = '/static/common/images/colors/' . $at . '.png'; + foreach ($attrs as $attr) { + foreach ($attr as $k => $at) { + if ($k != 'sort') { + $attr_value = $at; + if (in_array($k, ['颜色', 'Color'])) { + if ($k == 'Color') $k = '颜色'; + $attr_value = '/static/common/images/colors/' . $at . '.png'; + } + $images[$pkey]['attrs'][] = [ + 'attr_id' => $attrs_map[$v['country_code']][$k], + 'attr_value' => $attr_value, + ]; } - $images[$pkey]['attrs'] = [ - 'attr_id' => $attrs_map[$v['country_code']][$k], - 'attr_value' => $attr_value, - ]; } } } else { @@ -402,20 +440,21 @@ class DataMigration extends Command 'attr_value' => $attr_value, ]; if ( - empty($images[$pkey]['attrs']) || - (!empty($images[$pkey]['attrs']) && empty(array_intersect($images[$pkey]['attrs'], $attr_arr))) + empty($images[$pkey]['attrs']) || !array_some($images[$pkey]['attrs'], function($k, $v) use($attr_arr) { + return $v == $attr_arr; + }) ) { - $images[$pkey]['attrs'][] = $attr_arr; - file_put_contents(runtime_path() . 'attrs.txt', json_encode($attr_arr)); } } } - foreach ($images as $key => $image) { - $images[$key]['photo_album'] = $photo_album[$key]; + if (!empty($photo_album)) { + foreach ($images as $key => $image) { + if (isset($photo_album[$key])) { + $images[$key]['photo_album'] = $photo_album[$key]; + } + } } - file_put_contents(runtime_path() . 'images.txt', json_encode($images, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - file_put_contents(runtime_path() . 'photo_album.txt', json_encode($photo_album, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); $two_images = $old_db->name('product_two_img') ->where('product_id', '=', $v['id']) @@ -428,56 +467,74 @@ class DataMigration extends Command if (!empty($ti['image_color'])) { $tpkey = md5($ti['image_color']); } - if (!empty($ti['image_url'])) { + if (!empty($ti['image_url']) && $ti['image_url'] != '[]') { $main_image_ret = $uploadMgr->upload($uploadMgr->download($ti['image_url']), 'image', 'product'); if ($main_image_ret['code'] == 0) { $ti['image_url'] = $main_image_ret['data']['path']; } } - $skus[] = ['main_image' => $ti['image_url'], 'image_color' => $ti['image_color'], 'pkey' => $tpkey]; + if (\think\helper\Str::endsWith($ti['image_color'], ['.png', '.jpg', 'jpeg', '.gif'])) { + $arrt_ret = $uploadMgr->upload($uploadMgr->download($ti['image_color']), 'image', 'product'); + if ($arrt_ret['code'] == 0) { + $ti['image_color'] = $arrt_ret['data']['path']; + } + } + $skus[] = [ + 'main_image' => $ti['image_url'], + 'attrs' => [[ + 'attr_id' => $attrs_map[$v['country_code']]['颜色'], + 'attr_value' => $ti['image_color'], + ]], + 'pkey' => $tpkey + ]; } + if (!empty($skus)) { - $temp = []; + $temp = []; + $temp_images = []; foreach ($skus as $idx => $sku) { + $skus[$idx]['product_id'] = $id; if (isset($images[$sku['pkey']])) { - $skus[$idx]['product_id'] = $id; - $skus[$idx]['sku'] = $images[$sku['pkey']]['sku']; - $skus[$idx]['photo_album'] = $images[$sku['pkey']]['photo_album']; - $skus[$idx]['attrs'] = $images[$sku['pkey']]['attrs']; + $skus[$idx]['sku'] = $images[$sku['pkey']]['sku']??null; + $skus[$idx]['photo_album'] = $images[$sku['pkey']]['photo_album']??null; + if (!empty($images[$sku['pkey']]['attrs'])) { + $skus[$idx]['attrs'] = $images[$sku['pkey']]['attrs']; + } } else { - if (!empty($images[$idx])) { - $temp = $images[$idx]; + if (empty($temp_images)) { + $temp_images = array_values($images); + } + if (!empty($temp_images[$idx])) { + $temp = $temp_images[$idx]; } if (!empty($temp)) { - $skus[$idx]['product_id'] = $id; - $skus[$idx]['sku'] = $temp['sku']; - $skus[$idx]['photo_album'] = $temp['photo_album']; - $skus[$idx]['attrs'] = $temp['attrs']; + $skus[$idx]['sku'] = $temp['sku']??null; + $skus[$idx]['photo_album'] = $temp['photo_album']??null; + if (!empty($temp['attrs'])) { + $skus[$idx]['attrs'] = $temp['attrs']; + } } } unset($skus[$idx]['pkey']); - unset($skus[$idx]['image_color']); } } else { foreach ($images as $image) { $skus[] = [ 'product_id' => $id, - 'sku' => $image['sku'], + 'sku' => $image['sku']??null, 'main_image' => '', - 'photo_album' => $image['photo_album'], + 'photo_album' => $image['photo_album']??null, 'skus' => $image['attrs'] ]; } } - file_put_contents(runtime_path() . 'skus.txt', json_encode($skus, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - // throw new \Exception("exit;"); foreach ($skus as $sku) { $sku_id = Db::name('product_sku')->insertGetId([ 'product_id' => $sku['product_id'], - 'sku' => $sku['sku'], - 'main_image' => $sku['main_image'], - 'photo_album' => json_encode($sku['photo_album']), + 'sku' => $sku['sku']??'', + 'main_image' => $sku['main_image']??'', + 'photo_album' => !empty($sku['photo_album']) ? json_encode($sku['photo_album']) : null, ]); if (!empty($sku['attrs'])) { foreach ($sku['attrs'] as $attr) { @@ -491,13 +548,21 @@ class DataMigration extends Command } } } + Db::commit(); - $this->println(sprintf('迁移产品ID:%s => %s', $v['id'], $id)); + $total += 1; + file_put_contents( + runtime_path() . 'product_success.txt', + sprintf('["ow_product_id" => %d, "cod_product_id" => %d]'.PHP_EOL, $id, $v['id']), + FILE_APPEND + ); + $this->println(sprintf('迁移产品ID:%s => %s 【耗时:%s】', $v['id'], 0, round(microtime(true) - $start, 2) . 's')); } catch (\Throwable $th) { Db::rollback(); file_put_contents( runtime_path() . 'product_throwable.txt', - sprintf('【%s】 产品【%s】,迁移失败,错误【%s:%d】' . PHP_EOL, date('Y-h-d H:i:s'), $v['id'], $th->getMessage(), $th->getLine()) + sprintf('【%s】 产品【%s】,迁移失败,错误【%s:%d】' . PHP_EOL, date('Y-h-d H:i:s'), $v['id'], $th->getMessage(), $th->getLine()), + FILE_APPEND ); file_put_contents( runtime_path() . 'product_throwable_details.txt', @@ -505,6 +570,74 @@ class DataMigration extends Command ); } } + + $this->println(sprintf('迁移产品完成,共迁移 %s 条数据', $total)); + } + + // 迁移产品关联产品数据 + private function migrateProductRelated() + { + $sources = include_once(runtime_path() . 'product_success.php'); + $maps = []; + foreach ($sources as $so) { + $maps[$so['cod_product_id']] = $so; + } + + $old_db = Db::connect('old'); + $related = $old_db->name('product_related') + ->where('country_code', 'in', ['ZH', 'US']) + ->where('stat', '=', 0) + ->cursor(); + + $data = []; + foreach ($related as $rl) { + if (empty($maps[$rl['product_id']]) || empty($maps[$rl['related_product_id']])) { + continue; + } + $data[] = [ + 'product_id' => $maps[$rl['product_id']]['ow_product_id'], + 'related_product_id' => $maps[$rl['related_product_id']]['ow_product_id'], + 'desc' => empty($rl['related_desc']) ? null : $rl['related_desc'], + 'sort' => $rl['related_sort'] == 9999 ? 0 : $rl['related_sort'] + ]; + } + Db::name('product_related')->insertAll($data); + } + + // 迁移产品购买链接 + private function migrateProductPurchaseLinks() + { + $platform_maps = [ + 1 => 2, + 2 => 1, + 3 => 3, + 4 => 4 + ]; + + $sources = include_once(runtime_path() . 'product_success.php'); + $maps = []; + foreach ($sources as $so) { + $maps[$so['cod_product_id']] = $so; + } + + $old_db = Db::connect('old'); + $links = $old_db->name('product_purchase_links') + ->where('country_code', 'in', ['ZH', 'US']) + ->where('link', '<>', '') + ->cursor(); + + foreach ($links as $link) { + if (empty($maps[$link['product_id']])) { + continue; + } + $item = [ + 'language_id' => $link['country_code'] == 'ZH' ? 1 : 2, + 'product_id' => $maps[$link['product_id']]['ow_product_id'], + 'platform_id' => $platform_maps[$link['platform_id']], + 'link' => $link['link'] + ]; + Db::name('product_purchase_link')->insert($item); + } } // 迁移文章 @@ -758,11 +891,13 @@ class UploadMannager private $password = 'Aa-1221'; private $token = ''; private $retrys = []; + private $maps = []; public function __construct() { // 登录获取token $this->token = $this->getAuthorization(); + $this->maps = include_once(runtime_path() . 'fiber_product_image_mapping.php'); } // 下载图片 @@ -805,21 +940,14 @@ class UploadMannager $file_path = self::DOWNLOAD_TEMP_PATH . $file_name; if (file_exists($file_path)) { + if (!is_valid_image($file_path)) { + return ''; + } return $file_path; } - // 使用file_get_contents下载 - // $opts = [ - // 'http' => [ - // 'method' => 'GET', - // 'header' => 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)' - // ] - // ]; - // $context = stream_context_create($opts); - // $file = file_get_contents($url, false, $context); - // 使用curl下载 - $file = $this->file_get_withcurl($url); + $file = file_get_withcurl($url); $dir = dirname($file_path); if (!is_dir($dir)) { mkdir($dir, 0777, true); @@ -828,34 +956,17 @@ class UploadMannager return $file_path; } - // 使用curl下载图片 - private function file_get_withcurl($url) - { - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 连接超时 10 秒 - curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 传输超时 30 秒 - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 跟随重定向 - curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0'); // 模拟浏览器 UA - - $data = curl_exec($ch); - if ($data === false) { - error_log(sprintf('cURL Error: %s; URL: %s', curl_error($ch), $url)); - return false; - } - curl_close($ch); - - return $data; - } - // 上传图片 public function upload($file_path, $field_name, $module = 'unknown') { if (empty($file_path)) { - return ['code' => 0, 'msg' => 'file_path为空', 'data' => ['path' => '']]; + return ['code' => 400, 'msg' => 'file_path为空', 'data' => ['path' => '']]; } if (\think\helper\Str::startsWith($file_path, 'http')) { return ['code' => 0, 'msg' => 'file_path为http', 'data' => ['path' => $file_path]]; } + if (isset($this->maps[$file_path])) { + return ['code' => 0, 'msg' => '成功', 'data' => ['path' => $this->maps[$file_path]]]; + } switch($field_name){ case 'image': @@ -939,3 +1050,48 @@ class UploadMannager return $result['data']['token']; } } + +// 使用curl下载图片 +function file_get_withcurl($url) +{ + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 连接超时 10 秒 + curl_setopt($ch, CURLOPT_TIMEOUT, 60); // 传输超时 30 秒 + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 跟随重定向 + curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0'); // 模拟浏览器 UA + + $data = curl_exec($ch); + if ($data === false) { + file_put_contents(runtime_path() . 'file_get_withcurl.txt', sprintf('cURL Error: %s; URL: %s' . PHP_EOL, curl_error($ch), $url), FILE_APPEND); + return false; + } + curl_close($ch); + + return $data; +} + +// 检查图片是否有效 +function is_valid_image($filepath) +{ + try { + $image_size = getimagesize($filepath); + if ($image_size === false) { + return false; + } + [$width, $height] = $image_size; + return $width > 0 && $height > 0; + } catch (\Throwable $e) { + return false; + } +} + +// 根据自定义函数检查数组 +function array_some($array, callable $ca): bool +{ + foreach ($array as $k => $v) { + if ($ca($k, $v)) return true; + } + + return false; +} diff --git a/app/index/controller/Attachment.php b/app/index/controller/Attachment.php index af288e40..5fe82545 100644 --- a/app/index/controller/Attachment.php +++ b/app/index/controller/Attachment.php @@ -111,7 +111,7 @@ class Attachment extends Common ]) ->withSearch(['name'], ['name' => $param['keyword']??null]) ->language($this->lang_id) - ->category($param['id']??null) + ->category($param['id']??$video_categorys[0]['id']??null) ->order(['sort' => 'asc', 'id' => 'desc']) ->paginate([ 'list_rows' => $param['size'], diff --git a/app/index/controller/Common.php b/app/index/controller/Common.php index 9040bb91..04eba78e 100644 --- a/app/index/controller/Common.php +++ b/app/index/controller/Common.php @@ -121,6 +121,7 @@ abstract class Common extends BaseController $products = ProductModel::field([ 'id', 'name', + 'short_name', 'cover_image', ]) ->language($language) diff --git a/app/index/controller/Index.php b/app/index/controller/Index.php index ab7610d2..efb15f72 100644 --- a/app/index/controller/Index.php +++ b/app/index/controller/Index.php @@ -26,7 +26,14 @@ class Index extends Common View::assign('featured_topics', $banner['featured_topics']); View::assign('video', array_shift($banner['video'])); View::assign('scenes', $banner['scenes']); - View::assign('brand_story', $banner['brand_story']); + View::assign('brand_story', array_map(function($item) { + $item['year'] = ''; + $arr = explode('-', $item['title']); + if (count($arr) > 1) { + $item['year'] = $arr[0]; + } + return $item; + }, $banner['brand_story']??null)); View::assign('data_statistics', $banner['data_statistics']); // 获取明星产品/热点产品 @@ -45,7 +52,7 @@ class Index extends Common private function getBannerData() { $banners = SysBannerModel::with(['items' => function($query) { - $query->where('type', '=', 'image')->where('status', '=', 1)->order(['sort' => 'asc', 'id' => 'desc']); + $query->where('type', 'IN', ['image', 'video'])->where('status', '=', 1)->order(['sort' => 'asc', 'id' => 'desc']); }]) ->uniqueLabel([ 'BANNER_67f61cd70e8e1', @@ -64,7 +71,7 @@ class Index extends Common foreach ($banners as $v) { $banner_map[$v->unique_label] = $v; } - + // 处理焦点轮播图和产品分类 $data['focus_images'] = []; // 焦点轮播图 $data['product_categorys'] = []; // 产品分类信息 diff --git a/app/index/controller/Product.php b/app/index/controller/Product.php index 080502bd..52a8205d 100644 --- a/app/index/controller/Product.php +++ b/app/index/controller/Product.php @@ -22,12 +22,100 @@ use think\helper\Arr; */ class Product extends Common { - // 产品分类 + // 产品分类 - 查看顶层分类 public function category() { // 参数 $param = request()->param(['id']); + // 获取分类及产品信息 + $categorys_data = ProductCategoryModel::field(['id', 'pid', 'name', 'path', 'level']) + ->language($this->lang_id) + ->displayed(true) + ->children($param['id']) + ->order(['pid' => 'asc', 'sort' => 'asc', 'id' => 'desc']) + ->select() + ->toArray(); + + $list = []; + if (!empty($categorys_data)) { + // 分组分类 + $list = array_filter($categorys_data, fn($it) => $it['level'] == 2); + foreach ($list as &$it) { + $it['children'] = array_column(array_filter($categorys_data, fn($v) => in_array($it['id'], explode(',', $v['path']))), 'id'); + } + unset($it); + + // 获取分类下的产品信息 + if (!empty($list)) { + $product_model = new ProductModel; + $sql = $product_model->field([ + 'id', + 'category_id', + 'spu', + 'name', + 'cover_image', + 'is_new', + '(' . $list[0]['id'] . ')' => 'group_mark' + ]) + ->byCategory(data_get($list[0], 'children')) + ->language($this->lang_id) + ->enabled(true) + ->onSale(true) + ->onShelves(true) + ->append(['p' => $list[0]['id']]) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->limit(5) + ->buildSql(); + $query = \think\facade\Db::table("($sql) as a"); + foreach ($list as $it) { + $query = $query->union(function($query) use($product_model, $it) { + $query->model($product_model) + ->name($product_model->getName()) + ->field([ + 'id', + 'category_id', + 'spu', + 'name', + 'cover_image', + 'is_new', + '(' . $it['id'] . ')' => 'group_mark' + ]) + ->byCategory($it['children']) + ->language($this->lang_id) + ->enabled(true) + ->onSale(true) + ->onShelves(true) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->limit(5); + }); + } + $pros = $query->select(); + if (!empty($pros)) { + $pros_map = []; + foreach ($pros as $pro) { + $pros_map[$pro['group_mark']][] = $pro; + } + foreach ($list as &$it) { + if (isset($pros_map[$it['id']])) { + $it['products'] = $pros_map[$it['id']]; + } + unset($it['children']); + } + unset($it); + } + } + } + View::assign('list', $list); + + return View::fetch('category'); + } + // 产品分类 - 查看子类 + public function subcategory() + { + // 参数 + $param = request()->param(['id']); + $focus_image = []; // 获取产品分类页焦点横幅 $banner = SysBannerModel::with(['items' => function($query) { @@ -60,6 +148,7 @@ class Product extends Common ->child($param['id'], true) ->order(['sort' => 'asc', 'id' => 'desc']) ->select(); + if (!$categorys_data->isEmpty()) { if ($categorys_data->count() > 1) { // 当分类数不只一个时,当前分类下有子分类,移除当前分类,只输出子分类 @@ -70,8 +159,8 @@ class Product extends Common $categorys_data = $categorys_data->toArray(); $products = ProductModel::field([ - 'id', - 'category_id', + 'id', + 'category_id', 'spu', 'name', 'short_name', @@ -151,7 +240,40 @@ class Product extends Common } View::assign('categorys_data', $categorys_data); - return View::fetch('category'); + return View::fetch('subcategory'); + } + + /** + * 产品搜索 + */ + public function search() + { + $keywords = request()->param('keywords', ''); + + // 关键词搜索 + $products = ProductModel::field([ + 'id', + 'name', + 'short_name', + 'cover_image', + 'spu' + ]) + ->where(fn ($query) => $query->withSearch(['keywords'], ['keywords' => $keywords])) + ->language($this->lang_id) + ->enabled(true) + ->onSale(true) + ->onShelves(true) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->select() + ->each(function ($item) use($keywords) { + $item['spu'] = str_replace($keywords, ''.$keywords.'', $item['spu']); + $item['name'] = str_replace($keywords, ''.$keywords.'', $item['name']); + $item['short_name'] = str_replace($keywords, ''.$keywords.'', $item['short_name']); + return $item; + }); + View::assign('products', $products); + + return View::fetch('search'); } /** @@ -334,7 +456,7 @@ class Product extends Common $newpros = []; // 获取新品上市产品 - $products = ProductModel::field(['id', 'category_id', 'name', 'spu', 'cover_image']) + $products = ProductModel::field(['id', 'category_id', 'name', 'spu', 'cover_image', 'is_new']) ->language($this->lang_id) ->enabled(true) ->onSale(true) @@ -343,24 +465,45 @@ class Product extends Common ->order(['sort' => 'asc', 'id' => 'desc']) ->select(); if (!$products->isEmpty()) { - // 按分类分组产品 - $products_map = []; - foreach ($products as $product) { - $products_map[$product['category_id']][] = $product; - } // 获取产品分类信息 - $categorys = ProductCategoryModel::field(['id', 'name']) - ->byPks(array_keys($products_map)) + $categorys = ProductCategoryModel::field(['id', 'name', 'path', 'level']) + ->where(function($query) use($products) { + $query->where('id', 'IN', Arr::pluck($products, 'category_id'))->whereOr('level', '=', 2); + }) ->language($this->lang_id) ->displayed(true) - ->order(['sort' => 'asc', 'id' => 'desc']) + ->order(['sort' => 'asc', 'id' => 'asc']) ->select(); if (!$categorys->isEmpty()) { + // 根据产品分类path信息分组 + $map = []; foreach ($categorys as $category) { - $newpros[] = [ - 'category' => $category, - 'products' => $products_map[$category['id']] ?? [], - ]; + $map[$category['id']] = $category; + } + $pro_map = []; + foreach ($products as $pro) { + if (!isset($map[$pro['category_id']])) { + continue; + } + $m = $map[$pro['category_id']]; + $pro_map[$m['path']][] = $pro; + } + + // 获取二级分类下的产品信息 + foreach ($categorys as $val) { + if ($val['level'] != 2) { + continue; + } + + foreach ($pro_map as $k => $pro) { + if (in_array($val['id'], explode(',', $k))) { + $newpros[] = [ + 'category' => $val, + 'products' => $pro, + ]; + break; + } + } } } } diff --git a/app/index/lang/en-us.php b/app/index/lang/en-us.php index 62b86746..e0452af2 100644 --- a/app/index/lang/en-us.php +++ b/app/index/lang/en-us.php @@ -2,7 +2,8 @@ return [ 'header_navigation' => [ - 'product_categorys' => 'Products' + 'product_categorys' => 'Products', + 'store' => 'Store' ], 'header_search' => [ 'history' => 'Search History', @@ -163,6 +164,9 @@ return [ 'send_success' => 'Add Success!', 'send_fail' => 'Add Fail!', ], + 'product_newpro' => [ + 'view_all' => 'View all' + ], 'product_detail' => [ 'detail_section_title' => 'Product Description', 'related_products' => 'Related Products', diff --git a/app/index/lang/zh-cn.php b/app/index/lang/zh-cn.php index f12d0f37..a0f867e1 100644 --- a/app/index/lang/zh-cn.php +++ b/app/index/lang/zh-cn.php @@ -2,7 +2,8 @@ return [ 'header_navigation' => [ - 'product_categorys' => '产品列表' + 'product_categorys' => '产品列表', + 'store' => '店铺' ], 'header_search' => [ 'hot_product' => '热销产品', @@ -163,6 +164,9 @@ return [ 'send_success' => '信息已成功提交', 'send_fail' => '信息提交失败', ], + 'product_newpro' => [ + 'view_all' => '查看全部' + ], 'product_detail' => [ 'detail_section_title' => '产品详情', 'related_products' => '相关产品', diff --git a/app/index/model/ProductModel.php b/app/index/model/ProductModel.php index e5d18164..5b8a74f3 100644 --- a/app/index/model/ProductModel.php +++ b/app/index/model/ProductModel.php @@ -4,6 +4,7 @@ declare (strict_types = 1); namespace app\index\model; use app\common\model\ProductBaseModel; +use think\facade\Db; use think\model\concern\SoftDelete; /** @@ -74,4 +75,10 @@ class ProductModel extends ProductBaseModel { $query->where('is_new', '=', (int)$stat); } + + // 关键词搜索 + public function searchKeywordsAttr($query, string $keywords) + { + $query->whereRaw('BINARY spu LIKE "%' . $keywords . '%" OR BINARY name LIKE "%' . $keywords . '%" OR BINARY short_name LIKE "%' . $keywords . '%"'); + } } diff --git a/app/index/view/attachment/video.html b/app/index/view/attachment/video.html index 8f6a0239..02302065 100644 --- a/app/index/view/attachment/video.html +++ b/app/index/view/attachment/video.html @@ -36,8 +36,8 @@ {notempty name="video_categorys"}
{/block} diff --git a/app/index/view/product/category.bak.html b/app/index/view/product/category.bak.html new file mode 100644 index 00000000..6363511d --- /dev/null +++ b/app/index/view/product/category.bak.html @@ -0,0 +1,71 @@ +{extend name="public/base" /} +{block name="style"} + +{/block} +{block name="main"} +
+ {notempty name="focus_image"} +
+ {volist name="focus_image" id="fimg"} + + {/volist} +
+ {/notempty} + +
+ {notempty name="categorys_data"} + {volist name="categorys_data" id="vo"} +
+ {$vo.name} + {eq name="vo.level" value="2"} + 查看更多 + {/eq} +
+ {notempty name="vo.products"} + + {/notempty} + {/volist} + {/notempty} +
+
+{/block} +{block name="script"} + +{/block} \ No newline at end of file diff --git a/app/index/view/product/category.html b/app/index/view/product/category.html index 93187046..597e265c 100644 --- a/app/index/view/product/category.html +++ b/app/index/view/product/category.html @@ -1,71 +1,36 @@ -{extend name="public/base" /} -{block name="style"} - -{/block} -{block name="main"} -
- {notempty name="focus_image"} -
- {volist name="focus_image" id="fimg"} - - {/volist} -
- {/notempty} - -
- {notempty name="categorys_data"} - {volist name="categorys_data" id="vo"} -
- {$vo.name} - {eq name="vo.level" value="2"} - 查看更多 - {/eq} -
- {notempty name="vo.products"} - - {/notempty} - {/volist} - {/notempty} -
-
-{/block} -{block name="script"} - +{extend name="public/base" /} +{block name="style"} + +{/block} +{block name="main"} +
+ +
+ {volist name="list" id="vo"} + + {/volist} +
+
{/block} \ No newline at end of file diff --git a/app/index/view/product/detail copy.html b/app/index/view/product/detail copy.html deleted file mode 100644 index c2d260a2..00000000 --- a/app/index/view/product/detail copy.html +++ /dev/null @@ -1,308 +0,0 @@ -{extend name="public/base" /} -{block name="title"} -{notempty name="product.seo_title"}{$product.seo_title}{else /}{__BLOCK__}{/notempty} -{/block} -{block name="seo"} -{notempty name="product.seo_keywords"} - - -{else/} -{__BLOCK__} -{/notempty} -{/block} -{block name="style"} - -{/block} -{block name="main"} -
- -
- -
- 首页 - {volist name="product_categorys" id="ca"} -
- {$ca.name} - {/volist} -
- -
- -
-
-
-
-
-
    -
  • - -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
-
-
-
-
- -
-
-
- - -
-
-

{$product.name|default=''}

-

{$product.desc|default=''}

-
-
    - {volist name="product_params" id="pp"} -
  • -
    {$pp.name}
    -
    -
    {$pp.value}
    -
  • - {/volist} -
-
- - {volist name="product_sku_attrs" id="ps"} -
-
{$ps.attr_name}
- -
- {/volist} -
- -
- {volist name="product_purchase_links" id="ppp"} - {$ppp.platform_name} - {/volist} - {:lang('product_detail.display_form')} -
-
-
- - - - {notempty name="product_related"} - - {/notempty} - -
-
- × -

{:lang('product_detail.display_form')}

-
-
-
- -
- - -
-
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- -
-
-
-
- -
-
- -
-
x
-
-
-{/block} -{block name="script"} - -{/block} \ No newline at end of file diff --git a/app/index/view/product/detail.html b/app/index/view/product/detail.html index 2f7beff9..40340301 100644 --- a/app/index/view/product/detail.html +++ b/app/index/view/product/detail.html @@ -31,42 +31,37 @@
{volist name="product_skus" id="sku" key="idx"}
- {if condition="!empty($product.video_img) && !empty($product.video_url) && $idx == 1"} -
- -
-
    -
  • -
-
-
- -
- -
- {/if}
+ +
    + {if condition="!empty($product.video_img) && !empty($product.video_url) && $idx == 1"} + +
  • + {/if} {volist name="sku.photo_album" id="photo"} -
  • +
  • {/volist}
+ +
- -
+ {if condition="!empty($product.video_img) && !empty($product.video_url) && $idx == 1"} + + + {else/} {notempty name="sku.photo_album[0]"} {/notempty} - -
+ {/if}
{/volist} @@ -110,8 +105,8 @@
- {volist name="product_purchase_links" id="ppp"} - {$ppp.platform_name} + {volist name="product_purchase_links" id="ppp" key="k"} + {$ppp.platform_name} {/volist} {:lang('product_detail.display_form')}
@@ -121,8 +116,10 @@
{:lang('product_detail.detail_section_title')} + {notempty name="product_related"} | {:lang('product_detail.related_products')}
+ {/notempty}
{$product.detail|default=''|raw} @@ -137,7 +134,7 @@ {volist name="product_related" id="related"}
  • - {$related.name} + {$related.name}

    {$related.name}

    {$related.spu}

    diff --git a/app/index/view/product/newpro.html b/app/index/view/product/newpro.html index 6c75dc8a..a425c586 100644 --- a/app/index/view/product/newpro.html +++ b/app/index/view/product/newpro.html @@ -16,26 +16,22 @@

    {$vo.category.name}
    - - View all - -

    {/volist}
  • -
    {/block} {block name="script"} diff --git a/app/index/view/product/product_category.html b/app/index/view/product/product_category.html deleted file mode 100644 index cbff7f93..00000000 --- a/app/index/view/product/product_category.html +++ /dev/null @@ -1,59 +0,0 @@ -{extend name="public/base" /} -{block name="style"} - -{block name="main"} -
    - -
    - -
    -
    -{/block} \ No newline at end of file diff --git a/app/index/view/product/search.html b/app/index/view/product/search.html index 0d52260e..2356987d 100644 --- a/app/index/view/product/search.html +++ b/app/index/view/product/search.html @@ -5,27 +5,33 @@ {block name="main"} {/block} \ No newline at end of file diff --git a/app/index/view/product/product_subcategory.html b/app/index/view/product/subcategory.html similarity index 96% rename from app/index/view/product/product_subcategory.html rename to app/index/view/product/subcategory.html index 681ac0f1..0e9ce46d 100644 --- a/app/index/view/product/product_subcategory.html +++ b/app/index/view/product/subcategory.html @@ -1,6 +1,7 @@ {extend name="public/base" /} {block name="style"} +{/block} {block name="main"}
    diff --git a/app/index/view/public/base.html b/app/index/view/public/base.html index ec2f923d..829b6c34 100644 --- a/app/index/view/public/base.html +++ b/app/index/view/public/base.html @@ -16,8 +16,8 @@ - - + + {block name="header"} diff --git a/app/index/view/public/footer.html b/app/index/view/public/footer.html index 6c2a0bd9..f80fe7b2 100644 --- a/app/index/view/public/footer.html +++ b/app/index/view/public/footer.html @@ -1,4 +1,5 @@ +{eq name="$Request.cookie.think_lang" value="en-us"}
    @@ -11,6 +12,7 @@ Need to contact us? Just send us an e-mail at odmmarket@orico.com.cn
    +{/eq}
    - - Store + {notempty name="basic_config['navigation_store_url']['value']"} + + {:lang('header_navigation.store')} + + {/notempty} @@ -109,7 +112,7 @@
    {$vo.name}
    -
    {$vo.name}
    +
    {$vo.short_name}
    {/volist} diff --git a/app/index/view/public/nas_header.html b/app/index/view/public/nas_header.html index f0b4f0b9..e52d3056 100644 --- a/app/index/view/public/nas_header.html +++ b/app/index/view/public/nas_header.html @@ -1,6 +1,8 @@
    - + + + {notempty name="header_navigation"}
    {/notempty} diff --git a/app/index/view/topic_nas/help.html b/app/index/view/topic_nas/help.html index 0b22e80d..f1658cb6 100644 --- a/app/index/view/topic_nas/help.html +++ b/app/index/view/topic_nas/help.html @@ -59,7 +59,7 @@ {if condition="!empty($co.desc) && str_contains($co.desc, ' + $co.title_txt_color])}>{$co.title} {else/} $co.title_txt_color])}>{$co.title} @@ -73,7 +73,7 @@ {if condition="!empty($co.desc) && str_contains($co.desc, ' + $co.title_txt_color])}>{$co.title} {else/} $co.title_txt_color])}>{$co.title} @@ -132,12 +132,20 @@ }); $('.nhlplxwmit:not(:first)').hover(function () { // 当鼠标移入时,显示.lxewmimg 并隐藏.lximg - $(this).find('.lxewmimg').show(); - $(this).find('.lximg').hide(); + var lxe = $(this).find('.lxewmimg'); + var lxi = $(this).find('.lximg'); + if (lxe.length > 0) { + lxe.show(); + lxi.hide(); + } }, function () { // 当鼠标移出时,隐藏.lxewmimg 并显示.lximg - $(this).find('.lxewmimg').hide(); - $(this).find('.lximg').show(); + var lxe = $(this).find('.lxewmimg'); + var lxi = $(this).find('.lximg'); + if (lxe.length > 0) { + lxe.hide(); + lxi.show(); + } }); }); }) diff --git a/composer.json b/composer.json index 51f53e19..5f3d933c 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ }, "require-dev": { "symfony/var-dumper": ">=4.2", - "topthink/think-trace": "^1.0" + "topthink/think-trace": "^1.0", + "swoole/ide-helper": "^6.0" }, "autoload": { "psr-4": { diff --git a/database/migrations/20241218064708_create_product.php b/database/migrations/20241218064708_create_product.php index 937f3f2d..0d9b680f 100644 --- a/database/migrations/20241218064708_create_product.php +++ b/database/migrations/20241218064708_create_product.php @@ -31,8 +31,8 @@ class CreateProduct extends Migrator $table->addColumn('language_id', 'integer', ['signed' => false, 'null' => false, 'comment' => '语言ID']) ->addColumn('category_id', 'integer', ['signed' => false , 'null' => true, 'comment' => '分类ID']) ->addColumn('spu', 'string', ['limit' => 255, 'null' => false, 'default' => '', 'comment' => '产品规格']) - ->addColumn('name', 'string', ['limit' => 125, 'null' => false, 'default' => '', 'comment' => '产品名称']) - ->addColumn('short_name', 'string', ['limit' => 64, 'null' => false, 'default' => '', 'comment' => '产品简称']) + ->addColumn('name', 'string', ['limit' => 255, 'null' => false, 'default' => '', 'comment' => '产品名称']) + ->addColumn('short_name', 'string', ['limit' => 255, 'null' => false, 'default' => '', 'comment' => '产品简称']) ->addColumn('cover_image', 'string', ['limit' => 255, 'null' => false, 'default' => '', 'comment' => '产品封面图片']) ->addColumn('desc', 'string', ['limit' => 255, 'null' => false, 'default' => '', 'comment' => '产品描述']) ->addColumn('video_img', 'string', ['limit' => 255, 'null' => false, 'default' => '', 'comment' => '产品视频封面']) @@ -47,7 +47,7 @@ class CreateProduct extends Migrator ->addColumn('status', 'boolean', ['null' => false, 'default' => 1, 'comment' => '状态:1启用,-1禁用']) ->addColumn('seo_title', 'string', ['limit' => 255, 'default' => null, 'comment' => 'seo标题']) ->addColumn('seo_keywords', 'string', ['limit' => 255, 'default' => null, 'comment' => 'seo关建词']) - ->addColumn('seo_desc', 'string', ['limit' => 255, 'default' => null, 'comment' => 'seo描述']) + ->addColumn('seo_desc', 'string', ['limit' => 512, 'default' => null, 'comment' => 'seo描述']) ->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP', 'comment' => '创建时间']) ->addColumn('updated_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP', 'update' => 'CURRENT_TIMESTAMP', 'comment' => '更新时间']) ->addColumn('deleted_at', 'timestamp', ['null' => true, 'comment' => '删除时间']) diff --git a/database/migrations/20241218083326_create_product_params.php b/database/migrations/20241218083326_create_product_params.php index ba79599c..d1256376 100644 --- a/database/migrations/20241218083326_create_product_params.php +++ b/database/migrations/20241218083326_create_product_params.php @@ -30,7 +30,7 @@ class CreateProductParams extends Migrator $table = $this->table('product_params', ['engine' => 'InnoDB', 'comment' => '产品参数表']); $table->addColumn('product_id', 'integer', ['signed' => false , 'null' => false, 'comment' => '产品ID']) ->addColumn('name', 'string', ['limit' => 125, 'null' => false, 'default' => '', 'comment' => '参数名']) - ->addColumn('value', 'string', ['limit' => 125, 'null' => false, 'default' => '', 'comment' => '参数值']) + ->addColumn('value', 'string', ['limit' => 1024, 'null' => false, 'default' => '', 'comment' => '参数值']) ->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP', 'comment' => '创建时间']) ->addColumn('updated_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP', 'update' => 'CURRENT_TIMESTAMP', 'comment' => '更新时间']) ->create(); diff --git a/public/migrate_temp_images/.gitignore b/public/migrate_temp_images/.gitignore new file mode 100644 index 00000000..c96a04f0 --- /dev/null +++ b/public/migrate_temp_images/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/public/static/index/images/logo_nas_en-us.png b/public/static/index/images/logo_nas_en-us.png new file mode 100644 index 00000000..8ffd61e4 Binary files /dev/null and b/public/static/index/images/logo_nas_en-us.png differ diff --git a/public/static/index/images/logo_nas.png b/public/static/index/images/logo_nas_zh-cn.png similarity index 100% rename from public/static/index/images/logo_nas.png rename to public/static/index/images/logo_nas_zh-cn.png