From a1c7ae62ba7d00f06e7b2b223eb29da40716b523 Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Fri, 27 Mar 2026 14:03:14 +0800 Subject: [PATCH 01/58] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E6=8E=A8=E8=8D=90=E6=95=B0=E6=8D=AE=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + .../v1/ProductCategoryRecommend.php | 200 ++++++++++++++++++ .../v1/ProductCategoryRecommendModel.php | 47 ++++ app/admin/route/v1.php | 22 +- .../v1/ProductCategoryRecommendValidate.php | 63 ++++++ app/admin/validate/v1/ProductValidate.php | 2 - .../ProductCategoryRecommendBaseModel.php | 33 +++ ...3640_create_product_category_recommend.php | 50 +++++ 8 files changed, 416 insertions(+), 4 deletions(-) create mode 100644 app/admin/controller/v1/ProductCategoryRecommend.php create mode 100644 app/admin/model/v1/ProductCategoryRecommendModel.php create mode 100644 app/admin/validate/v1/ProductCategoryRecommendValidate.php create mode 100644 app/common/model/ProductCategoryRecommendBaseModel.php create mode 100644 database/migrations/20260326073640_create_product_category_recommend.php diff --git a/.gitignore b/.gitignore index 8c9279ce..1ee66503 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ public/.well-known /.settings /.buildpath /.project + +CLAUDE.md +/skills diff --git a/app/admin/controller/v1/ProductCategoryRecommend.php b/app/admin/controller/v1/ProductCategoryRecommend.php new file mode 100644 index 00000000..2eae9dd0 --- /dev/null +++ b/app/admin/controller/v1/ProductCategoryRecommend.php @@ -0,0 +1,200 @@ +server(); + $image_host = $server['REQUEST_SCHEME'] . "://" . $server['SERVER_NAME'] . '/'; + $param = request()->get([ + 'keywords', + 'page/d' => 1, + 'size/d' => 10 + ]); + + // 查询数据 + $data = ProductCategoryRecommendModel::with(['category' => function($query) { + $query->field(['id', 'name']); + }]) + ->withoutField(['language_id', 'updated_at', 'deleted_at']) + ->withSearch(['keywords'], ['keywords' => $param['keywords']??null]) + ->language(request()->lang_id) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->paginate([ + 'list_rows' => $param['size'], + 'page' => $param['page'], + ]) + ->bindAttr('category', ['category_name' => 'name']) + ->hidden(['category']) + ?->each(function($item) use($image_host) { + // 拼接完整图片URL + if (!empty($item['image'])) { + $item['image'] = url_join($image_host, $item['image']); + } + }); + + return success('获取成功', $data); + } + + /** + * 导出Excel + */ + public function export() + { + $schema = [ + 'id' => 'ID', + 'image' => '图片', + 'category_name' => '分类名称', + 'desc' => '产品介绍', + 'link' => '链接地址', + 'sort' => '排序', + 'disabled' => '状态', + 'created_at' => '添加时间' + ]; + + // 获取导出数据 + $data = $this->getProductCategoryRecommendData(); + + // 导出 + return xlsx_writer($data, $schema, '产品列表' . date('YmdHis')); + } + // 获取要导出的推荐记录数据 + private function getProductCategoryRecommendData() + { + $server = request()->server(); + $image_host = $server['REQUEST_SCHEME'] . "://" . $server['SERVER_NAME'] . '/'; + $param = request()->get(['keywords']); + + // 查询数据 + return ProductCategoryRecommendModel::with(['category' => function($query) { + $query->field(['id', 'name']); + }]) + ->withoutField(['language_id', 'updated_at', 'deleted_at']) + ->withSearch(['keywords'], ['keywords' => $param['keywords']??null]) + ->language(request()->lang_id) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->select() + ->each(function($item) use($image_host) { + // 拼接完整图片URL + if (!empty($item['image'])) { + $item['image'] = url_join($image_host, $item['image']); + } + // 状态 + $item['disabled'] = $item['disabled'] == 1 ? '禁用' : '启用'; + }) + ->toArray(); + } + + /** + * 获取详细数据 + */ + public function read() + { + $id = request()->param('id/d'); + $record = ProductCategoryRecommendModel::bypk($id) + ->withoutField(['language_id', 'created_at', 'updated_at', 'deleted_at']) + ->find(); + if (empty($record)) { + return error('推荐数据不存在'); + } + + return success('success', $record); + } + + /** + * 新增数据 + */ + public function save() + { + $post = request()->post([ + 'category_id', + 'title', + 'image', + 'desc', + 'link', + 'sort', + 'disabled' + ]); + $data = array_merge($post, ['language_id' => request()->lang_id]); + + // 参数校验 + $validate = new ProductCategoryRecommendValidate(); + if (!$validate->scene('create')->check($data)) { + return error($validate->getError()); + } + + // 保存推荐数据 + $recommend = ProductCategoryRecommendModel::create($data); + if ($recommend->isEmpty()) { + return error('保存失败'); + } + return success('保存成功'); + } + + /** + * 更新数据 + */ + public function update() + { + $id = request()->param('id/d'); + $post = request()->post([ + 'category_id', + 'title', + 'image', + 'desc', + 'link', + 'sort', + 'disabled' + ]); + $data = array_merge($post, ['language_id' => request()->lang_id]); + + // 参数校验 + $validate = new ProductCategoryRecommendValidate(); + $check_data = array_merge($data, ['id' => $id]); + if (!$validate->scene('update')->check($check_data)) { + return error($validate->getError()); + } + + // 更新推荐数据 + $recommend = ProductCategoryRecommendModel::bypk($id)->find(); + if (empty($recommend)) { + return error('请确认操作对象是否存在'); + } + if (!$recommend->save($data)) { + return error('操作失败'); + } + + return success('操作成功'); + } + + /** + * 删除 + */ + public function delete() + { + $id = request()->param('id/d'); + + // 删除推荐记录数据 + $record = ProductCategoryRecommendModel::bypk($id)->find(); + if (empty($record)) { + return error('请确认操作对象是否正确'); + } + if (!$record->delete()) { + return error('操作失败'); + } + + return success('操作成功'); + } +} diff --git a/app/admin/model/v1/ProductCategoryRecommendModel.php b/app/admin/model/v1/ProductCategoryRecommendModel.php new file mode 100644 index 00000000..507681b0 --- /dev/null +++ b/app/admin/model/v1/ProductCategoryRecommendModel.php @@ -0,0 +1,47 @@ +belongsTo(\app\index\model\LanguageModel::class, 'language_id', 'id'); + } + + // 关联产品分类 + public function category() + { + return $this->belongsTo(\app\index\model\ProductCategoryModel::class, 'category_id', 'id'); + } + + // 所属语言范围查询 + public function scopeLanguage($query, $language) + { + $query->where('language_id', '=', $language); + } + + // 关键词搜索 + public function searchKeywordsAttr($query, string|null $keywords) + { + if (is_null($keywords)) { + return; + } + $query->where('title', 'like', "%{$keywords}%") + ->whereOr('desc', 'like', "%{$keywords}%"); + } +} diff --git a/app/admin/route/v1.php b/app/admin/route/v1.php index e2aa35f2..40f261ad 100644 --- a/app/admin/route/v1.php +++ b/app/admin/route/v1.php @@ -87,7 +87,7 @@ Route::group('v1', function () { // 视频分类列表 Route::get('categorys', 'VideoCategory/list'); - + // 视频分类 Route::group('category', function () { // 视频分类分页数据 @@ -311,6 +311,24 @@ Route::group('v1', function () { // 分类删除 Route::delete('delete/:id', 'ProductCategory/delete'); + + // 产品分类推荐数据 + Route::group('recommend', function () { + // 推荐数据分页列表 + Route::get('index', 'ProductCategoryRecommend/index'); + + // 推荐数据详情 + Route::get('read/:id', 'ProductCategoryRecommend/read'); + + // 推荐数据新增 + Route::post('save', 'ProductCategoryRecommend/save'); + + // 推荐数据更新 + Route::put('update/:id', 'ProductCategoryRecommend/update'); + + // 推荐数据删除 + Route::delete('delete/:id', 'ProductCategoryRecommend/delete'); + }); }); // 产品购买链接 @@ -483,7 +501,7 @@ Route::group('v1', function () { // 分页 Route::get('index', 'Navigation/index'); - + // 导航详情 Route::get('read/:id', 'Navigation/read'); diff --git a/app/admin/validate/v1/ProductCategoryRecommendValidate.php b/app/admin/validate/v1/ProductCategoryRecommendValidate.php new file mode 100644 index 00000000..6c0a947b --- /dev/null +++ b/app/admin/validate/v1/ProductCategoryRecommendValidate.php @@ -0,0 +1,63 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = [ + 'language_id' => 'require|integer', + 'category_id' => 'require|integer', + 'title' => 'require|max:255', + 'image' => 'require|max:255', + 'desc' => 'require|max:255', + 'link' => 'max:500', + 'sort' => 'require|integer', + 'disabled' => 'in:0,1' + ]; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = [ + 'id.require' => 'ID不能为空', + 'id.integer' => 'ID必须是整数', + 'language_id.require' => '语言ID不能为空', + 'language_id.integer' => '语言ID必须是整数', + 'category_id.require' => '分类ID不能为空', + 'category_id.integer' => '分类ID必须是整数', + 'title.require' => '标题不能为空', + 'title.max' => '标题长度不能超过:rule个字符', + 'image.require' => '图片不能为空', + 'image.max' => '图片长度不能超过:rule个字符', + 'desc.require' => '描述不能为空', + 'desc.max' => '描述长度不能超过:rule个字符', + 'link.max' => '链接长度不能超过:rule个字符', + 'sort.require' => '排序不能为空', + 'sort.integer' => '排序必须是整数', + 'disabled.in' => '禁用状态只能是0或1', + ]; + + // 新增场景 + protected function sceneCreate() + { + return $this->remove('id', 'require|integer'); + } + + // 更新场景 + protected function sceneUpdate() + { + return $this->append('id', 'require|integer'); + } +} diff --git a/app/admin/validate/v1/ProductValidate.php b/app/admin/validate/v1/ProductValidate.php index 1a1f2d30..0431ba9c 100644 --- a/app/admin/validate/v1/ProductValidate.php +++ b/app/admin/validate/v1/ProductValidate.php @@ -5,8 +5,6 @@ namespace app\admin\validate\v1; use think\Validate; -use function PHPSTORM_META\type; - class ProductValidate extends Validate { /** diff --git a/app/common/model/ProductCategoryRecommendBaseModel.php b/app/common/model/ProductCategoryRecommendBaseModel.php new file mode 100644 index 00000000..13dff1ac --- /dev/null +++ b/app/common/model/ProductCategoryRecommendBaseModel.php @@ -0,0 +1,33 @@ + 'int', + 'language_id' => 'int', + 'category_id' => 'int', + 'title' => 'string', + 'image' => 'string', + 'desc' => 'string', + 'link' => 'string', + 'sort' => 'int', + 'disabled' => 'int', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime' + ]; +} diff --git a/database/migrations/20260326073640_create_product_category_recommend.php b/database/migrations/20260326073640_create_product_category_recommend.php new file mode 100644 index 00000000..a93bb986 --- /dev/null +++ b/database/migrations/20260326073640_create_product_category_recommend.php @@ -0,0 +1,50 @@ +table('product_category_recommend', [ + 'engine' => 'MyISAM', + 'collation' => 'utf8mb4_general_ci', + 'comment' => '产品分类推荐表' + ]); + + $table->addColumn('language_id', 'integer', ['signed' => false, 'null' => false, 'comment' => '语言ID']) + ->addColumn('category_id', 'integer', ['signed' => false, 'null' => false, 'comment' => '分类ID']) + ->addColumn('title', 'string', ['limit' => 255, 'comment' => '标题']) + ->addColumn('image', 'string', ['limit' => 255, 'default' => '', 'comment' => '图片']) + ->addColumn('desc', 'string', ['limit' => 255, 'comment' => '描述']) + ->addColumn('link', 'string', ['limit' => 500, 'default' => '', 'comment' => '外链地址']) + ->addColumn('sort', 'integer', ['default' => 0, 'comment' => '排序']) + ->addColumn('disabled', 'boolean', ['default' => 0, 'comment' => '是否禁用 0:启用 1:禁用']) + ->addColumn('created_at', 'timestamp', ["null" => false,'default' => 'CURRENT_TIMESTAMP', 'comment' => '创建时间']) + ->addColumn('updated_at', 'timestamp', ["null" => false,'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', 'comment' => '更新时间']) + ->addColumn('deleted_at', 'timestamp', ['null' => true, 'comment' => '删除时间']) + ->create(); + } +} From 863fdda9a56edd02b391e9d5b6bbb761081bd6a3 Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Fri, 27 Mar 2026 16:31:29 +0800 Subject: [PATCH 02/58] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E6=8E=A5=E5=8F=A3=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/v1/ProductCategoryRecommend.php | 2 +- app/admin/route/v1.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/admin/controller/v1/ProductCategoryRecommend.php b/app/admin/controller/v1/ProductCategoryRecommend.php index 2eae9dd0..bd62f86f 100644 --- a/app/admin/controller/v1/ProductCategoryRecommend.php +++ b/app/admin/controller/v1/ProductCategoryRecommend.php @@ -68,7 +68,7 @@ class ProductCategoryRecommend $data = $this->getProductCategoryRecommendData(); // 导出 - return xlsx_writer($data, $schema, '产品列表' . date('YmdHis')); + return xlsx_writer($data, $schema, '产品推荐列表' . date('YmdHis')); } // 获取要导出的推荐记录数据 private function getProductCategoryRecommendData() diff --git a/app/admin/route/v1.php b/app/admin/route/v1.php index 40f261ad..bae867d5 100644 --- a/app/admin/route/v1.php +++ b/app/admin/route/v1.php @@ -317,6 +317,9 @@ Route::group('v1', function () { // 推荐数据分页列表 Route::get('index', 'ProductCategoryRecommend/index'); + // 推荐数据导出 + Route::get('export', 'ProductCategoryRecommend/export'); + // 推荐数据详情 Route::get('read/:id', 'ProductCategoryRecommend/read'); From be26f2d75b4514bbc6c3caaf94c57d17513636ac Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Fri, 27 Mar 2026 17:43:41 +0800 Subject: [PATCH 03/58] =?UTF-8?q?fix:=20=E4=BA=A7=E5=93=81=E6=8E=A8?= =?UTF-8?q?=E8=8D=90=E5=88=97=E8=A1=A8=E5=8F=8A=E5=AF=BC=E5=87=BA=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v1/ProductCategoryRecommend.php | 21 ++++++++++++------- .../v1/ProductCategoryRecommendModel.php | 6 +++--- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/admin/controller/v1/ProductCategoryRecommend.php b/app/admin/controller/v1/ProductCategoryRecommend.php index bd62f86f..7d2d42ae 100644 --- a/app/admin/controller/v1/ProductCategoryRecommend.php +++ b/app/admin/controller/v1/ProductCategoryRecommend.php @@ -20,15 +20,17 @@ class ProductCategoryRecommend $image_host = $server['REQUEST_SCHEME'] . "://" . $server['SERVER_NAME'] . '/'; $param = request()->get([ 'keywords', + 'category_name', 'page/d' => 1, 'size/d' => 10 ]); // 查询数据 - $data = ProductCategoryRecommendModel::with(['category' => function($query) { - $query->field(['id', 'name']); + $data = ProductCategoryRecommendModel::withJoin(['category' => function($query) use ($param) { + if (!empty($param['category_name'])) { + $query->where('category.name', 'like', '%' . $param['category_name'] . '%'); + } }]) - ->withoutField(['language_id', 'updated_at', 'deleted_at']) ->withSearch(['keywords'], ['keywords' => $param['keywords']??null]) ->language(request()->lang_id) ->order(['sort' => 'asc', 'id' => 'desc']) @@ -37,7 +39,7 @@ class ProductCategoryRecommend 'page' => $param['page'], ]) ->bindAttr('category', ['category_name' => 'name']) - ->hidden(['category']) + ->hidden(['category', 'language_id', 'updated_at', 'deleted_at']) ?->each(function($item) use($image_host) { // 拼接完整图片URL if (!empty($item['image'])) { @@ -78,15 +80,18 @@ class ProductCategoryRecommend $param = request()->get(['keywords']); // 查询数据 - return ProductCategoryRecommendModel::with(['category' => function($query) { - $query->field(['id', 'name']); + return ProductCategoryRecommendModel::withJoin(['category' => function($query) use ($param) { + if (!empty($param['category_name'])) { + $query->where('category.name', 'like', '%' . $param['category_name'] . '%'); + } }]) - ->withoutField(['language_id', 'updated_at', 'deleted_at']) ->withSearch(['keywords'], ['keywords' => $param['keywords']??null]) ->language(request()->lang_id) ->order(['sort' => 'asc', 'id' => 'desc']) ->select() - ->each(function($item) use($image_host) { + ->bindAttr('category', ['category_name' => 'name']) + ->hidden(['category', 'language_id', 'updated_at', 'deleted_at']) + ?->each(function($item) use($image_host) { // 拼接完整图片URL if (!empty($item['image'])) { $item['image'] = url_join($image_host, $item['image']); diff --git a/app/admin/model/v1/ProductCategoryRecommendModel.php b/app/admin/model/v1/ProductCategoryRecommendModel.php index 507681b0..317bb968 100644 --- a/app/admin/model/v1/ProductCategoryRecommendModel.php +++ b/app/admin/model/v1/ProductCategoryRecommendModel.php @@ -32,7 +32,7 @@ class ProductCategoryRecommendModel extends ProductCategoryRecommendBaseModel // 所属语言范围查询 public function scopeLanguage($query, $language) { - $query->where('language_id', '=', $language); + $query->where($this->getTable() . '.language_id', '=', $language); } // 关键词搜索 @@ -41,7 +41,7 @@ class ProductCategoryRecommendModel extends ProductCategoryRecommendBaseModel if (is_null($keywords)) { return; } - $query->where('title', 'like', "%{$keywords}%") - ->whereOr('desc', 'like', "%{$keywords}%"); + $query->where($this->getTable() . '.title', 'like', "%{$keywords}%") + ->whereOr($this->getTable() . '.desc', 'like', "%{$keywords}%"); } } From 8577877f83eaba12b6553eb49b07dc49dd887ee4 Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Fri, 27 Mar 2026 17:50:25 +0800 Subject: [PATCH 04/58] =?UTF-8?q?feat:=20banner=E5=88=86=E7=B1=BB=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0unique=5Flabel=E5=AD=97=E6=AE=B5=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/v1/Banner.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/admin/controller/v1/Banner.php b/app/admin/controller/v1/Banner.php index a28a041f..52ac5edc 100644 --- a/app/admin/controller/v1/Banner.php +++ b/app/admin/controller/v1/Banner.php @@ -91,7 +91,6 @@ class Banner $banner = SysBannerModel::withoutField([ 'at_page', - 'unique_label', 'language_id', 'created_at', 'updated_at', @@ -142,10 +141,11 @@ class Banner 'name', 'desc', 'recommend', + 'unique_label', 'at_platform' => 'pc', 'status' => 1 ]); - + $validate = new SysBannerValidate; if (!$validate->scene('edit')->check(array_merge($put, ['id' => $id]))) { return error($validate->getError()); From 9584b817298ff84205ee27610b221ea4608e9e0c Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Fri, 27 Mar 2026 17:59:08 +0800 Subject: [PATCH 05/58] refactor: banner|navigation --- app/admin/controller/v1/NavigationItem.php | 8 +++++++- .../validate/v1/NavigationItemValidate.php | 18 +++++++++++------- .../model/SysNavigationItemBaseModel.php | 2 ++ ...241230093811_create_sys_navigation_item.php | 2 ++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/admin/controller/v1/NavigationItem.php b/app/admin/controller/v1/NavigationItem.php index 3875d7f7..fa63be15 100644 --- a/app/admin/controller/v1/NavigationItem.php +++ b/app/admin/controller/v1/NavigationItem.php @@ -67,6 +67,8 @@ class NavigationItem 'id', 'pid', 'name', + 'desc', + 'image', 'nav_id', 'sort', 'status', @@ -93,7 +95,9 @@ class NavigationItem 'pid', 'nav_id', 'name', + 'desc', 'icon', + 'image', 'link_to' => 'custom', 'link', 'sort', @@ -121,7 +125,9 @@ class NavigationItem 'pid', 'nav_id', 'name', + 'desc', 'icon', + 'image', 'link_to', 'link', 'sort', @@ -172,7 +178,7 @@ class NavigationItem if (empty($nav)) { return error('请确认要操作对象是否存在'); } - + if (!$nav->delete()) { return error('操作失败'); } diff --git a/app/admin/validate/v1/NavigationItemValidate.php b/app/admin/validate/v1/NavigationItemValidate.php index ee3e7c7e..103f2fa3 100644 --- a/app/admin/validate/v1/NavigationItemValidate.php +++ b/app/admin/validate/v1/NavigationItemValidate.php @@ -20,7 +20,9 @@ class NavigationItemValidate extends Validate 'nav_id' => 'require|integer', 'pid' => 'integer|different:id|checkPidNotBeChildren', 'name' => 'require|max:64', + 'desc' => 'max:255', 'icon' => 'max:64', + 'image' => 'max:255', 'link_to' => 'require|max:64|in:article,article_category,product,product_category,system_page,custom', 'link' => 'max:255', 'sort' => 'integer', @@ -44,7 +46,9 @@ class NavigationItemValidate extends Validate 'pid.checkPidNotBeChildren' => '父级ID不能为自身的子导航', 'name.require' => '导航名称不能为空', 'name.max' => '导航名称最多不能超过64个字符', + 'desc.max' => '导航名称最多不能超过:rule个字符', 'icon.max' => '图标最多不能超过64个字符', + 'image.max' => '图标最多不能超过:rule个字符', 'link_to.require' => '链接类型不能为空', 'link_to.max' => '链接类型最多不能超过64个字符', 'link_to.in' => '链接类型必须是article,article_category,product_category,product,system_page,custom中之一', @@ -67,7 +71,7 @@ class NavigationItemValidate extends Validate if (env('DB_VERSION', '5') == '8') { $children = Db::query( preg_replace( - '/\s+/u', + '/\s+/u', ' ', "WITH RECURSIVE tree_by AS ( SELECT a.id, a.pid FROM $table_name a WHERE a.id = {$data['id']} @@ -80,13 +84,13 @@ class NavigationItemValidate extends Validate } else { $children = \think\facade\Db::query(" SELECT t2.id - FROM ( - SELECT + FROM ( + SELECT @r AS _id, (SELECT @r := GROUP_CONCAT(id) FROM $table_name WHERE FIND_IN_SET(pid, _id)) AS parent_id - FROM - (SELECT @r := {$data['id']}) vars, $table_name h - WHERE @r <> 0) t1 - JOIN $table_name t2 + FROM + (SELECT @r := {$data['id']}) vars, $table_name h + WHERE @r <> 0) t1 + JOIN $table_name t2 ON FIND_IN_SET(t2.pid, t1._id) ORDER BY t2.id; "); diff --git a/app/common/model/SysNavigationItemBaseModel.php b/app/common/model/SysNavigationItemBaseModel.php index db3e7597..824604c6 100644 --- a/app/common/model/SysNavigationItemBaseModel.php +++ b/app/common/model/SysNavigationItemBaseModel.php @@ -21,7 +21,9 @@ class SysNavigationItemBaseModel extends BaseModel 'nav_id' => 'int', 'pid' => 'int', 'name' => 'string', + 'desc' => 'string', 'icon' => 'string', + 'image' => 'string', 'link_to' => 'string', 'link' => 'string', 'sort' => 'int', diff --git a/database/migrations/20241230093811_create_sys_navigation_item.php b/database/migrations/20241230093811_create_sys_navigation_item.php index 81c193f3..00a8124c 100644 --- a/database/migrations/20241230093811_create_sys_navigation_item.php +++ b/database/migrations/20241230093811_create_sys_navigation_item.php @@ -31,7 +31,9 @@ class CreateSysNavigationItem extends Migrator $table->addColumn('nav_id', 'string', ['limit' => 64, 'null' => false, 'comment' => '所属导航ID']) ->addColumn('pid', 'integer', ['null' => false, 'default' => 0, 'comment' => '父级ID']) ->addColumn('name', 'string', ['limit' => 64, 'null' => false, 'comment' => '名称']) + ->addColumn('desc', 'string', ['limit' => 255, 'null' => true, 'default' => null, 'comment' => '描述']) ->addColumn('icon', 'string', ['limit' => 64, 'null' => true, 'default' => null, 'comment' => '图标']) + ->addColumn('image', 'string', ['limit' => 255, 'null' => true, 'default' => null, 'comment' => '图片']) ->addColumn('link_to', 'string', ['limit' => 64, 'null' => true, 'default' => null, 'comment' => '链接到(类型): article:文章, article_category:文章分类, product:产品, product_category:产品分类, custom:自定义链接']) ->addColumn('link', 'string', ['limit' => 255, 'null' => true, 'default' => null, 'comment' => '链接']) ->addColumn('sort', 'integer', ['limit' => 11, 'null' => false, 'default' => 0, 'comment' => '排序']) From ebe1c3015ef8ced5fa50055b2e8df653608b4d5b Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Sat, 28 Mar 2026 09:13:48 +0800 Subject: [PATCH 06/58] =?UTF-8?q?fix:=20=E5=8E=BB=E9=99=A4banner=E4=B8=8D?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E6=9B=B4=E6=96=B0unique=5Flabel=E9=99=90?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/validate/v1/SysBannerValidate.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/admin/validate/v1/SysBannerValidate.php b/app/admin/validate/v1/SysBannerValidate.php index 9af8c9a8..584134a4 100644 --- a/app/admin/validate/v1/SysBannerValidate.php +++ b/app/admin/validate/v1/SysBannerValidate.php @@ -43,7 +43,6 @@ class SysBannerValidate extends BaseValidate 'at_platform.in' => '显示端口只能是pc或mobile', 'at_page.max' => '页面位置最多255个字符', 'unique_label.max' => '唯一标识最多64个字符', - 'unique_label.mustOmit' => '更新时不能有unique_label字段', 'name.require' => '名称不能为空', 'name.max' => '名称最多64个字符', 'desc.max' => '描述最多255个字符', @@ -61,6 +60,6 @@ class SysBannerValidate extends BaseValidate // 编辑场景 public function sceneEdit() { - return $this->remove('language_id', 'require|integer')->append('unique_label', 'mustOmit'); + return $this->remove('language_id', 'require|integer'); } } From 4c592d934704a1a7721471a26f801b1c9de56de4 Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Sat, 28 Mar 2026 11:04:10 +0800 Subject: [PATCH 07/58] =?UTF-8?q?refactor:=20=E4=BA=A7=E5=93=81=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E6=8E=A8=E8=8D=90=E5=88=97=E8=A1=A8=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=BC=A9=E7=95=A5=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/v1/ProductCategoryRecommend.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/admin/controller/v1/ProductCategoryRecommend.php b/app/admin/controller/v1/ProductCategoryRecommend.php index 7d2d42ae..3a1581de 100644 --- a/app/admin/controller/v1/ProductCategoryRecommend.php +++ b/app/admin/controller/v1/ProductCategoryRecommend.php @@ -16,8 +16,6 @@ class ProductCategoryRecommend */ public function index() { - $server = request()->server(); - $image_host = $server['REQUEST_SCHEME'] . "://" . $server['SERVER_NAME'] . '/'; $param = request()->get([ 'keywords', 'category_name', @@ -40,10 +38,10 @@ class ProductCategoryRecommend ]) ->bindAttr('category', ['category_name' => 'name']) ->hidden(['category', 'language_id', 'updated_at', 'deleted_at']) - ?->each(function($item) use($image_host) { - // 拼接完整图片URL + ?->each(function($item) { + // 列表页面图片输出缩略图 if (!empty($item['image'])) { - $item['image'] = url_join($image_host, $item['image']); + $item['image'] = thumb($item['image']); } }); From 90b4bccbcf2f8441e5ad210c43901ffc6ecc072f Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Sat, 28 Mar 2026 11:37:26 +0800 Subject: [PATCH 08/58] =?UTF-8?q?feat:=20=E7=B3=BB=E7=BB=9F=E5=95=86?= =?UTF-8?q?=E5=9F=8E=E5=BA=97=E9=93=BA=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/v1/SysMallStoreEntrance.php | 201 ++++++++++++++++++ .../model/v1/SysMallStoreEntranceModel.php | 61 ++++++ app/admin/route/v1.php | 23 ++ .../v1/SysMallStoreEntranceValidate.php | 60 ++++++ .../model/SysMallStoreEntranceBaseModel.php | 32 +++ ...20260328021117_sys_mall_store_entrance.php | 50 +++++ 6 files changed, 427 insertions(+) create mode 100644 app/admin/controller/v1/SysMallStoreEntrance.php create mode 100644 app/admin/model/v1/SysMallStoreEntranceModel.php create mode 100644 app/admin/validate/v1/SysMallStoreEntranceValidate.php create mode 100644 app/common/model/SysMallStoreEntranceBaseModel.php create mode 100644 database/migrations/20260328021117_sys_mall_store_entrance.php diff --git a/app/admin/controller/v1/SysMallStoreEntrance.php b/app/admin/controller/v1/SysMallStoreEntrance.php new file mode 100644 index 00000000..94f38910 --- /dev/null +++ b/app/admin/controller/v1/SysMallStoreEntrance.php @@ -0,0 +1,201 @@ +get([ + 'name', + 'page/d' => 1, + 'size/d' => 10 + ]); + + // 查询数据 + $data = SysMallStoreEntranceModel::withoutField([ + 'language_id', + 'hover_image', + 'updated_at', + 'deleted_at' + ]) + ->withSearch(['name'], ['name' => $param['name']??null]) + ->language(request()->lang_id) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->paginate([ + 'list_rows' => $param['size'], + 'page' => $param['page'], + ]) + ?->each(function($item) { + // 列表页面图片输出缩略图 + if (!empty($item['image'])) { + $item['image'] = thumb($item['image']); + } + }); + + return success('获取成功', $data); + } + + /** + * 获取详情 + */ + public function read() + { + $id = request()->param('id/d'); + $record = SysMallStoreEntranceModel::bypk($id) + ->withoutField(['language_id', 'created_at', 'updated_at', 'deleted_at']) + ->find(); + if (empty($record)) { + return error('商城店铺入口数据不存在'); + } + + return success('success', $record); + } + + /** + * 新增数据 + */ + public function save() + { + $post = request()->post([ + 'name', + 'image', + 'hover_image', + 'link', + 'sort', + 'disabled' + ]); + $data = array_merge($post, ['language_id' => request()->lang_id]); + + // 参数校验 + $validate = new SysMallStoreEntranceValidate(); + if (!$validate->scene('create')->check($data)) { + return error($validate->getError()); + } + + // 保存数据 + $entrance = SysMallStoreEntranceModel::create($data); + if ($entrance->isEmpty()) { + return error('保存失败'); + } + return success('保存成功'); + } + + /** + * 更新数据 + */ + public function update() + { + $id = request()->param('id/d'); + $post = request()->post([ + 'name', + 'image', + 'hover_image', + 'link', + 'sort', + 'disabled' + ]); + $data = array_merge($post, ['language_id' => request()->lang_id]); + + // 参数校验 + $validate = new SysMallStoreEntranceValidate(); + $check_data = array_merge($data, ['id' => $id]); + if (!$validate->scene('update')->check($check_data)) { + return error($validate->getError()); + } + + // 更新数据 + $entrance = SysMallStoreEntranceModel::bypk($id)->find(); + if (empty($entrance)) { + return error('请确认操作对象是否存在'); + } + if (!$entrance->save($data)) { + return error('操作失败'); + } + + return success('操作成功'); + } + + /** + * 删除 + */ + public function delete() + { + $id = request()->param('id/d'); + + // 删除数据 + $record = SysMallStoreEntranceModel::bypk($id)->find(); + if (empty($record)) { + return error('请确认操作对象是否正确'); + } + if (!$record->delete()) { + return error('操作失败'); + } + + return success('操作成功'); + } + + /** + * 导出Excel + */ + public function export() + { + $schema = [ + 'id' => 'ID', + 'image' => '图片', + 'hover_image' => '悬浮图', + 'name' => '名称', + 'link' => '链接地址', + 'sort' => '排序', + 'disabled' => '状态', + 'created_at' => '添加时间' + ]; + + // 获取导出数据 + $data = $this->getExportData(); + + // 导出 + return xlsx_writer($data, $schema, '系统商城店铺入口列表' . date('YmdHis')); + } + + // 获取要导出的数据 + private function getExportData() + { + $server = request()->server(); + $image_host = $server['REQUEST_SCHEME'] . "://" . $server['SERVER_NAME'] . '/'; + $param = request()->get(['name']); + + // 查询数据 + return SysMallStoreEntranceModel::withoutField([ + 'language_id', + 'updated_at', + 'deleted_at' + ]) + ->withSearch(['name'], ['name' => $param['name']??null]) + ->language(request()->lang_id) + ->order(['sort' => 'asc', 'id' => 'desc']) + ->select() + ?->each(function($item) use($image_host) { + // 拼接完整图片URL + if (!empty($item['image'])) { + $item['image'] = url_join($image_host, $item['image']); + } + if (!empty($item['hover_image'])) { + $item['hover_image'] = url_join($image_host, $item['hover_image']); + } + // 状态转换 + $item['disabled'] = $item['disabled'] == 1 ? '禁用' : '启用'; + }) + ->toArray(); + } +} diff --git a/app/admin/model/v1/SysMallStoreEntranceModel.php b/app/admin/model/v1/SysMallStoreEntranceModel.php new file mode 100644 index 00000000..f1cd1a81 --- /dev/null +++ b/app/admin/model/v1/SysMallStoreEntranceModel.php @@ -0,0 +1,61 @@ +belongsTo(\app\index\model\LanguageModel::class, 'language_id', 'id'); + } + + // 所属语言范围查询 + public function scopeLanguage($query, $language) + { + $query->where($this->getTable() . '.language_id', '=', $language); + } + + // 查询启用状态 + public function scopeEnabled($query) + { + $query->where('disabled', '=', 0); + } + + // 查询禁用状态 + public function scopeDisabled($query) + { + $query->where('disabled', '=', 1); + } + + // 按名称搜索 + public function searchNameAttr($query, $value, $data) + { + if (is_null($value)) { + return; + } + $query->where('name', 'like', "%{$value}%"); + } + + // 按链接地址搜索 + public function searchLinkAttr($query, $value, $data) + { + if (is_null($value)) { + return; + } + $query->where('link_url', 'like', "%{$value}%"); + } +} diff --git a/app/admin/route/v1.php b/app/admin/route/v1.php index bae867d5..b8e56853 100644 --- a/app/admin/route/v1.php +++ b/app/admin/route/v1.php @@ -595,6 +595,29 @@ Route::group('v1', function () { // 反馈管理 - 产品询盘列表 Route::get('product/inquiry/index', 'ProductInquiry/index'); + // 系统商城店铺入口 + Route::group('mall', function() { + Route::group('store', function() { + // 店铺入口列表分页 + Route::get('index', 'SysMallStoreEntrance/index'); + + // 店铺入口导出 + Route::get('export', 'SysMallStoreEntrance/export'); + + // 店铺入口详情 + Route::get('read/:id', 'SysMallStoreEntrance/read'); + + // 店铺入口新增 + Route::post('save', 'SysMallStoreEntrance/save'); + + // 店铺入口更新 + Route::put('update/:id', 'SysMallStoreEntrance/update'); + + // 店铺入口删除 + Route::delete('delete/:id', 'SysMallStoreEntrance/delete'); + }); + }); + // 配置项列表 Route::group('config', function() { // 配置分组 diff --git a/app/admin/validate/v1/SysMallStoreEntranceValidate.php b/app/admin/validate/v1/SysMallStoreEntranceValidate.php new file mode 100644 index 00000000..31d8b33d --- /dev/null +++ b/app/admin/validate/v1/SysMallStoreEntranceValidate.php @@ -0,0 +1,60 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = [ + 'id' => 'require|integer', + 'language_id' => 'require|integer', + 'name' => 'require|max:255', + 'image' => 'require|max:255', + 'hover_image' => 'max:255', + 'link' => 'max:500', + 'sort' => 'require|integer', + 'disabled' => 'in:0,1', + ]; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = [ + 'id.require' => 'ID不能为空', + 'id.integer' => 'ID必须是整数', + 'language_id.require' => '语言ID不能为空', + 'language_id.integer' => '语言ID必须是整数', + 'name.require' => '商城名称不能为空', + 'name.max' => '商城名称长度不能超过:rule个字符', + 'image.require' => '图片不能为空', + 'image.max' => '图片长度不能超过:rule个字符', + 'hover_image.max' => '悬浮图长度不能超过:rule个字符', + 'link.max' => '链接地址长度不能超过:rule个字符', + 'sort.require' => '排序不能为空', + 'sort.integer' => '排序必须是整数', + 'disabled.in' => '禁用状态只能是0或1', + ]; + + // 新增场景 + protected function sceneCreate() + { + return $this->remove('id', 'require|integer'); + } + + // 更新场景 + protected function sceneUpdate() + { + return $this->append('id', 'require|integer'); + } +} diff --git a/app/common/model/SysMallStoreEntranceBaseModel.php b/app/common/model/SysMallStoreEntranceBaseModel.php new file mode 100644 index 00000000..7e4115c2 --- /dev/null +++ b/app/common/model/SysMallStoreEntranceBaseModel.php @@ -0,0 +1,32 @@ + 'int', + 'language_id' => 'int', + 'name' => 'string', + 'image' => 'string', + 'hover_image' => 'string', + 'link' => 'string', + 'sort' => 'int', + 'disabled' => 'int', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; +} diff --git a/database/migrations/20260328021117_sys_mall_store_entrance.php b/database/migrations/20260328021117_sys_mall_store_entrance.php new file mode 100644 index 00000000..4180738c --- /dev/null +++ b/database/migrations/20260328021117_sys_mall_store_entrance.php @@ -0,0 +1,50 @@ +table('sys_mall_store_entrance', [ + 'engine' => 'MyISAM', + 'collation' => 'utf8mb4_general_ci', + 'comment' => '系统商城店铺入口表' + ]); + + $table->addColumn('language_id', 'integer', ['signed' => false, 'null' => false, 'comment' => '语言ID']) + ->addColumn('name', 'string', ['limit' => 255, 'null' => false, 'comment' => '商城名称']) + ->addColumn('image', 'string', ['limit' => 255, 'default' => '', 'comment' => '图片']) + ->addColumn('hover_image', 'string', ['limit' => 255, 'default' => '', 'comment' => '悬浮图']) + ->addColumn('link', 'string', ['limit' => 500, 'default' => '', 'comment' => '链接地址']) + ->addColumn('sort', 'integer', ['default' => 0, 'comment' => '排序']) + ->addColumn('disabled', 'boolean', ['default' => 0, 'comment' => '是否禁用 0:启用 1:禁用']) + ->addColumn('created_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP', 'comment' => '创建时间']) + ->addColumn('updated_at', 'timestamp', ['null' => false, 'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', 'comment' => '更新时间']) + ->addColumn('deleted_at', 'timestamp', ['null' => true, 'comment' => '删除时间']) + ->addIndex(['language_id'], ['name' => 'idx_language_id']) + ->create(); + } +} From 8a9d66f5d3f0945b58ccbd02d4016a13d98afdda Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Sat, 28 Mar 2026 11:46:23 +0800 Subject: [PATCH 09/58] =?UTF-8?q?fix:=20=E4=BA=A7=E5=93=81=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E6=8E=A5=E5=8F=A3=E6=9C=AA=E6=89=BE=E5=88=B0=E4=BA=A7?= =?UTF-8?q?=E5=93=81=E6=97=B6=E6=8A=A5=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/v1/Product.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/admin/controller/v1/Product.php b/app/admin/controller/v1/Product.php index 3b635255..5914a8ed 100644 --- a/app/admin/controller/v1/Product.php +++ b/app/admin/controller/v1/Product.php @@ -24,7 +24,7 @@ class Product 'category_id', 'created_at', 'is_show', - 'page/d' => 1, + 'page/d' => 1, 'size/d' => 10 ]); @@ -83,7 +83,7 @@ class Product ]) ->bypk(request()->param('id')) ->find() - ->bindAttr('category', ['category_name']) + ?->bindAttr('category', ['category_name']) ->hidden(['category']); if (empty($product)) { return error('产品不存在'); @@ -108,7 +108,7 @@ class Product // 获取关联产品 $product->related = ProductRelatedModel::field([ - 'related_product_id', + 'related_product_id', 'sort' ]) ->with(['product' => function($query) { @@ -149,8 +149,8 @@ class Product 'seo_desc' ]); $put = array_merge( - $put, - ['skus' => json_decode($put['skus'], true)], + $put, + ['skus' => json_decode($put['skus'], true)], ['related' => json_decode($put['related'], true)], ); @@ -305,7 +305,7 @@ class Product return success('操作成功'); } - + // 导出 public function export() { @@ -444,7 +444,7 @@ class Product }); } } - + return $products->toArray(); } } From 29abb5e23032cc1a703b5e6ef2fad34d9da6dd3e Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Mon, 30 Mar 2026 18:03:03 +0800 Subject: [PATCH 10/58] =?UTF-8?q?refactor:=20pc=E5=AF=BC=E8=88=AA=E9=87=8D?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/index/controller/Common.php | 59 +- app/index/lang/en-us/pc.php | 9 +- app/index/model/ProductCategoryModel.php | 6 + .../model/ProductCategoryRecommendModel.php | 25 + app/index/model/SysMallStoreEntranceModel.php | 43 + app/index/view/pc/public/20230331.header.html | 287 +++ app/index/view/pc/public/header.html | 1618 ++++++++++++++--- 7 files changed, 1763 insertions(+), 284 deletions(-) create mode 100644 app/index/model/ProductCategoryRecommendModel.php create mode 100644 app/index/model/SysMallStoreEntranceModel.php create mode 100644 app/index/view/pc/public/20230331.header.html diff --git a/app/index/controller/Common.php b/app/index/controller/Common.php index 703890c8..a4de17b2 100644 --- a/app/index/controller/Common.php +++ b/app/index/controller/Common.php @@ -8,6 +8,7 @@ use app\index\model\LanguageModel; use app\index\model\ProductCategoryModel; use app\index\model\ProductModel; use app\index\model\SysConfigModel; +use app\index\model\SysMallStoreEntranceModel; use app\index\model\SysNavigationItemModel; use think\facade\Lang; use think\facade\View; @@ -38,7 +39,7 @@ abstract class Common extends BaseController } // 获取产品分类 - $categorys = $this->getProductCategory($this->lang_id); + $categorys = $this->getProductCategory($this->lang_id, true); // 输出产品分类 View::assign('header_categorys', $categorys); @@ -47,6 +48,9 @@ abstract class Common extends BaseController // 输出热销产品 View::assign('header_hot_products', $hot_products); + // 获取商品购买入口 + View::assign("header_mall_entrance", $this->getMallStoreEntrance($this->lang_id)); + // 输出顶部导航 View::assign('header_navigation', $this->getNavigation('NAV_67f3701f3e831', $this->lang_id)); @@ -78,7 +82,7 @@ abstract class Common extends BaseController } // 获取产品分类 - protected function getProductCategory($language = 1) + protected function getProductCategory($language = 1, $with_recommends = false) { $categorys = ProductCategoryModel::field([ 'id', @@ -87,15 +91,42 @@ abstract class Common extends BaseController 'icon', 'level' ]) + ->when($with_recommends, function($query) { + $query->with(['recommends' => function($query) { + $query->field(['id', 'category_id', 'title', 'image', 'desc', 'link']) + ->order(['sort' => 'asc', 'id' => 'desc']); + }]); + }) ->language($language) ->displayed() ->order(['sort' => 'asc', 'id' => 'desc']) - ->select(); + ->select() + ->hidden(["recommends.category_id"]); if ($categorys->isEmpty()) { return []; } - return array_to_tree($categorys->toArray(), 0, 'pid', 1, false); + return $this->toTreeAndChunk($categorys->toArray(), 0, 1); + } + private function toTreeAndChunk(array $categorys, int $pid, int|bool $level): array + { + $ret = []; + foreach ($categorys as $item) { + if ($item['pid'] == $pid) { + $lv = $level; + if ($level !== false) { + $item['level'] = $level; + $lv = $level + 1; + } + $children = $this->toTreeAndChunk($categorys, $item['id'], $lv); + if (!empty($children)) { + $item['children'] = $item['level'] == 1 ? array_chunk($children, 2) : $children; + } + $ret[] = $item; + } + } + + return $ret; } // 获取顶部导航 @@ -164,6 +195,26 @@ abstract class Common extends BaseController return $languages; } + // 获取商品购买入口 + private function getMallStoreEntrance($language = 1) + { + return SysMallStoreEntranceModel::field([ + 'id', + 'name', + 'image', + 'hover_image', + 'link' + ]) + ->language($language) + ->enabled() + ->order(['sort' => 'asc', 'id' => 'desc']) + ->select() + ?->each(function($item) { + $item->image = thumb($item->image); + return $item; + }); + } + // 获取系统联系方式配置 protected function getSysConfig($language, $group = []) { diff --git a/app/index/lang/en-us/pc.php b/app/index/lang/en-us/pc.php index 4aa02b3f..73452f7a 100644 --- a/app/index/lang/en-us/pc.php +++ b/app/index/lang/en-us/pc.php @@ -5,10 +5,17 @@ return [ '产品列表' => 'Products', '店铺' => 'Store', '搜索记录' => 'Search History', - '热销产品' => 'Popular Products', '产品' => 'Product', '联系我们' => 'Contact', + // 新导航栏 - 2023-03-31 + '搜索' => 'Search', + '搜索产品、分类...' => 'Search products and categories...', + '最近搜索' => 'Recent Searches', + '清空' => 'Clear', + '热销产品' => 'Popular Products', + '购买' => 'Buy', + // 返回文本 '提交成功' => 'success', '提交失败' => 'fail', diff --git a/app/index/model/ProductCategoryModel.php b/app/index/model/ProductCategoryModel.php index a6ea7e5e..7846820a 100644 --- a/app/index/model/ProductCategoryModel.php +++ b/app/index/model/ProductCategoryModel.php @@ -17,6 +17,12 @@ class ProductCategoryModel extends ProductCategoryBaseModel // 软件删除时间字段 protected $deleteTime = 'deleted_at'; + // 关联产品推荐 + public function recommends() + { + return $this->hasMany(ProductCategoryRecommendModel::class, 'category_id', 'id'); + } + // 所属语言范围查询 public function scopeLanguage($query, $language) { diff --git a/app/index/model/ProductCategoryRecommendModel.php b/app/index/model/ProductCategoryRecommendModel.php new file mode 100644 index 00000000..122e4dc0 --- /dev/null +++ b/app/index/model/ProductCategoryRecommendModel.php @@ -0,0 +1,25 @@ +where($this->getTable() . '.language_id', '=', $language); + } +} diff --git a/app/index/model/SysMallStoreEntranceModel.php b/app/index/model/SysMallStoreEntranceModel.php new file mode 100644 index 00000000..df07e042 --- /dev/null +++ b/app/index/model/SysMallStoreEntranceModel.php @@ -0,0 +1,43 @@ +belongsTo(\app\index\model\LanguageModel::class, 'language_id', 'id'); + } + + // 所属语言范围查询 + public function scopeLanguage($query, $language) + { + $query->where($this->getTable() . '.language_id', '=', $language); + } + + // 查询启用状态 + public function scopeEnabled($query) + { + $query->where('disabled', '=', 0); + } + + // 查询禁用状态 + public function scopeDisabled($query) + { + $query->where('disabled', '=', 1); + } +} diff --git a/app/index/view/pc/public/20230331.header.html b/app/index/view/pc/public/20230331.header.html new file mode 100644 index 00000000..b29fb394 --- /dev/null +++ b/app/index/view/pc/public/20230331.header.html @@ -0,0 +1,287 @@ +
+ + + +
+
+ × + + +
+

