feat: 产品
refactor: 产品详情页seo
@@ -20,7 +20,7 @@ class ProductInquiryBaseModel extends BaseModel
|
|||||||
'id' => 'int',
|
'id' => 'int',
|
||||||
'language_id' => 'int',
|
'language_id' => 'int',
|
||||||
'corp_name' => 'string',
|
'corp_name' => 'string',
|
||||||
'fisrt_name' => 'string',
|
'first_name' => 'string',
|
||||||
'last_name' => 'string',
|
'last_name' => 'string',
|
||||||
'email' => 'string',
|
'email' => 'string',
|
||||||
'phone' => 'string',
|
'phone' => 'string',
|
||||||
|
|||||||
@@ -13,3 +13,82 @@ if (!function_exists('str_contains')) {
|
|||||||
return \think\helper\Str::contains($haystack, $needle);
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ abstract class Common extends BaseController
|
|||||||
// 当前语言id
|
// 当前语言id
|
||||||
protected $lang_id = 1;
|
protected $lang_id = 1;
|
||||||
|
|
||||||
|
// 网站配置中的基本配置项
|
||||||
|
protected $basic_config = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 控制器初始化
|
* 控制器初始化
|
||||||
* @access public
|
* @access public
|
||||||
@@ -49,6 +52,7 @@ abstract class Common extends BaseController
|
|||||||
|
|
||||||
// 获取系统配置
|
// 获取系统配置
|
||||||
$configs = $this->getSysConfig($this->lang_id, ['basic', 'contact', 'media']);
|
$configs = $this->getSysConfig($this->lang_id, ['basic', 'contact', 'media']);
|
||||||
|
$this->basic_config = $configs['basic'];
|
||||||
// 输出系统配置
|
// 输出系统配置
|
||||||
View::assign('basic_config', $configs['basic']);
|
View::assign('basic_config', $configs['basic']);
|
||||||
View::assign('contact_config', $configs['contact']);
|
View::assign('contact_config', $configs['contact']);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use app\index\model\ProductInquiryModel;
|
|||||||
use app\index\model\ProductModel;
|
use app\index\model\ProductModel;
|
||||||
use app\index\model\ProductParamsModel;
|
use app\index\model\ProductParamsModel;
|
||||||
use app\index\model\ProductPurchaseLinkModel;
|
use app\index\model\ProductPurchaseLinkModel;
|
||||||
|
use app\index\model\ProductRelatedModel;
|
||||||
use app\index\model\ProductSkuAttrModel;
|
use app\index\model\ProductSkuAttrModel;
|
||||||
use app\index\model\ProductSkuModel;
|
use app\index\model\ProductSkuModel;
|
||||||
use app\index\model\SysBannerModel;
|
use app\index\model\SysBannerModel;
|
||||||
@@ -177,6 +178,7 @@ class Product extends Common
|
|||||||
$product_skus = [];
|
$product_skus = [];
|
||||||
$product_sku_attrs = [];
|
$product_sku_attrs = [];
|
||||||
$product_purchase_links = [];
|
$product_purchase_links = [];
|
||||||
|
$product_related = [];
|
||||||
if (!empty($product)) {
|
if (!empty($product)) {
|
||||||
// 获取产品分类信息
|
// 获取产品分类信息
|
||||||
$product_categorys = ProductCategoryModel::field(['id', 'pid', 'name'])
|
$product_categorys = ProductCategoryModel::field(['id', 'pid', 'name'])
|
||||||
@@ -237,12 +239,24 @@ class Product extends Common
|
|||||||
->hidden(['platform'])
|
->hidden(['platform'])
|
||||||
->bindAttr('platform', ['platform_name' => 'platform'])
|
->bindAttr('platform', ['platform_name' => 'platform'])
|
||||||
->toArray();
|
->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_categorys', $product_categorys); // 产品分类
|
||||||
View::assign('product_skus', $product_skus);
|
View::assign('product_params', $product_params); // 产品参数
|
||||||
View::assign('product_sku_attrs', $product_sku_attrs);
|
View::assign('product_skus', $product_skus); // 产品sku
|
||||||
View::assign('product_purchase_links', $product_purchase_links);
|
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'];
|
$config = $this->basic_config['optional_country_for_product_inquiry'];
|
||||||
@@ -328,10 +342,7 @@ class Product extends Common
|
|||||||
->isNew(true)
|
->isNew(true)
|
||||||
->order(['sort' => 'asc', 'id' => 'desc'])
|
->order(['sort' => 'asc', 'id' => 'desc'])
|
||||||
->select();
|
->select();
|
||||||
if ($products->isEmpty()) {
|
if (!$products->isEmpty()) {
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按分类分组产品
|
// 按分类分组产品
|
||||||
$products_map = [];
|
$products_map = [];
|
||||||
foreach ($products as $product) {
|
foreach ($products as $product) {
|
||||||
@@ -352,6 +363,7 @@ class Product extends Common
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
View::assign('newpros', $newpros);
|
View::assign('newpros', $newpros);
|
||||||
|
|
||||||
return View::fetch('newpro');
|
return View::fetch('newpro');
|
||||||
|
|||||||
@@ -163,4 +163,40 @@ return [
|
|||||||
'send_success' => 'Add Success!',
|
'send_success' => 'Add Success!',
|
||||||
'send_fail' => 'Add Fail!',
|
'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!',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
@@ -163,4 +163,40 @@ return [
|
|||||||
'send_success' => '信息已成功提交',
|
'send_success' => '信息已成功提交',
|
||||||
'send_fail' => '信息提交失败',
|
'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' => '信息提交失败',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
15
app/index/model/ProductAttrModel.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductAttrBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品属性模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductAttrModel extends ProductAttrBaseModel
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
@@ -28,4 +28,21 @@ class ProductCategoryModel extends ProductCategoryBaseModel
|
|||||||
{
|
{
|
||||||
$query->where('is_show', '=', 1);
|
$query->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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
app/index/model/ProductInquiryModel.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductInquiryBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品询盘表模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductInquiryModel extends ProductInquiryBaseModel
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
@@ -17,6 +17,28 @@ class ProductModel extends ProductBaseModel
|
|||||||
// 软件删除时间字段
|
// 软件删除时间字段
|
||||||
protected $deleteTime = 'deleted_at';
|
protected $deleteTime = 'deleted_at';
|
||||||
|
|
||||||
|
// 关联产品分类
|
||||||
|
public function category()
|
||||||
|
{
|
||||||
|
return $this->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)
|
public function scopeLanguage($query, $language)
|
||||||
{
|
{
|
||||||
|
|||||||
23
app/index/model/ProductParamsModel.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductParamsBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品参数模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductParamsModel extends ProductParamsBaseModel
|
||||||
|
{
|
||||||
|
// 所属产品范围查询
|
||||||
|
public function scopeByProductId($query, $product)
|
||||||
|
{
|
||||||
|
if (is_array($product)) {
|
||||||
|
$query->whereIn('product_id', $product);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$query->where('product_id', '=', $product);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/index/model/ProductPurchaseLinkModel.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductPurchaseLinkBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品购买链接模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductPurchaseLinkModel extends ProductPurchaseLinkBaseModel
|
||||||
|
{
|
||||||
|
// 关联购买平台
|
||||||
|
public function platform()
|
||||||
|
{
|
||||||
|
return $this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/index/model/ProductPurchasePlatformModel.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductPurchasePlatformBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品购买平台模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductPurchasePlatformModel extends ProductPurchasePlatformBaseModel
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
25
app/index/model/ProductRelatedModel.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductRelatedBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相关产品表模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductRelatedModel extends ProductRelatedBaseModel
|
||||||
|
{
|
||||||
|
// 关联产品
|
||||||
|
public function product()
|
||||||
|
{
|
||||||
|
return $this->hasOne(ProductModel::class, 'id', 'related_product_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所属产品范围查询
|
||||||
|
public function scopeByProductId($builder, $product_id)
|
||||||
|
{
|
||||||
|
return $builder->where('product_id', '=', $product_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/index/model/ProductSkuAttrModel.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductSkuAttrBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品SKU属性模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductSkuAttrModel extends ProductSkuAttrBaseModel
|
||||||
|
{
|
||||||
|
// 所属sku范围查询
|
||||||
|
public function scopeBySkuId($query, $sku_ids)
|
||||||
|
{
|
||||||
|
return $query->where('sku_id', 'IN', $sku_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/index/model/ProductSkuModel.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\model;
|
||||||
|
|
||||||
|
use app\common\model\ProductSkuBaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品SKU模型
|
||||||
|
* @mixin \think\Model
|
||||||
|
*/
|
||||||
|
class ProductSkuModel extends ProductSkuBaseModel
|
||||||
|
{
|
||||||
|
// json字段
|
||||||
|
protected $json = ['photo_album'];
|
||||||
|
protected $jsonAssoc = true;
|
||||||
|
|
||||||
|
// 关联sku属性
|
||||||
|
public function attr()
|
||||||
|
{
|
||||||
|
return $this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,8 @@ Route::get('/', 'Index/index');
|
|||||||
|
|
||||||
// 产品相关路由
|
// 产品相关路由
|
||||||
Route::group('product', function() {
|
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');
|
Route::get('detail/:id', 'Product/detail')->name('product_detail');
|
||||||
// 产品询盘
|
// 产品询盘
|
||||||
|
|||||||
51
app/index/validate/ProductInquiryValidate.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
declare (strict_types = 1);
|
||||||
|
|
||||||
|
namespace app\index\validate;
|
||||||
|
|
||||||
|
use think\Validate;
|
||||||
|
|
||||||
|
class ProductInquiryValidate extends Validate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 定义验证规则
|
||||||
|
* 格式:'字段名' => ['规则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'
|
||||||
|
];
|
||||||
|
}
|
||||||
71
app/index/view/product/category.html
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{extend name="public/base" /}
|
||||||
|
{block name="style"}
|
||||||
|
<link rel="stylesheet" href="__CSS__/product_category.css" type="text/css" />
|
||||||
|
{/block}
|
||||||
|
{block name="main"}
|
||||||
|
<div class="orico_Page_products">
|
||||||
|
{notempty name="focus_image"}
|
||||||
|
<div class="focus_image">
|
||||||
|
{volist name="focus_image" id="fimg"}
|
||||||
|
<a {notempty name="fimg.link"}href="{$fimg.link}"{/notempty}><img src="{$fimg.image}" alt="" /></a>
|
||||||
|
{/volist}
|
||||||
|
</div>
|
||||||
|
{/notempty}
|
||||||
|
<!-- 首页主题内容 -->
|
||||||
|
<div class="pageMain">
|
||||||
|
{notempty name="categorys_data"}
|
||||||
|
{volist name="categorys_data" id="vo"}
|
||||||
|
<h1 class="ori-pd-title">
|
||||||
|
<span>{$vo.name}</span>
|
||||||
|
{eq name="vo.level" value="2"}
|
||||||
|
<a href="{:url('product_category', ['id' => $vo.id])}">查看更多</a>
|
||||||
|
{/eq}
|
||||||
|
</h1>
|
||||||
|
{notempty name="vo.products"}
|
||||||
|
<div class="ori-pd-list">
|
||||||
|
{volist name="vo.products" id="vp"}
|
||||||
|
<a class="oripditem" href="{:url('product_detail', ['id' => $vp.id])}">
|
||||||
|
<div>
|
||||||
|
{volist name="vp.sku" id="vs" key="vs_idx"}
|
||||||
|
<img src="{$vs.main_image}" id="sku_image_{$vs.id}" class="prdimg {eq name='vs_idx' value='1'}prdimg-show{/eq}" />
|
||||||
|
{/volist}
|
||||||
|
</div>
|
||||||
|
<div class="prdName">{$vp.name}</div>
|
||||||
|
<div class="prddec">{$vp.short_name}</div>
|
||||||
|
{notempty name="vp.colors"}
|
||||||
|
<div class="prd-colors">
|
||||||
|
{volist name="vp.colors" id="vc" key="vc_idx"}
|
||||||
|
<div class="prdolorit {eq name='vc_idx' value='1'}on{/eq}" data-sku_id="{$vc.sku_id}">
|
||||||
|
{assign name="color_type" value=":rgb_or_image($vc.attr_value)" /}
|
||||||
|
{eq name="color_type" value="IMAGE"}
|
||||||
|
<img src="{$vc.attr_value}" />
|
||||||
|
{elseif condition="$color_type == 'RGB'" /}
|
||||||
|
<span class="rgb_hex" {:style(['background-color'=>$vc.attr_value])}></span>
|
||||||
|
{/eq}
|
||||||
|
</div>
|
||||||
|
{/volist}
|
||||||
|
</div>
|
||||||
|
{/notempty}
|
||||||
|
</a>
|
||||||
|
{/volist}
|
||||||
|
</div>
|
||||||
|
{/notempty}
|
||||||
|
{/volist}
|
||||||
|
{/notempty}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/block}
|
||||||
|
{block name="script"}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
$('.prd-colors .prdolorit').click(function(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var sku_id = $(this).data('sku_id');
|
||||||
|
$('#sku_image_' + sku_id).addClass('prdimg-show').siblings().removeClass('prdimg-show');
|
||||||
|
$(this).addClass('on').siblings().removeClass('on');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{/block}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
{notempty name="product.seo_title"}<title>{$product.seo_title}</title>{else /}{__BLOCK__}{/notempty}
|
{notempty name="product.seo_title"}<title>{$product.seo_title}</title>{else /}{__BLOCK__}{/notempty}
|
||||||
{/block}
|
{/block}
|
||||||
{block name="seo"}
|
{block name="seo"}
|
||||||
{notempty name=""}
|
{notempty name="product.seo_keywords"}
|
||||||
<meta name="keywords" content="{$product.seo_keywords}" />
|
<meta name="keywords" content="{$product.seo_keywords}" />
|
||||||
<meta name="description" content="{$product.seo_description}" />
|
<meta name="description" content="{$product.seo_desc}" />
|
||||||
{else/}
|
{else/}
|
||||||
{__BLOCK__}
|
{__BLOCK__}
|
||||||
{/notempty}
|
{/notempty}
|
||||||
@@ -29,13 +29,14 @@
|
|||||||
<div class="cp">
|
<div class="cp">
|
||||||
<!--左边图片 -->
|
<!--左边图片 -->
|
||||||
<div class="cpfl">
|
<div class="cpfl">
|
||||||
{if condition="!empty($product.video_img) && !empty($product.video_url)"}
|
{volist name="product_skus" id="sku" key="idx"}
|
||||||
<div class="preview" id="preview{$sku.id}">
|
<div class="preview" id="preview{$sku.id}" {neq name="idx" value="1"}style="display:none"{/neq}>
|
||||||
|
{if condition="!empty($product.video_img) && !empty($product.video_url) && $idx == 1"}
|
||||||
<div class="smallImg">
|
<div class="smallImg">
|
||||||
<!-- 小图片预览 -->
|
<!-- 小图片预览 -->
|
||||||
<div id="imageMenu">
|
<div id="imageMenu">
|
||||||
<ul class="image_list">
|
<ul class="image_list">
|
||||||
<li id="onlickImg"><img src="{$product.video_img}" /></li>
|
<li id="onlickImg"><img src="{:thumb($product.video_img)}" /></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,20 +46,13 @@
|
|||||||
<source src="{$product.video_url}" type="video/mp4"/>
|
<source src="{$product.video_url}" type="video/mp4"/>
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{volist name="product_skus" id="sku" key="idx"}
|
|
||||||
{if condition="!empty($product.video_img) && !empty($product.video_url)"}
|
|
||||||
<div class="preview" id="preview{$sku.id}" style="display:none">
|
|
||||||
{else /}
|
|
||||||
<div class="preview" id="preview{$sku.id}" {neq name="idx" value="1"}style="display:none"{/neq}>
|
|
||||||
{/if}
|
{/if}
|
||||||
<div class="smallImg">
|
<div class="smallImg">
|
||||||
<!-- 小图片预览 -->
|
<!-- 小图片预览 -->
|
||||||
<div id="imageMenu">
|
<div id="imageMenu">
|
||||||
<ul class="image_list">
|
<ul class="image_list">
|
||||||
{volist name="sku.photo_album" id="thumb_image"}
|
{volist name="sku.photo_album" id="photo"}
|
||||||
<li id="onlickImg"><img src="{$thumb_image}" /></li>
|
<li id="onlickImg"><img src="{:thumb($photo)}" /></li>
|
||||||
{/volist}
|
{/volist}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,12 +119,34 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 产品介绍详情-->
|
<!-- 产品介绍详情-->
|
||||||
<div class="oriprInfo">
|
<div class="oriprInfo">
|
||||||
<div class="titleprinfo">{:lang('product_detail.detail_section_title')}</div>
|
<div class="titleprinfo">
|
||||||
|
<a href="#detail">{:lang('product_detail.detail_section_title')}</a>
|
||||||
|
<span>|</span>
|
||||||
|
<a href="#related">{:lang('product_detail.related_products')}</a></div>
|
||||||
<!-- 富文本渲染-->
|
<!-- 富文本渲染-->
|
||||||
<div class="products_des">
|
<div class="products_des" id="detail">
|
||||||
{$product.detail|default=''|raw}
|
{$product.detail|default=''|raw}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 相关产品 -->
|
||||||
|
{notempty name="product_related"}
|
||||||
|
<div id="related">
|
||||||
|
<p>{:lang('product_detail.related_products')}</p>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
{volist name="product_related" id="related"}
|
||||||
|
<li>
|
||||||
|
<a href="{:url('product/detail', ['id' => $related.id])}">
|
||||||
|
<img src="{$related.cover_img}" alt="{$related.name}" />
|
||||||
|
<p>{$related.name}</p>
|
||||||
|
<p>{$related.spu}</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/volist}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/notempty}
|
||||||
<!-- 弹框-->
|
<!-- 弹框-->
|
||||||
<div id="form_modal" class="XJmodal">
|
<div id="form_modal" class="XJmodal">
|
||||||
<div class="XJmodal-content">
|
<div class="XJmodal-content">
|
||||||
|
|||||||
BIN
public/static/common/images/colors/BK.png
Normal file
|
After Width: | Height: | Size: 149 B |
BIN
public/static/common/images/colors/BL.png
Normal file
|
After Width: | Height: | Size: 152 B |
BIN
public/static/common/images/colors/CF.png
Normal file
|
After Width: | Height: | Size: 152 B |
BIN
public/static/common/images/colors/CO.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/static/common/images/colors/CR.png
Normal file
|
After Width: | Height: | Size: 628 B |
BIN
public/static/common/images/colors/GD.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/static/common/images/colors/GR.png
Normal file
|
After Width: | Height: | Size: 152 B |
BIN
public/static/common/images/colors/GY.png
Normal file
|
After Width: | Height: | Size: 151 B |
BIN
public/static/common/images/colors/OR.png
Normal file
|
After Width: | Height: | Size: 151 B |
BIN
public/static/common/images/colors/PK.png
Normal file
|
After Width: | Height: | Size: 152 B |
BIN
public/static/common/images/colors/PU.png
Normal file
|
After Width: | Height: | Size: 151 B |
BIN
public/static/common/images/colors/RD.png
Normal file
|
After Width: | Height: | Size: 149 B |
BIN
public/static/common/images/colors/RG.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/static/common/images/colors/SV.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/static/common/images/colors/WD.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/static/common/images/colors/WH.png
Normal file
|
After Width: | Height: | Size: 151 B |
BIN
public/static/common/images/colors/YL.png
Normal file
|
After Width: | Height: | Size: 151 B |
@@ -2,21 +2,28 @@
|
|||||||
.orico_Page_products {
|
.orico_Page_products {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100vh;
|
min-height: 100vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: #f9f9f9;
|
background: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
.orico_Page_products .focus_image {
|
||||||
|
padding-top: 3.75rem;
|
||||||
|
}
|
||||||
|
.orico_Page_products .focus_image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
.orico_Page_products p, .orico_Page_products a, .orico_Page_products div, .orico_Page_products span {
|
.orico_Page_products p, .orico_Page_products a, .orico_Page_products div, .orico_Page_products span {
|
||||||
font-family: "Microsoft YaHei", "Arial", sans-serif;
|
font-family: "Microsoft YaHei", "Arial", sans-serif;
|
||||||
}
|
}
|
||||||
.orico_Page_products .pageMain {
|
.orico_Page_products .pageMain {
|
||||||
width: 85%;
|
width: 85%;
|
||||||
padding: 50px 0 10px;
|
padding: 50px 0 2.5rem;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-top: 2%;
|
|
||||||
}
|
}
|
||||||
.orico_Page_products .pageMain .ori-pd-title {
|
.orico_Page_products .pageMain .ori-pd-title {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
@@ -82,6 +89,11 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 0.65rem;
|
border-radius: 0.65rem;
|
||||||
}
|
}
|
||||||
|
.orico_Page_products .pageMain .ori-pd-list .oripditem .prd-colors .prdolorit .rgb_hex {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
.orico_Page_products .pageMain .ori-pd-list .oripditem:hover {
|
.orico_Page_products .pageMain .ori-pd-list .oripditem:hover {
|
||||||
box-shadow: 0px 5px 35px rgba(227, 227, 227, 0.75);
|
box-shadow: 0px 5px 35px rgba(227, 227, 227, 0.75);
|
||||||
transform: translate3d(0, -2px, 0);
|
transform: translate3d(0, -2px, 0);
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
.orico_Page_prdetail {
|
.orico_Page_prdetail {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100vh;
|
min-height: 100vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: #f1f1f1;
|
background: #f1f1f1;
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
}
|
}
|
||||||
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgUp,
|
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgUp,
|
||||||
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgUp.disabled {
|
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgUp.disabled {
|
||||||
background: url(../productDetailimg/fl.png) no-repeat;
|
background: url(/static/index/images/fl.png) no-repeat;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -118,11 +118,11 @@
|
|||||||
}
|
}
|
||||||
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgUp:hover,
|
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgUp:hover,
|
||||||
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgUp.disabled:hover {
|
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgUp.disabled:hover {
|
||||||
background: url(../productDetailimg/fl1.png) no-repeat;
|
background: url(/static/index/images/fl1.png) no-repeat;
|
||||||
}
|
}
|
||||||
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgDown,
|
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgDown,
|
||||||
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgDown.disabled {
|
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgDown.disabled {
|
||||||
background: url(../productDetailimg/rh.png) no-repeat;
|
background: url(/static/index/images/rh.png) no-repeat;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
right: -2%;
|
right: -2%;
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
}
|
}
|
||||||
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgDown:hover,
|
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgDown:hover,
|
||||||
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgDown.disabled:hover {
|
.orico_Page_prdetail .oriprdetail .cp .cpfl .preview .bigImg .smallImgDown.disabled:hover {
|
||||||
background: url(../productDetailimg/rh1.png) no-repeat;
|
background: url(/static/index/images/rh1.png) no-repeat;
|
||||||
}
|
}
|
||||||
.orico_Page_prdetail .oriprdetail .cp .cprh {
|
.orico_Page_prdetail .oriprdetail .cp .cprh {
|
||||||
width: 45%;
|
width: 45%;
|
||||||
BIN
public/static/index/images/fl.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/static/index/images/fl1.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/static/index/images/rh.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/static/index/images/rh1.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |