From 25969889657fb3cd40c824f90745b6d1cab7a2f7 Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Thu, 24 Apr 2025 10:21:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 产品详情页seo --- app/common/model/ProductInquiryBaseModel.php | 2 +- app/index/common.php | 79 ++++++++++++++++++ app/index/controller/Common.php | 4 + app/index/controller/Product.php | 74 +++++++++------- app/index/lang/en-us.php | 40 ++++++++- app/index/lang/zh-cn.php | 40 ++++++++- app/index/model/ProductAttrModel.php | 15 ++++ app/index/model/ProductCategoryModel.php | 17 ++++ app/index/model/ProductInquiryModel.php | 15 ++++ app/index/model/ProductModel.php | 22 +++++ app/index/model/ProductParamsModel.php | 23 +++++ app/index/model/ProductPurchaseLinkModel.php | 28 +++++++ .../model/ProductPurchasePlatformModel.php | 15 ++++ app/index/model/ProductRelatedModel.php | 25 ++++++ app/index/model/ProductSkuAttrModel.php | 19 +++++ app/index/model/ProductSkuModel.php | 33 ++++++++ app/index/route/route.php | 4 +- app/index/validate/ProductInquiryValidate.php | 51 +++++++++++ app/index/view/product/category.html | 71 ++++++++++++++++ app/index/view/product/detail.html | 50 +++++++---- public/static/common/images/colors/BK.png | Bin 0 -> 149 bytes public/static/common/images/colors/BL.png | Bin 0 -> 152 bytes public/static/common/images/colors/CF.png | Bin 0 -> 152 bytes public/static/common/images/colors/CO.png | Bin 0 -> 1096 bytes public/static/common/images/colors/CR.png | Bin 0 -> 628 bytes public/static/common/images/colors/GD.png | Bin 0 -> 1364 bytes public/static/common/images/colors/GR.png | Bin 0 -> 152 bytes public/static/common/images/colors/GY.png | Bin 0 -> 151 bytes public/static/common/images/colors/OR.png | Bin 0 -> 151 bytes public/static/common/images/colors/PK.png | Bin 0 -> 152 bytes public/static/common/images/colors/PU.png | Bin 0 -> 151 bytes public/static/common/images/colors/RD.png | Bin 0 -> 149 bytes public/static/common/images/colors/RG.png | Bin 0 -> 1488 bytes public/static/common/images/colors/SV.png | Bin 0 -> 1090 bytes public/static/common/images/colors/WD.png | Bin 0 -> 1082 bytes public/static/common/images/colors/WH.png | Bin 0 -> 151 bytes public/static/common/images/colors/YL.png | Bin 0 -> 151 bytes .../{products.css => product_category.css} | 18 +++- ...products_detail.css => product_detail.css} | 10 +-- public/static/index/images/fl.png | Bin 0 -> 1626 bytes public/static/index/images/fl1.png | Bin 0 -> 1627 bytes public/static/index/images/rh.png | Bin 0 -> 1617 bytes public/static/index/images/rh1.png | Bin 0 -> 1636 bytes 43 files changed, 592 insertions(+), 63 deletions(-) create mode 100644 app/index/model/ProductAttrModel.php create mode 100644 app/index/model/ProductInquiryModel.php create mode 100644 app/index/model/ProductParamsModel.php create mode 100644 app/index/model/ProductPurchaseLinkModel.php create mode 100644 app/index/model/ProductPurchasePlatformModel.php create mode 100644 app/index/model/ProductRelatedModel.php create mode 100644 app/index/model/ProductSkuAttrModel.php create mode 100644 app/index/model/ProductSkuModel.php create mode 100644 app/index/validate/ProductInquiryValidate.php create mode 100644 app/index/view/product/category.html create mode 100644 public/static/common/images/colors/BK.png create mode 100644 public/static/common/images/colors/BL.png create mode 100644 public/static/common/images/colors/CF.png create mode 100644 public/static/common/images/colors/CO.png create mode 100644 public/static/common/images/colors/CR.png create mode 100644 public/static/common/images/colors/GD.png create mode 100644 public/static/common/images/colors/GR.png create mode 100644 public/static/common/images/colors/GY.png create mode 100644 public/static/common/images/colors/OR.png create mode 100644 public/static/common/images/colors/PK.png create mode 100644 public/static/common/images/colors/PU.png create mode 100644 public/static/common/images/colors/RD.png create mode 100644 public/static/common/images/colors/RG.png create mode 100644 public/static/common/images/colors/SV.png create mode 100644 public/static/common/images/colors/WD.png create mode 100644 public/static/common/images/colors/WH.png create mode 100644 public/static/common/images/colors/YL.png rename public/static/index/css/{products.css => product_category.css} (86%) rename public/static/index/css/{products_detail.css => product_detail.css} (97%) create mode 100755 public/static/index/images/fl.png create mode 100755 public/static/index/images/fl1.png create mode 100755 public/static/index/images/rh.png create mode 100755 public/static/index/images/rh1.png diff --git a/app/common/model/ProductInquiryBaseModel.php b/app/common/model/ProductInquiryBaseModel.php index ad219aa9..08589ad9 100644 --- a/app/common/model/ProductInquiryBaseModel.php +++ b/app/common/model/ProductInquiryBaseModel.php @@ -20,7 +20,7 @@ class ProductInquiryBaseModel extends BaseModel 'id' => 'int', 'language_id' => 'int', 'corp_name' => 'string', - 'fisrt_name' => 'string', + 'first_name' => 'string', 'last_name' => 'string', 'email' => 'string', 'phone' => 'string', diff --git a/app/index/common.php b/app/index/common.php index 724ba086..4760aaf7 100644 --- a/app/index/common.php +++ b/app/index/common.php @@ -13,3 +13,82 @@ if (!function_exists('str_contains')) { return \think\helper\Str::contains($haystack, $needle); } } + +if (!function_exists('rgb_or_image')) { + /** + * 判断字符串是否为RGB颜色值或图片链接 + * @param string $str 要判断的字符串 + * @return string IMAGE|RGB|'' + */ + function rgb_or_image(string $str): string + { + if ( + \think\helper\Str::startsWith($str, ['http', 'https']) || + \think\helper\Str::endsWith($str, ['.png', '.jpg', '.jpeg', '.gif', '.tif', '.svg', '.webp', '.bmp']) || + preg_match('/^data:image\/[jpg|jpeg|png|svg\+xml];base64,.+$/', $str) + ) { + return 'IMAGE'; + } + + if ( + preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', $str) || + preg_match('/^rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)$/', $str) || + preg_match('/^rgba\(\d{1,3},\s*\d{1,3},\s*\d{1,3},\s*\d*(\.\d+)?\)$/', $str) + ) { + return 'RGB'; + } + + return ''; + } +} + +if (!function_exists('style')) { + /** + * 输出style行内样式 - 避免编辑器因css校验规则提示错误 + * @param array|string $styles 样式数组 + * @return string + */ + function style(array|string $styles): string + { + $css = ''; + if (is_array($styles)) { + $key = array_keys($styles); + foreach ($key as $v) { + $css .= $v . ':' . $styles[$v] . ';'; + } + } else { + $css = $styles; + } + return 'style="' . $css . '"'; + } +} + +if (!function_exists('thumb')) { + /** + * 获取缩略图 + * @param string $url 图片地址 + * @return string + */ + function thumb(string $url): string + { + if (empty($url)) { + return ''; + } + if ( + str_contains($url, '_thumb') || + \think\helper\Str::startsWith($url, ['http://', 'https://']) || + !\think\helper\Str::endsWith($url, ['.png', '.jpg', '.jpeg', '.gif', '.tif', '.svg', '.webp', '.bmp']) + ) { + return $url; + } + + $idx = mb_strripos($url, '.', 0, 'utf-8'); + if ($idx === false) { + return $url; + } + + $len = mb_strlen($url, 'utf-8'); + + return mb_substr($url, 0, $idx, 'utf-8') . '_thumb' . mb_substr($url, $idx, $len - $idx, 'utf-8'); + } +} diff --git a/app/index/controller/Common.php b/app/index/controller/Common.php index 2e4b8c50..f1fc92ec 100644 --- a/app/index/controller/Common.php +++ b/app/index/controller/Common.php @@ -17,6 +17,9 @@ abstract class Common extends BaseController // 当前语言id protected $lang_id = 1; + // 网站配置中的基本配置项 + protected $basic_config = []; + /** * 控制器初始化 * @access public @@ -49,6 +52,7 @@ abstract class Common extends BaseController // 获取系统配置 $configs = $this->getSysConfig($this->lang_id, ['basic', 'contact', 'media']); + $this->basic_config = $configs['basic']; // 输出系统配置 View::assign('basic_config', $configs['basic']); View::assign('contact_config', $configs['contact']); diff --git a/app/index/controller/Product.php b/app/index/controller/Product.php index 06a8ca5d..080502bd 100644 --- a/app/index/controller/Product.php +++ b/app/index/controller/Product.php @@ -9,6 +9,7 @@ use app\index\model\ProductInquiryModel; use app\index\model\ProductModel; use app\index\model\ProductParamsModel; use app\index\model\ProductPurchaseLinkModel; +use app\index\model\ProductRelatedModel; use app\index\model\ProductSkuAttrModel; use app\index\model\ProductSkuModel; use app\index\model\SysBannerModel; @@ -172,11 +173,12 @@ class Product extends Common ->find(); View::assign('product', $product); - $product_categorys = []; - $product_params = []; - $product_skus = []; - $product_sku_attrs = []; + $product_categorys = []; + $product_params = []; + $product_skus = []; + $product_sku_attrs = []; $product_purchase_links = []; + $product_related = []; if (!empty($product)) { // 获取产品分类信息 $product_categorys = ProductCategoryModel::field(['id', 'pid', 'name']) @@ -237,12 +239,24 @@ class Product extends Common ->hidden(['platform']) ->bindAttr('platform', ['platform_name' => 'platform']) ->toArray(); + + // 获取相关产品信息 + $related = ProductRelatedModel::with(['product' => function($query) { + $query->field(['id', 'name', 'spu', 'cover_image']); + }]) + ->byProductId($product['id']) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->select(); + if (!$related->isEmpty()) { + $product_related = Arr::pluck($related, 'product'); + } } - View::assign('product_categorys', $product_categorys); - View::assign('product_params', $product_params); - View::assign('product_skus', $product_skus); - View::assign('product_sku_attrs', $product_sku_attrs); - View::assign('product_purchase_links', $product_purchase_links); + View::assign('product_categorys', $product_categorys); // 产品分类 + View::assign('product_params', $product_params); // 产品参数 + View::assign('product_skus', $product_skus); // 产品sku + View::assign('product_sku_attrs', $product_sku_attrs); // 产品sku属性 + View::assign('product_purchase_links', $product_purchase_links); // 产品购买链接 + View::assign('product_related', $product_related); // 相关产品 // 获取询盘可选国家 $config = $this->basic_config['optional_country_for_product_inquiry']; @@ -328,28 +342,26 @@ class Product extends Common ->isNew(true) ->order(['sort' => 'asc', 'id' => 'desc']) ->select(); - if ($products->isEmpty()) { - return []; - } - - // 按分类分组产品 - $products_map = []; - foreach ($products as $product) { - $products_map[$product['category_id']][] = $product; - } - // 获取产品分类信息 - $categorys = ProductCategoryModel::field(['id', 'name']) - ->byPks(array_keys($products_map)) - ->language($this->lang_id) - ->displayed(true) - ->order(['sort' => 'asc', 'id' => 'desc']) - ->select(); - if (!$categorys->isEmpty()) { - foreach ($categorys as $category) { - $newpros[] = [ - 'category' => $category, - 'products' => $products_map[$category['id']] ?? [], - ]; + 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)) + ->language($this->lang_id) + ->displayed(true) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->select(); + if (!$categorys->isEmpty()) { + foreach ($categorys as $category) { + $newpros[] = [ + 'category' => $category, + 'products' => $products_map[$category['id']] ?? [], + ]; + } } } View::assign('newpros', $newpros); diff --git a/app/index/lang/en-us.php b/app/index/lang/en-us.php index 8833ef77..dec73640 100644 --- a/app/index/lang/en-us.php +++ b/app/index/lang/en-us.php @@ -160,7 +160,43 @@ return [ 'validate_message_require' => 'Message is required', 'validate_message_max' => 'Message cannot exceed 1024 characters', // 返回文本 - 'send_success' => 'Add Success!', - 'send_fail' => 'Add Fail!', + 'send_success' => 'Add Success!', + 'send_fail' => 'Add Fail!', + ], + 'product_detail' => [ + 'detail_section_title' => 'Product Description', + 'related_products' => 'Related Products', + 'display_form' => 'Send Inquiry', + 'form_name' => 'Name', + 'form_first_name_placeholder' => 'First name', + 'form_last_name_placeholder' => 'Last name', + 'form_corp' => 'Company/Organization', + 'form_email' => 'Email Address', + 'form_phone' => 'Phone Number', + 'form_country' => 'Country', + 'form_country_placeholder' => '--- Select your country ---', + 'form_industry' => 'Industry', + 'form_inquiry' => 'Inquiry', + 'form_submit' => 'SUBMIT', + // 验证器中文本 + 'validate_first_name_require' => 'First Name is required', + 'validate_first_name_max' => 'First Name cannot exceed 64 characters', + 'validate_last_name_require' => 'Last Name is required', + 'validate_last_name_max' => 'Last Name cannot exceed 64 characters', + 'validate_email_require' => 'Email is required', + 'validate_email_email' => 'Email format is incorrect', + 'validate_email_max' => 'Email cannot exceed 128 characters', + 'validate_phone_max' => 'Phone Number cannot exceed 32 characters', + 'validate_country_name_require' => 'Country is required', + 'validate_country_name_max' => 'Country cannot exceed 128 characters', + 'validate_corp_name_require' => 'Company/Organization is required', + 'validate_corp_name_max' => 'Company/Organization cannot exceed 128 characters', + 'validate_industry_require' => 'Industry is required', + 'validate_industry_max' => 'Industry cannot exceed 64 characters', + 'validate_message_require' => 'Message is required', + 'validate_message_max' => 'Message cannot exceed 1024 characters', + // 返回文本 + 'send_success' => 'Add Success!', + 'send_fail' => 'Add Fail!', ], ]; \ No newline at end of file diff --git a/app/index/lang/zh-cn.php b/app/index/lang/zh-cn.php index 4a517141..f26ef7b8 100644 --- a/app/index/lang/zh-cn.php +++ b/app/index/lang/zh-cn.php @@ -160,7 +160,43 @@ return [ 'validate_message_require' => '留言内容不能为空', 'validate_message_max' => '留言内容不能超过1024个字符', // 返回文本 - 'send_success' => '信息已成功提交', - 'send_fail' => '信息提交失败', + 'send_success' => '信息已成功提交', + 'send_fail' => '信息提交失败', + ], + 'product_detail' => [ + 'detail_section_title' => '产品详情', + 'related_products' => '相关产品', + 'display_form' => '发送查询', + 'form_name' => '姓名', + 'form_first_name_placeholder' => '请输入您的姓', + 'form_last_name_placeholder' => '请输入您的名', + 'form_corp' => '公司/组织', + 'form_email' => '电子邮箱', + 'form_phone' => '电话号码', + 'form_country' => '国家', + 'form_country_placeholder' => '请选择所属国家', + 'form_industry' => '行业', + 'form_inquiry' => '询问内容', + 'form_submit' => '提交', + // 验证器中文本 + 'validate_first_name_require' => '名不能为空', + 'validate_first_name_max' => '名不能超过64个字符', + 'validate_last_name_require' => '姓不能为空', + 'validate_last_name_max' => '姓不能超过64个字符', + 'validate_email_require' => '邮箱不能为空', + 'validate_email_email' => '邮箱格式不正确', + 'validate_email_max' => '邮箱不能超过128个字符', + 'validate_phone_max' => '电话号码不能超过32个字符', + 'validate_country_name_require' => '国家不能为空', + 'validate_country_name_max' => '国家不能超过128个字符', + 'validate_corp_name_require' => '公司/组织不能为空', + 'validate_corp_name_max' => '公司/组织不能超过128个字符', + 'validate_industry_require' => '行业不能为空', + 'validate_industry_max' => '行业不能超过64个字符', + 'validate_message_require' => '询问内容不能为空', + 'validate_message_max' => '询问内容不能超过1024个字符', + // 返回文本 + 'send_success' => '信息已成功提交', + 'send_fail' => '信息提交失败', ], ]; \ No newline at end of file diff --git a/app/index/model/ProductAttrModel.php b/app/index/model/ProductAttrModel.php new file mode 100644 index 00000000..81f65dc5 --- /dev/null +++ b/app/index/model/ProductAttrModel.php @@ -0,0 +1,15 @@ +where('is_show', '=', 1); } + + // 所属所有子分类范围查询 + public function scopeChild($query, $id, $merge_self = false) + { + $query->where(function($q) use($id, $merge_self) { + $q->where('pid', '=', $id); + if ($merge_self) { + $q->whereOr('id', '=', $id); + } + }); + } + + // 所属所有子孙分类范围查询 + public function scopeChildren($query, $id) + { + $query->whereRaw('FIND_IN_SET(:id, path)', ['id' => $id]); + } } diff --git a/app/index/model/ProductInquiryModel.php b/app/index/model/ProductInquiryModel.php new file mode 100644 index 00000000..bc6adc1b --- /dev/null +++ b/app/index/model/ProductInquiryModel.php @@ -0,0 +1,15 @@ +belongsTo(ProductCategoryModel::class, 'category_id', 'id'); + } + + // 关联sku + public function sku() + { + return $this->hasMany(ProductSkuModel::class, 'product_id', 'id'); + } + + // 所属分类范围查询 + public function scopeByCategory($query, $category) + { + if (is_array($category)) { + $query->whereIn('category_id', $category); + return; + } + $query->where('category_id', '=', $category); + } + // 所属语言范围查询 public function scopeLanguage($query, $language) { diff --git a/app/index/model/ProductParamsModel.php b/app/index/model/ProductParamsModel.php new file mode 100644 index 00000000..e347702c --- /dev/null +++ b/app/index/model/ProductParamsModel.php @@ -0,0 +1,23 @@ +whereIn('product_id', $product); + return; + } + $query->where('product_id', '=', $product); + } +} diff --git a/app/index/model/ProductPurchaseLinkModel.php b/app/index/model/ProductPurchaseLinkModel.php new file mode 100644 index 00000000..1e7c55a3 --- /dev/null +++ b/app/index/model/ProductPurchaseLinkModel.php @@ -0,0 +1,28 @@ +hasOne(ProductPurchasePlatformModel::class, 'id', 'platform_id'); + } + + // 所属产品范围查询 + public function scopeByProductId($query, $product_id) + { + if (is_array($product_id)) { + return $query->where('product_id', 'IN', $product_id); + } + return $query->where('product_id', '=', $product_id); + } +} diff --git a/app/index/model/ProductPurchasePlatformModel.php b/app/index/model/ProductPurchasePlatformModel.php new file mode 100644 index 00000000..f450f9fc --- /dev/null +++ b/app/index/model/ProductPurchasePlatformModel.php @@ -0,0 +1,15 @@ +hasOne(ProductModel::class, 'id', 'related_product_id'); + } + + // 所属产品范围查询 + public function scopeByProductId($builder, $product_id) + { + return $builder->where('product_id', '=', $product_id); + } +} diff --git a/app/index/model/ProductSkuAttrModel.php b/app/index/model/ProductSkuAttrModel.php new file mode 100644 index 00000000..7dfa08ca --- /dev/null +++ b/app/index/model/ProductSkuAttrModel.php @@ -0,0 +1,19 @@ +where('sku_id', 'IN', $sku_ids); + } +} diff --git a/app/index/model/ProductSkuModel.php b/app/index/model/ProductSkuModel.php new file mode 100644 index 00000000..b47fade3 --- /dev/null +++ b/app/index/model/ProductSkuModel.php @@ -0,0 +1,33 @@ +hasMany(ProductSkuAttrModel::class, 'sku_id', 'id'); + } + + // 所属产品范围查询 + public function scopeByProductId($query, $product) + { + if (is_array($product)) { + $query->whereIn('product_id', $product); + return; + } + $query->where('product_id', '=', $product); + } +} diff --git a/app/index/route/route.php b/app/index/route/route.php index 11ba8770..3ffeb845 100644 --- a/app/index/route/route.php +++ b/app/index/route/route.php @@ -14,8 +14,8 @@ Route::get('/', 'Index/index'); // 产品相关路由 Route::group('product', function() { - // 产品列表页 - Route::get('index/:id', 'Product/index')->name('product_index'); + // 产品分类页 + Route::get('category/:id', 'Product/category')->name('product_category'); // 产品详情页 Route::get('detail/:id', 'Product/detail')->name('product_detail'); // 产品询盘 diff --git a/app/index/validate/ProductInquiryValidate.php b/app/index/validate/ProductInquiryValidate.php new file mode 100644 index 00000000..0613fbd8 --- /dev/null +++ b/app/index/validate/ProductInquiryValidate.php @@ -0,0 +1,51 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = [ + 'first_name' => 'require|max:64', + 'last_name' => 'require|max:64', + 'email' => 'require|email|max:128', + 'phone' => 'require|max:64', + 'country_name' => 'require|max:128', + 'corp_name' => 'require|max:128', + 'industry' => 'require|max:64', + 'message' => 'require|max:1024', + ]; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = [ + 'first_name.require' => 'validate_first_name_require', + 'first_name.max' => 'validate_first_name_max', + 'last_name.require' => 'validate_last_name_require', + 'last_name.max' => 'validate_last_name_max', + 'email.require' => 'validate_email_require', + 'email.email' => 'validate_email_email', + 'email.max' => 'validate_email_max', + 'phone.max' => 'validate_phone_max', + 'country_name.require' => 'validate_country_name_require', + 'country_name.max' => 'validate_country_name_max', + 'corp_name.require' => 'validate_corp_name_require', + 'corp_name.max' => 'validate_corp_name_max', + 'industry.require' => 'validate_industry_require', + 'industry.max' => 'validate_industry_max', + 'message.require' => 'validate_message_require', + 'message.max' => 'validate_message_max' + ]; +} diff --git a/app/index/view/product/category.html b/app/index/view/product/category.html new file mode 100644 index 00000000..a50d7c02 --- /dev/null +++ b/app/index/view/product/category.html @@ -0,0 +1,71 @@ +{extend name="public/base" /} +{block name="style"} + +{/block} +{block name="main"} +