{:lang_i18n('搜索记录')}

+
    +
    +
    +

    {:lang_i18n('热销产品')}

    +
    + {volist name="header_hot_products" id="vo"} +
    + +
    {$vo.name}
    +
    {$vo.short_name}
    +
    + {/volist} +
    +
    +
    +
    +
    + + diff --git a/app/index/view/pc/public/header.html b/app/index/view/pc/public/header.html index b29fb394..f4d5c5a3 100644 --- a/app/index/view/pc/public/header.html +++ b/app/index/view/pc/public/header.html @@ -1,287 +1,1347 @@ -
    - + + + + - From 23f9a8fc9a56d25d4dc43f971ced1f0844a4b4cc Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Tue, 31 Mar 2026 17:30:22 +0800 Subject: [PATCH 11/58] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AF=BC=E8=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/index/view/pc/public/header.html | 517 ++++++++++++---------- app/index/view/pc/topic_laptop/index.html | 2 +- 2 files changed, 287 insertions(+), 232 deletions(-) diff --git a/app/index/view/pc/public/header.html b/app/index/view/pc/public/header.html index f4d5c5a3..32ea3353 100644 --- a/app/index/view/pc/public/header.html +++ b/app/index/view/pc/public/header.html @@ -8,40 +8,48 @@ body { overflow-x: hidden; - font-size: 16px; + } a { text-decoration: none; } - + .header { + position: fixed; + top:0; + z-index: 999; + width:100%; + background: #fff; + font-size: 16px; + } /* 导航外容器:1920px宽,居中 */ - .nav-outer { + .header-nav-outer { max-width: 1920px; position: relative; padding: 0 12.5em; overflow: visible; + background: #fff; } /* 顶部导航栏:内容区带左右边距,外框1920px */ - .nav-bar { + .header-nav-bar { width: 100%; max-width: 1520px; - height: 3.75em; + height: 4em; display: flex; align-items: center; flex-wrap: nowrap; justify-content: space-between; } - .log { + .header-log { width: 6.9375em; height: 1.75em; margin-right: 9.125em; flex-shrink: 0; } - .nav-items { + .header-nav-items { display: flex; align-items: center; justify-content: space-between; @@ -52,7 +60,7 @@ margin-right: 5.125em; } - .nav-item { + .header-nav-item { height: 100%; display: flex; align-items: center; @@ -62,7 +70,7 @@ min-width: 0; } - .nav-title { + .header-nav-title { position: relative; height: 100%; font-size: 1em; @@ -74,7 +82,7 @@ } /* 导航项下划线 */ - .nav-title::after { + .header-nav-title::after { content: ''; position: absolute; bottom: 0; @@ -86,17 +94,17 @@ transition: transform 0.2s ease; } - .nav-title:hover::after, - .nav-title.active::after { + .header-nav-title:hover::after, + .header-nav-title.active::after { transform: scaleX(1); } - .nav-title.active { + .header-nav-title.active { color: #004bfa; } /* 右侧功能按钮 */ - .nav-actions { + .header-nav-actions { margin-left: auto; display: flex; align-items: center; @@ -106,42 +114,42 @@ } /* 搜索按钮容器 */ - .search-wrapper { + .header-search-wrapper { position: relative; display: inline-block; } /* 语言按钮容器 */ - .lang-wrapper { + .header-lang-wrapper { position: relative; display: inline-block; } /* 购买按钮容器 */ - .buy-wrapper { + .header-buy-wrapper { position: relative; display: inline-block; } - .nav-btn { + .header-nav-btn { cursor: pointer; display: flex; align-items: center; transition: color 0.2s; } - .nav-btn img { + .header-nav-btn img { width: 1.5em; max-width: 24px; max-height: 24px; height: 1.5em; } - .nav-btn:hover { + .header-nav-btn:hover { color: #004bfa; } - .buy-btn { + .header-buy-btn { background-color: #004bfa; color: #fff; padding: 0.375em 1.125em; @@ -156,84 +164,84 @@ /* 1920px 及以上 - 100% */ @media screen and (min-width: 1920px) { - body { + .header { font-size: 16px; } } /* 1680px - 1919px - 缩放至 93.75% */ @media screen and (max-width: 1919px) and (min-width: 1680px) { - body { + .header { font-size: 15px; } } /* 1600px - 1679px - 缩放至 90.625% */ @media screen and (max-width: 1679px) and (min-width: 1600px) { - body { + .header { font-size: 14.5px; } } /* 1520px - 1599px - 缩放至 87.5% */ @media screen and (max-width: 1599px) and (min-width: 1520px) { - body { + .header { font-size: 14px; } } /* 1440px - 1519px - 缩放至 84.375% */ @media screen and (max-width: 1519px) and (min-width: 1440px) { - body { + .header { font-size: 13.5px; } } /* 1360px - 1439px - 缩放至 81.25% */ @media screen and (max-width: 1439px) and (min-width: 1360px) { - body { + .header { font-size: 13px; } } /* 1280px - 1359px - 缩放至 78.125% */ @media screen and (max-width: 1359px) and (min-width: 1280px) { - body { + .header { font-size: 12.5px; } } /* 1200px - 1279px - 缩放至 75% */ @media screen and (max-width: 1279px) and (min-width: 1200px) { - body { + .header { font-size: 12px; } } /* 1140px - 1199px - 缩放至 75% (保持最小12px) */ @media screen and (max-width: 1199px) and (min-width: 1140px) { - body { + .header { font-size: 12px; } } /* 1080px - 1139px - 缩放至 75% (保持最小12px) */ @media screen and (max-width: 1139px) and (min-width: 1080px) { - body { + .header { font-size: 12px; } } /* 992px - 1079px - 缩放至 75% (保持最小12px) */ @media screen and (max-width: 1079px) and (min-width: 992px) { - body { + .header { font-size: 12px; } } /* 小于992px - 保持最小12px */ @media screen and (max-width: 991px) { - body { + .header { font-size: 12px; } } @@ -241,12 +249,12 @@ /* ========== 以下为原有样式,保持不变 ========== */ /* 原有的下拉框样式 */ - .dropdown { + .header-dropdown { box-sizing: border-box; max-width: 1920px; width: 100%; position: absolute; - top: 3.75em; + top: 4em; left: 0; right: 0; background-color: #fafafb; @@ -257,25 +265,25 @@ display: none; } - .nav-item:hover .dropdown { + .header-nav-item:hover .header-dropdown { display: block; } /* Tabs切换栏:居中,宽度100%(基于下拉框内容区) */ - .dropdown-tabs { + .header-dropdown-tabs { display: flex; justify-content: center; margin: 0 auto; - padding-bottom: 2em; + padding-bottom: 1.75em; padding-top: 1.75em; width: calc(100% - 2.5em); } - .tab-item:last-child { + .header-tab-item:last-child { margin-right: 0; } - .tab-item { + .header-tab-item { font-size: 1em; color: #1d1d1f; cursor: pointer; @@ -283,7 +291,7 @@ margin-right: 9.375em; } - .tab-item.active::after { + .header-tab-item.active::after { content: ''; position: absolute; bottom: -0.6875em; @@ -294,7 +302,7 @@ } /* 下拉框内容容器:宽度100%,左右留20px边距 */ - .dropdown-content { + .header-dropdown-content { width: 100%; min-width: 1080px; padding: 0 12.5em; @@ -305,122 +313,160 @@ } /* 左侧6个分类区块 */ - .dropdown-category { + .header-dropdown-category { display: flex; flex-wrap: wrap; flex-direction: column; flex: 1; } - .category-box { + .header-category-box { display: flex; justify-content: space-between; } /* 单个分类区块 */ - .category-block { + .header-category-block { flex: 1; box-sizing: border-box; } - .category-block:nth-child(5) { + .header-category-block:nth-child(5) { margin-bottom: 0; } - .category-block:nth-child(6) { + .header-category-block:nth-child(6) { margin-bottom: 0; } - .category-title { + .header-category-title { font-size: 1em; color: #1d1d1f; margin-bottom: 0.75em; font-weight: 900; } - .category-list { + .header-category-list { list-style: none; } - .category-item { + .header-category-item { font-size: 0.875em; color: #686a70; - margin-bottom: 0.5em; + margin-bottom: 0.71em; cursor: pointer; } - .category-item a { + .header-category-item a { color: #686a70; } - .category-item:last-child { + .header-category-item:last-child { margin: 0; } - .category-item a:hover { + .header-category-item a:hover { color: #0066ff; } /* 右侧产品展示区:自适应剩余宽度,无溢出 */ - .dropdown-products { + .header-dropdown-products { display: flex; flex-wrap: wrap; justify-content: flex-end; max-width: 46.75em; } + + + .header-dropdown-products { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + flex: 1; + min-width: 0; + gap: 1.5em 0; + } - .product-card, - .product-card-1 { + + + .header-product-card { + text-align: center; + width: calc(50% - 0.75em); + max-width: 22.625em; + flex-shrink: 1; + min-width: 260px; + transition: transform 0.2s ease; + } + + + .header-product-card-1 { text-align: center; width: 100%; max-width: 22.625em; } - .product-card:hover { + .header-product-card:hover { transform: translateY(-2px) } - .product-card-1:hover { + .header-product-card-1:hover { transform: translateY(-2px) } - .product-card:nth-child(1) { + .header-product-card:nth-child(1) { margin-right: 1.5em; } - .product-card:nth-child(3) { + .header-product-card:nth-child(3) { margin-right: 1.5em; margin-top: 1.4375em; } - .product-card:nth-child(4) { + .header-product-card:nth-child(4) { margin-top: 1.4375em; } - .product-img { - width: 22.625em; - height: 12.25em; + /*.header-product-img {*/ + /* width: 22.625em;*/ + /* height: 12.25em;*/ + /* background-color: #f5f5f5;*/ + /* display: flex;*/ + /* align-items: center;*/ + /* justify-content: center;*/ + /* color: #999;*/ + /*}*/ + + /*.header-product-img img {*/ + /* width: 22.625em;*/ + /* height: 12.25em;*/ + /*}*/ + .header-product-img { + width: 100%; + aspect-ratio: 22.625 / 12.25; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; color: #999; + overflow: hidden; + border-radius: 0.5em; } - - .product-img img { - width: 22.625em; - height: 12.25em; + + .header-product-img img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; } - - .product-name { + .header-product-name { font-size: 1em; color: #1d1d1f; line-height: 1.5; } /* 修改 .dropdown-content1 的样式 - 卡片居中对齐 */ - .dropdown-content1 { + .header-dropdown-content1 { width: 100%; min-width: 1080px; padding: 3.4em 12.5em; @@ -432,7 +478,7 @@ } /* 修改 .product-card-1 的样式 - 设置宽度,确保一行最多4个 */ - .product-card-1 { + .header-product-card-1 { width: calc(25% - 1.40625em); background: #fff; border-radius: 0.5em; @@ -442,7 +488,7 @@ } /* 如果需要更好的兼容性,用 padding 技巧替代 aspect-ratio */ - .product-img-1 { + .header-product-img-1 { background-color: #f5f5f5; display: flex; align-items: center; @@ -456,18 +502,18 @@ } /* 图片样式 - 确保图片正确显示 */ - .product-img-1 img { + .header-product-img-1 img { position: absolute; top: 0; left: 0; - width: 100%; + width: 100%;header- height: 100%; object-fit: cover; display: block; } /* 内容区域自适应 */ - .product-title-1 { + .header-product-title-1 { font-weight: 900; font-size: 1em; color: #1d1d1f; @@ -477,7 +523,7 @@ word-break: break-word; } - .product-name-1 { + .header-product-name-1 { font-size: 1em; color: #686a70; text-align: left; @@ -488,16 +534,16 @@ } /* 隐藏非当前选中的Tabs内容 */ - .tab-content { + .header-tab-content { display: none; } - .tab-content.active { + .header-tab-content.active { display: flex; } /* ========== 搜索下拉框样式 ========== */ - .search-dropdown { + .header-search-dropdown { position: absolute; top: 100%; right: 0; @@ -512,11 +558,11 @@ animation: fadeIn 0.2s ease; } - .search-dropdown.show { + .header-search-dropdown.show { display: block; } - .search-input-wrapper { + .header-search-input-wrapper { display: flex; gap: 0.625em; border: 1px solid #e5e7eb; @@ -526,7 +572,7 @@ margin-bottom: 1em; } - .search-input { + .header-search-input { flex: 1; border: none; outline: none; @@ -535,11 +581,11 @@ color: #1d1d1f; } - .search-input::placeholder { + .header-search-input::placeholder { color: #9ca3af; } - .search-submit { + .header-search-submit { background: #004bfa; color: white; border: none; @@ -550,16 +596,16 @@ transition: background 0.2s; } - .search-submit:hover { + .header-search-submit:hover { background: #0039cc; } /* 搜索记录区域 */ - .search-history { + .header-search-history { margin-bottom: 1.25em; } - .history-title { + .header-history-title { font-size: 1em; color: #9ca3af; margin-bottom: 0.625em; @@ -568,7 +614,7 @@ align-items: center; } - .clear-history { + .header-clear-history { color: #004bfa; cursor: pointer; font-size: 14px; @@ -577,17 +623,17 @@ transition: background 0.2s; } - .clear-history:hover { + .header-clear-history:hover { background: #f0f7ff; } - .history-list { + .header-history-list { display: flex; flex-wrap: wrap; gap: 0.5em; } - .history-item { + .header-history-item { font-size: 0.8125em; color: #1d1d1f; cursor: pointer; @@ -600,34 +646,34 @@ gap: 0.25em; } - .history-item:hover { + .header-history-item:hover { background: #004bfa; color: white; } - .history-item .delete-icon { + .header-history-item .header-delete-icon { font-size: 0.875em; opacity: 0; transition: opacity 0.2s; } - .history-item:hover .delete-icon { + .header-history-item:hover .header-delete-icon { opacity: 1; } /* 热销产品区域 - 使用flex-wrap,一行最多3个,超过自动换行 */ - .hot-products { + .header-hot-products { border-top: 1px solid #f0f0f0; padding-top: 0.75em; } - .hot-title { + .header-hot-title { font-size: 1em; color: #9ca3af; margin-bottom: 0.75em; } - .hot-product-list { + .header-hot-product-list { display: flex; flex-wrap: wrap; gap: 1.5em; @@ -635,7 +681,7 @@ padding: 0; } - .hot-product-item { + .header-hot-product-item { flex: 0 0 calc(33.333% - 1em); min-width: 0; cursor: pointer; @@ -644,11 +690,11 @@ box-sizing: border-box; } - .hot-product-item:hover { + .header-hot-product-item:hover { transform: translateY(-2px); } - .hot-product-img { + .header-hot-product-img { width: 7.1875em; height: 7.1875em; max-width: 115px; @@ -662,18 +708,18 @@ overflow: hidden; } - .hot-product-img img { + .header-hot-product-img img { width: 100%; height: 100%; object-fit: cover; display: block; } - .hot-product-img .emoji { + .header-hot-product-img .emoji { font-size: 2.5em; } - .hot-product-name { + .header-hot-product-name { font-size: 0.8125em; color: #1d1d1f; font-weight: 500; @@ -684,7 +730,7 @@ } /* 语言下拉框样式 */ - .lang-dropdown { + .header-lang-dropdown { position: absolute; top: 100%; right: 0; @@ -699,15 +745,16 @@ animation: fadeIn 0.2s ease; } - .lang-dropdown.show { + .header-lang-dropdown.show { display: block; } - .lang-item { + .header-lang-item { display: block; padding: 0.75em 1.25em; cursor: pointer; transition: background 0.2s; + width: 100%; font-size: 0.875em; color: #1d1d1f; text-decoration: none; @@ -716,27 +763,31 @@ gap: 0.625em; } - .lang-item:hover { + .header-lang-item:hover { background: #f3f4f6; color: #004bfa; } - .lang-item.active { + .header-lang-item.active { color: #004bfa; background: #f0f7ff; } - .lang-code { + .header-lang-code { font-weight: 500; min-width: 2.5em; + white-space:nowrap; } - .lang-name { + .header-lang-name { flex: 1; } - - .lang-dropdown-delete-icon, - .search-dropdown-delete-icon { + .header-lang-name-en { + flex: 1; + min-width: 126px; + } + .header-lang-dropdown-delete-icon, + .header-search-dropdown-delete-icon { font-size: 12px; position: absolute; top: 10px; @@ -746,7 +797,7 @@ } /* ========== 购买下拉框样式 ========== */ - .buy-dropdown { + .header-buy-dropdown { box-sizing: border-box; max-width: 1920px; width: 100%; @@ -762,12 +813,12 @@ padding: 3.5em 12.5em; } - .buy-dropdown.show { + .header-buy-dropdown.show { display: block; } /* 购买下拉框内容容器 - 居中显示,固定一行7个卡片 */ - .buy-dropdown-content { + .header-buy-dropdown-content { width: 100%; max-width: 1520px; margin: 0 auto; @@ -779,7 +830,7 @@ } /* 购买产品卡片:宽度96px,右边距动态计算 */ - .buy-product-card { + .header-buy-product-card { width: 96px; text-align: center; cursor: pointer; @@ -787,22 +838,22 @@ } - .buy-product-card:hover { + .header-buy-product-card:hover { transform: translateY(-2px) } /* 每行第7个卡片(一行最后一个)清除右边距 */ - .buy-product-card:nth-child(7n) { + .header-buy-product-card:nth-child(7n) { margin-right: 0; } - .buy-product-card a { + .header-buy-product-card a { text-decoration: none; display: block; } /* 图片容器:宽度100%,高度自适应,保持正方形比例 */ - .buy-product-img { + .header-buy-product-img { width: 100%; aspect-ratio: 1 / 1; background-color: #f5f5f5; @@ -815,7 +866,7 @@ } /* 图片样式:实现悬浮切换效果,完全填充容器 */ - .buy-product-img img { + .header-buy-product-img img { position: absolute; top: 0; left: 0; @@ -827,25 +878,25 @@ } /* 默认显示普通图,隐藏悬浮图 */ - .buy-product-img .default-img { + .header-buy-product-img .header-default-img { opacity: 1; } - .buy-product-img .hover-img { + .header-buy-product-img .header-hover-img { opacity: 0; } /* 鼠标悬停时:显示悬浮图,隐藏普通图 */ - .buy-product-card:hover .buy-product-img .default-img { + .header-buy-product-card:hover .header-buy-product-img .header-default-img { opacity: 0; } - .buy-product-card:hover .buy-product-img .hover-img { + .header-buy-product-card:hover .header-buy-product-img .header-hover-img { opacity: 1; } /* 产品标题样式:不加粗,字体大小16px,超出一行显示省略号 */ - .buy-product-title { + .header-buy-product-title { font-weight: normal; font-size: 1em; color: #1d1d1f; @@ -876,31 +927,31 @@ /* 1280px及以下屏幕,渐进式调整间距 */ @media screen and (max-width: 1280px) { - .log { + .header-log { margin-right: 2em; } - .nav-items { + .header-nav-items { margin-right: 1em; } - .nav-outer { + .header-nav-outer { padding: 0 12em; } - .dropdown-content { + .header-dropdown-content { padding: 0 12em; } - .dropdown-content1 { + .header-dropdown-content1 { padding: 0 12em; } - .buy-dropdown { + .header-buy-dropdown { padding: 3.5em 12em; } - .buy-dropdown-content { + .header-buy-dropdown-content { max-width: 1200px; row-gap: 100px; } @@ -908,23 +959,23 @@ /* 1152px及以下,进一步缩小 */ @media screen and (max-width: 1152px) { - .log { + .header-log { margin-right: 1em; } - .nav-outer { + .header-nav-outer { padding: 0 10em; } - .dropdown-content { + .header-dropdown-content { padding: 0 10em; } - .dropdown-content1 { + .header-dropdown-content1 { padding: 0 10em; } - .buy-dropdown { + .header-buy-dropdown { padding: 3.5em 10em; } @@ -936,68 +987,69 @@ /* 1080px及以下,最小间距 */ @media screen and (max-width: 1080px) { - .log { + .header-log { margin-right: 1em; } - .nav-outer { + .header-nav-outer { padding: 0 8em; } - .dropdown-content { + .header-dropdown-content { padding: 0 8em; } - .dropdown-content1 { + .header-dropdown-content1 { padding: 0 8em; } - .buy-dropdown { + .header-buy-dropdown { padding: 3.5em 8em; } - .buy-dropdown-content { + .header-buy-dropdown-content { max-width: 1000px; row-gap: 60px; } } +
    -