feat(decorate): 优化首页商品列表逻辑并移除缓存机制 - 移除商品列表缓存机制,提高数据实时性 - 重构首页商品ID处理逻辑,支持合并装修ID与全店商品ID - 新增allShelfGoodsIds方法获取所有可售商品ID - 添加mergeGoodsIdStrings方法合并多个商品ID字符串 - 优化buildHomeGoodsContextFromContent方法统一处理内容解析 - 增强首页商品上下文构建,支持多种数据源合并 ```
414 行
14 KiB
PHP
414 行
14 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro\model;
|
||
|
||
use think\Model;
|
||
use addons\shopro\exception\Exception;
|
||
|
||
/**
|
||
* 模板装修
|
||
*/
|
||
class Decorate extends Model
|
||
{
|
||
|
||
// 表名,不含前缀
|
||
protected $name = 'shopro_decorate';
|
||
// 自动写入时间戳字段
|
||
protected $autoWriteTimestamp = 'int';
|
||
// 定义时间戳字段名
|
||
protected $createTime = 'createtime';
|
||
protected $updateTime = 'updatetime';
|
||
protected $deleteTime = 'deletetime';
|
||
|
||
protected $hidden = ['createtime', 'updatetime', 'deletetime'];
|
||
// 追加属性
|
||
protected $append = [
|
||
|
||
];
|
||
|
||
// 获取当前平台模板装修数据
|
||
public static function getCurrentPlatformDecorate($platform = '', $id = 0)
|
||
{
|
||
$where = ['deletetime' => null];
|
||
if ($id !== 0) {
|
||
$where['id'] = $id;
|
||
}
|
||
if ($platform == 'preview') {
|
||
$decorate = self::where($where)->order('id asc')->find();
|
||
} else {
|
||
$where['status'] = 'normal';
|
||
$where['type'] = 'shop';
|
||
$decorate = self::where('find_in_set(:platform,platform)', ['platform' => $platform])->where($where)->order('id asc')->find();
|
||
}
|
||
|
||
if (!$decorate) {
|
||
new Exception('未找到模板');
|
||
}
|
||
|
||
$template = self::getDecorateContent($decorate);
|
||
return $template;
|
||
}
|
||
|
||
public static function getCustomDecorate($id)
|
||
{
|
||
$decorateContent = DecorateContent::all(['decorate_id' => $id]);
|
||
foreach ($decorateContent as &$v) {
|
||
$v['content'] = self::getDecorateContentByType($v);
|
||
}
|
||
return $decorateContent;
|
||
}
|
||
|
||
public static function asyncDecorateScreenShot($params)
|
||
{
|
||
$decorate = self::get($params['shop_id']);
|
||
if ($decorate) {
|
||
$decorate->image = $params['image'];
|
||
$decorate->save();
|
||
|
||
}
|
||
}
|
||
|
||
public static function getDecorateContent($decorate)
|
||
{
|
||
$template['home'] = []; //首页
|
||
$template['user'] = []; //个人中心
|
||
$template['tabbar'] = []; //底部菜单
|
||
$template['popup'] = []; //弹出提醒
|
||
$template['float-button'] = []; //悬浮按钮
|
||
$decorateContent = DecorateContent::all(['decorate_id' => $decorate->id]);
|
||
foreach ($decorateContent as $k => $v) {
|
||
$v['content'] = self::getDecorateContentByType($v);
|
||
$template[$v->category][] = $v;
|
||
}
|
||
if (!empty($template['home'])) {
|
||
$template['home'] = self::enrichHomeGoodsBlocks($template['home']);
|
||
}
|
||
return $template;
|
||
}
|
||
|
||
/**
|
||
* 首页「精选好酒」模块:API 侧补齐 goods_ids,避免小程序仅带 category_id 时少显示装修选中的 hidden 商品。
|
||
*
|
||
* @return array<int, string> [category_id => goods_ids]
|
||
*/
|
||
public static function resolveHomeGoodsListContext($platform = '')
|
||
{
|
||
try {
|
||
$template = self::getCurrentPlatformDecorate($platform ?: 'wxMiniProgram');
|
||
} catch (\Throwable $e) {
|
||
return ['category_id' => 0, 'goods_ids' => ''];
|
||
}
|
||
$home = $template['home'] ?? [];
|
||
if (!$home) {
|
||
return ['category_id' => 0, 'goods_ids' => ''];
|
||
}
|
||
$ctx = self::extractHomeGoodsContextFromBlocks($home);
|
||
return [
|
||
'category_id' => (int) ($ctx['category_id'] ?? 0),
|
||
'goods_ids' => (string) ($ctx['goods_ids'] ?? ''),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 与首页 sh-hot-goods 请求对齐:无筛选时用装修商品;首页分类与全部分类列表一致。
|
||
*/
|
||
public static function normalizeHomeGoodsListParams(array $params, $platform = '')
|
||
{
|
||
$keywords = trim((string) ($params['keywords'] ?? ''));
|
||
if ($keywords !== '') {
|
||
return $params;
|
||
}
|
||
|
||
$ctx = self::resolveHomeGoodsListContext($platform);
|
||
$homeIds = trim((string) ($ctx['goods_ids'] ?? ''));
|
||
$homeCat = (int) ($ctx['category_id'] ?? 0);
|
||
|
||
$categoryId = isset($params['category_id']) ? (int) $params['category_id'] : 0;
|
||
$isHomeScope = $categoryId <= 0 || ($homeCat > 0 && $categoryId === $homeCat);
|
||
if (!$isHomeScope) {
|
||
return $params;
|
||
}
|
||
|
||
// 首页:请求里已有 goods_ids(装修 ids)时也合并全店上架/隐藏,避免只返回 2 个
|
||
$mergedIds = self::mergeGoodsIdStrings(
|
||
$params['goods_ids'] ?? '',
|
||
$homeIds,
|
||
self::allShelfGoodsIds()
|
||
);
|
||
if ($mergedIds === '') {
|
||
return $params;
|
||
}
|
||
|
||
$params['goods_ids'] = $mergedIds;
|
||
unset($params['category_id']);
|
||
return $params;
|
||
}
|
||
|
||
/**
|
||
* 商城当前可售(上架+隐藏)商品,按 sort/weigh 排序
|
||
*/
|
||
public static function allShelfGoodsIds()
|
||
{
|
||
$ids = Goods::where('status', 'in', ['up', 'hidden'])
|
||
->order('weigh asc, id desc')
|
||
->column('id');
|
||
return self::normalizeGoodsIdsString(implode(',', $ids));
|
||
}
|
||
|
||
protected static function mergeGoodsIdStrings(...$parts)
|
||
{
|
||
$idMap = [];
|
||
foreach ($parts as $part) {
|
||
$part = trim((string) $part);
|
||
if ($part === '') {
|
||
continue;
|
||
}
|
||
foreach (explode(',', $part) as $id) {
|
||
$id = (int) $id;
|
||
if ($id > 0) {
|
||
$idMap[$id] = $id;
|
||
}
|
||
}
|
||
}
|
||
if (!$idMap) {
|
||
return '';
|
||
}
|
||
$ordered = Goods::where('id', 'in', array_values($idMap))
|
||
->where('status', 'in', ['up', 'hidden'])
|
||
->order('weigh asc, id desc')
|
||
->column('id');
|
||
return self::normalizeGoodsIdsString(implode(',', $ordered));
|
||
}
|
||
|
||
protected static function enrichHomeGoodsBlocks(array $home)
|
||
{
|
||
$ctx = self::extractHomeGoodsContextFromBlocks($home);
|
||
if (($ctx['goods_ids'] ?? '') === '') {
|
||
return $home;
|
||
}
|
||
$targetIdx = null;
|
||
foreach ($home as $idx => $block) {
|
||
if (in_array($block['type'] ?? '', ['goods-list', 'goods-group'], true)) {
|
||
$targetIdx = $idx;
|
||
break;
|
||
}
|
||
}
|
||
if ($targetIdx === null) {
|
||
$targetIdx = self::findLastCategoryTabsIndex($home);
|
||
}
|
||
if ($targetIdx === null || $targetIdx < 0) {
|
||
return $home;
|
||
}
|
||
$content = $home[$targetIdx]['content'] ?? [];
|
||
$content['ids'] = $ctx['goods_ids'];
|
||
unset($content['id']);
|
||
$home[$targetIdx]['content'] = $content;
|
||
return $home;
|
||
}
|
||
|
||
protected static function findLastCategoryTabsIndex(array $home)
|
||
{
|
||
for ($i = count($home) - 1; $i >= 0; $i--) {
|
||
if (($home[$i]['type'] ?? '') === 'category-tabs') {
|
||
return $i;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
protected static function extractHomeGoodsContextFromBlocks(array $home)
|
||
{
|
||
$categoryId = 0;
|
||
foreach ($home as $block) {
|
||
$type = $block['type'] ?? '';
|
||
if (!in_array($type, ['goods-list', 'goods-group'], true)) {
|
||
continue;
|
||
}
|
||
$content = $block['content'] ?? [];
|
||
$ctx = self::buildHomeGoodsContextFromContent($content);
|
||
if ($ctx['goods_ids'] !== '') {
|
||
return $ctx;
|
||
}
|
||
}
|
||
|
||
for ($i = count($home) - 1; $i >= 0; $i--) {
|
||
if (($home[$i]['type'] ?? '') !== 'category-tabs') {
|
||
continue;
|
||
}
|
||
$content = $home[$i]['content'] ?? [];
|
||
$ctx = self::buildHomeGoodsContextFromContent($content);
|
||
if ($ctx['goods_ids'] !== '' || $ctx['category_id'] > 0) {
|
||
return $ctx;
|
||
}
|
||
break;
|
||
}
|
||
|
||
return ['category_id' => 0, 'goods_ids' => ''];
|
||
}
|
||
|
||
protected static function buildHomeGoodsContextFromContent(array $content)
|
||
{
|
||
$categoryId = (int) ($content['id'] ?? ($content['category_arr'][0]['id'] ?? 0));
|
||
$ids = self::mergeGoodsIdStrings(
|
||
!empty($content['ids']) ? self::normalizeGoodsIdsString($content['ids']) : '',
|
||
self::extractGoodsIdsFromTimeData($content['timeData'] ?? null),
|
||
$categoryId > 0 ? self::goodsIdsFromCategoryId($categoryId) : ''
|
||
);
|
||
return [
|
||
'category_id' => $categoryId,
|
||
'goods_ids' => $ids,
|
||
];
|
||
}
|
||
|
||
protected static function extractGoodsIdsFromTimeData($timeData)
|
||
{
|
||
if ($timeData === null || $timeData === '') {
|
||
return '';
|
||
}
|
||
if (is_string($timeData)) {
|
||
$decoded = json_decode($timeData, true);
|
||
if (is_array($decoded)) {
|
||
$timeData = $decoded;
|
||
} else {
|
||
return '';
|
||
}
|
||
}
|
||
if (!is_array($timeData)) {
|
||
return '';
|
||
}
|
||
$ids = [];
|
||
foreach ($timeData as $row) {
|
||
if (is_array($row) && !empty($row['id'])) {
|
||
$ids[] = (int) $row['id'];
|
||
} elseif (is_numeric($row)) {
|
||
$ids[] = (int) $row;
|
||
}
|
||
}
|
||
return self::normalizeGoodsIdsString(implode(',', $ids));
|
||
}
|
||
|
||
protected static function normalizeGoodsIdsString($ids)
|
||
{
|
||
$ids = is_array($ids) ? implode(',', $ids) : (string) $ids;
|
||
$parts = array_filter(array_map('intval', explode(',', $ids)));
|
||
return implode(',', array_unique($parts));
|
||
}
|
||
|
||
protected static function goodsIdsFromCategoryId($categoryId)
|
||
{
|
||
$categoryId = (int) $categoryId;
|
||
if ($categoryId <= 0) {
|
||
return '';
|
||
}
|
||
$categoryIds = Category::getCategoryIds($categoryId);
|
||
if (!$categoryIds) {
|
||
return '';
|
||
}
|
||
$query = Goods::where('status', 'in', ['up', 'hidden']);
|
||
$query->where(function ($q) use ($categoryIds) {
|
||
foreach ($categoryIds as $cid) {
|
||
$q->whereOrRaw("find_in_set($cid, category_ids)");
|
||
}
|
||
});
|
||
$ids = $query->order('weigh asc, id desc')->column('id');
|
||
return self::normalizeGoodsIdsString(implode(',', $ids));
|
||
}
|
||
|
||
public static function getDecorateContentByType($templateValue)
|
||
{
|
||
$content = json_decode($templateValue->content, true);
|
||
if (!is_array($content)) {
|
||
$content = [];
|
||
}
|
||
switch ($templateValue['type']) {
|
||
case 'search':
|
||
case 'coupons':
|
||
case 'live':
|
||
case 'seckill':
|
||
case 'groupon':
|
||
case 'wallet-card':
|
||
case 'order-card':
|
||
case 'rich-text':
|
||
case 'nav-bg':
|
||
break;
|
||
case 'banner':
|
||
case 'menu':
|
||
case 'adv':
|
||
case 'grid-list':
|
||
case 'popup':
|
||
case 'nav-list':
|
||
if (!empty($content['list']) && is_array($content['list'])) {
|
||
foreach ($content['list'] as &$l) {
|
||
$l['image'] && $l['image'] = cdnurl($l['image'], true);
|
||
}
|
||
unset($l);
|
||
$content['list'] = array_values(array_filter($content['list'], function ($item) {
|
||
return !self::isHiddenMenuItem($item);
|
||
}));
|
||
} else {
|
||
$content['list'] = [];
|
||
}
|
||
break;
|
||
case 'user':
|
||
case 'title-block':
|
||
case 'goods-list':
|
||
case 'goods-group':
|
||
case 'image-goods':
|
||
$content['image'] && $content['image'] = cdnurl($content['image'], true);
|
||
break;
|
||
case 'tabbar':
|
||
if (isset($content['style']) && $content['style'] < 3 && !empty($content['list']) && is_array($content['list'])) {
|
||
foreach ($content['list'] as &$l) {
|
||
$l['image'] && $l['image'] = cdnurl($l['image'], true);
|
||
$l['activeImage'] && $l['activeImage'] = cdnurl($l['activeImage'], true);
|
||
}
|
||
unset($l);
|
||
} elseif (!isset($content['list']) || !is_array($content['list'])) {
|
||
$content['list'] = [];
|
||
}
|
||
break;
|
||
case 'float-button':
|
||
$content['image'] = cdnurl($content['image'], true);
|
||
if (!empty($content['list']) && is_array($content['list'])) {
|
||
foreach ($content['list'] as &$l) {
|
||
if ($l['style'] == 1) {
|
||
$l['image'] && $l['image'] = cdnurl($l['image'], true);
|
||
}
|
||
$l['btnimage'] && $l['btnimage'] = cdnurl($l['btnimage'], true);
|
||
}
|
||
unset($l);
|
||
} else {
|
||
$content['list'] = [];
|
||
}
|
||
break;
|
||
}
|
||
return $content;
|
||
}
|
||
|
||
/**
|
||
* 已关闭的功能入口,不在前端展示
|
||
*/
|
||
protected static function isHiddenMenuItem($item)
|
||
{
|
||
if (!is_array($item)) {
|
||
return false;
|
||
}
|
||
|
||
$name = isset($item['name']) ? (string) $item['name'] : '';
|
||
$path = isset($item['path']) ? (string) $item['path'] : '';
|
||
|
||
$hiddenNames = ['我的报修', '门店管理', '我的拼团', '今日必拼'];
|
||
if (in_array($name, $hiddenNames, true)) {
|
||
return true;
|
||
}
|
||
|
||
$hiddenPaths = ['/pages/activity/groupon', '/pages/app/merchant', '/pages/order/after-sale/list'];
|
||
foreach ($hiddenPaths as $hiddenPath) {
|
||
if ($path && strpos($path, $hiddenPath) !== false) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
} |