文件
xionggeshop/addons/shopro/model/Goods.php
可乐加冰 5b2d85af5f ```
feat(shopro): 完善首页商品列表装修逻辑,支持hidden商品展示

- 在Goods控制器中添加normalizeHomeGoodsListParams方法调用,
  用于处理首页商品列表参数规范化
- 重构Decorate模型,移除未使用的DecorateContent引用
- 新增resolveHomeGoodsListContext方法,解析首页商品列表上下文
- 实现normalizeHomeGoodsListParams方法,确保关键词搜索为空时
 使用装修配置的商品ID
- 添加enrichHomeGoodsBlocks方法,丰富首页商品区块数据
- 优化extractHomeGoodsContextFromBlocks方法,从装修区块中提取
  商品上下文信息
- 修改Goods模型,在指定goods_ids时允许查询status为hidden的商品,
  确保装修中设置的隐藏商品能够正常展示
```
2026-10-02 15:14:55 +08:00

664 行
21 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace addons\shopro\model;
use think\Model;
use addons\shopro\exception\Exception;
use addons\shopro\library\traits\model\goods\GoodsActivity;
use addons\shopro\model\GoodsSku;
use addons\shopro\model\GoodsSkuPrice;
use think\Db;
use traits\model\SoftDelete;
/**
* 商品模型
*/
class Goods extends Model
{
use SoftDelete, GoodsActivity;
// 表名,不含前缀
protected $name = 'shopro_goods';
// 自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
protected $deleteTime = 'deletetime';
protected $hidden = ['createtime', 'updatetime', 'status'];
// 列表动态隐藏字段
public static $list_hidden = ['content', 'params', 'images', 'service_ids'];
// 追加属性
protected $append = [
'dispatch_type_arr'
];
/**
* params 请求参数
* is_page 是否分页
*/
public static function getGoodsList($params, $is_page = true)
{
extract($params);
$where = [
'status' => ['in', ((isset($type) && $type == 'all') ? ['up', 'hidden'] : ['up'])], // type = all 查询全部
];
// 排序字段
if (isset($order)) {
$order = self::getGoodsListOrder($order);
} else {
// $order = 'weigh desc';
$order = 'weigh asc';
}
if (isset($keywords) && $keywords !== '') {
$where['title|subtitle'] = ['like', "%$keywords%"];
}
if (isset($goods_ids) && $goods_ids !== '') {
$order = 'field(id, ' . $goods_ids . ')'; // 如果传了 goods_ids 就按照里面的 id 进行排序
$goodsIdsArray = explode(',', $goods_ids);
$where['id'] = ['in', $goodsIdsArray];
// 装修/首页指定 id 时允许 hidden(如招商礼盒),仍不展示 down
$where['status'] = ['in', ['up', 'hidden']];
}
$category_ids = [];
if (isset($category_id) && $category_id != 0) {
// 查询分类所有子分类,包括自己
$category_ids = Category::getCategoryIds($category_id);
}
$goods = self::where($where)->where(function ($query) use ($category_ids) {
// 所有子分类使用 find_in_set or 匹配,亲测速度并不慢
foreach ($category_ids as $key => $category_id) {
$query->whereOrRaw("find_in_set($category_id, category_ids)");
}
});
// 过滤有活动的商品
if (isset($no_activity) && $no_activity) {
$goods = $goods->whereNotExists(function ($query) use ($where) {
$activityTableName = (new Activity())->getQuery()->getTable();
$goodsTableName = (new self())->getQuery()->getTable();
$query->table($activityTableName)
->where("find_in_set(" . $goodsTableName . ".id, goods_ids)")
->where('deletetime', 'null'); // 必须手动加上 deletetime = null
});
}
$goods = $goods->field('*,(sales + show_sales) as total_sales')->orderRaw($order)->order('id desc');
$cacheKey = 'goodslist-' . ($is_page ? 'page' : 'all') . '-' . md5(json_encode($params));
// 判断缓存
$goodsCache = cache($cacheKey);
if ($goodsCache) {
// 存在缓存直接 返回
$goodsCache = json_decode($goodsCache, true);
return $goodsCache ?: [];
}
if ($is_page) {
$goods = $goods->paginate($per_page ?? 10);
$goodsData = $goods->items();
} else {
$goods = $goodsData = $goods->select();
}
$data = [];
if ($goodsData) {
$collection = collection($goodsData);
$data = $collection->hidden(self::$list_hidden);
// 处理活动
// load_relation($data, 'skuPrice'); // 只针对数组
$data->load('skuPrice'); // 延迟预加载
// if (!isset($no_activity) || !$no_activity) { // 没有 传入 no_activity 或者 no_activity = false
// 默认查询活动, no_activity 的时候这里也要执行一下,这里计算了销量规格等信息
$source = !empty($params['platform']) ? $params['platform'] : '';
foreach ($data as $key => $g) {
$data[$key] = self::operActivity($g, $g['sku_price'], $source);
}
// }
}
if ($is_page) {
$goods->data = $data;
} else {
$goods = $data;
// 目前只缓存不分页的请求,
// 缓存暂时注释,如果需要,可以打开,请注意后台更新商品记得清除缓存
// cache($cacheKey, json_encode($goods), (600 + mt_rand(0, 300)));
}
return $goods;
}
public static function getGoodsListByIds($goodsIds)
{
$goodsIdsArray = explode(',', $goodsIds);
$where = [
'status' => 'up',
'deletetime' => null,
'id' => ['in', $goodsIdsArray]
];
$goods = self::where($where)->paginate(10);
if ($goods->items()) {
$collection = collection($goods->items());
$data = $collection->hidden(self::$list_hidden);
// 处理活动
// load_relation($data, 'skuPrice'); // 只针对数组
$data->load('skuPrice'); // 延迟预加载
foreach ($data as $key => $g) {
// $skuPrice = [];
// foreach ($g['sku_price'] as $v) {
// if ($v['status'] === 'up') {
// $skuPrice[] = $v;
// }
// }
$data[$key] = self::operActivity($g, $g['sku_price']);
}
$goods->data = $data;
}
return $goods;
}
public static function getFavoriteGoodsList($type = 'normal', $status = 'up')
{
$where = [
'type' => $type,
'status' => $status,
'deletetime' => null,
];
$goods = self::where($where)->paginate(10);
if ($goods->items()) {
$collection = collection($goods->items());
$data = $collection->hidden(self::$list_hidden);
$goods->data = $data;
}
return $goods;
}
// 预约抢购商品列表
public static function getRobBuyGoodsList($params)
{
extract($params);
$type = $type ?? 'all';
if ((new self)->hasRedis()) {
// 如果有redis,读取 redis
$activityList = (new self)->getActivityList(['robbuy'], $type);
} else {
$where = [
'type' => 'robbuy'
];
if ($type == 'ing') {
$where['starttime'] = ['<', time()];
$where['endtime'] = ['>', time()];
} else if ($type == 'nostart') {
$where['starttime'] = ['>', time()];
} else if ($type == 'ended') {
$where['endtime'] = ['<', time()];
}
$activityList = Activity::where($where)->select();
}
// 获取所有商品 id
$goodsIds = '';
foreach ($activityList as $key => $activity) {
$goodsIds .= ',' . $activity['goods_ids'];
}
if ($goodsIds) {
$goodsIds = trim($goodsIds, ',');
}
$goodsList = self::getGoodsListByIds($goodsIds);
return $goodsList;
}
// 获取秒杀商品列表
public static function getSeckillGoodsList($params)
{
extract($params);
$type = $type ?? 'all';
if ((new self)->hasRedis()) {
// 如果有redis,读取 redis
$activityList = (new self)->getActivityList(['seckill'], $type);
} else {
$where = [
'type' => 'seckill'
];
if ($type == 'ing') {
$where['starttime'] = ['<', time()];
$where['endtime'] = ['>', time()];
} else if ($type == 'nostart') {
$where['starttime'] = ['>', time()];
} else if ($type == 'ended') {
$where['endtime'] = ['<', time()];
}
$activityList = Activity::where($where)->select();
}
// 获取所有商品 id
$goodsIds = '';
foreach ($activityList as $key => $activity) {
$goodsIds .= ',' . $activity['goods_ids'];
}
if ($goodsIds) {
$goodsIds = trim($goodsIds, ',');
}
$goodsList = self::getGoodsListByIds($goodsIds);
return $goodsList;
}
// 获取促销商品列表
public static function getPromoteGoodsList($params)
{
extract($params);
$type = $type ?? 'all';
if ((new self)->hasRedis()) {
// 如果有redis,读取 redis
$activityList = (new self)->getActivityList(['promote'], $type);
} else {
$where = [
'type' => 'seckill'
];
if ($type == 'ing') {
$where['starttime'] = ['<', time()];
$where['endtime'] = ['>', time()];
} else if ($type == 'nostart') {
$where['starttime'] = ['>', time()];
} else if ($type == 'ended') {
$where['endtime'] = ['<', time()];
}
$activityList = Activity::where($where)->select();
}
// 获取所有商品 id
$goodsIds = '';
foreach ($activityList as $key => $activity) {
if (!empty($params['platform']) && strpos($activity['show'], handleSource($params['platform'])) !== false) {
$goodsIds .= ',' . $activity['goods_ids'];
}
}
if ($goodsIds) {
$goodsIds = trim($goodsIds, ',');
}
$goodsList = self::getGoodsListByIds($goodsIds);
return $goodsList;
}
// 获取限时秒杀商品列表
public static function getTimerGoodsList($params)
{
extract($params);
$type = $type ?? 'all';
if ((new self)->hasRedis()) {
// 如果有redis,读取 redis
$activityList = (new self)->getActivityList(['timer'], $type);
} else {
$where = [
'type' => 'timer'
];
if ($type == 'ing') {
$where['starttime'] = ['<', time()];
$where['endtime'] = ['>', time()];
} else if ($type == 'nostart') {
$where['starttime'] = ['>', time()];
} else if ($type == 'ended') {
$where['endtime'] = ['<', time()];
}
$activityList = Activity::where($where)->select();
}
// 获取所有商品 id
$goodsIds = '';
foreach ($activityList as $key => $activity) {
$goodsIds .= ',' . $activity['goods_ids'];
}
if ($goodsIds) {
$goodsIds = trim($goodsIds, ',');
}
$goodsList = self::getGoodsListByIds($goodsIds);
return $goodsList;
}
// 获取拼团商品列表
public static function getGrouponGoodsList($params)
{
extract($params);
$type = 'ing';
if ((new self)->hasRedis()) {
// 如果有redis,读取 redis
$activityList = (new self)->getActivityList(['groupon'], $type);
} else {
$where = [
'type' => 'groupon'
];
if ($type == 'ing') {
$where['starttime'] = ['<', time()];
$where['endtime'] = ['>', time()];
}
$activityList = Activity::where($where)->select();
}
// 获取所有商品 id
$goodsIds = '';
foreach ($activityList as $key => $activity) {
if (!empty($params['platform']) && strpos($activity['show'], handleSource($params['platform'])) !== false) {
$goodsIds .= ',' . $activity['goods_ids'];
}
}
if ($goodsIds) {
$goodsIds = trim($goodsIds, ',');
}
$goodsList = self::getGoodsListByIds($goodsIds);
return $goodsList;
}
public static function getGoodsDetail($id, $source = '')
{
$user = User::info();
$detail = (new self)->where('id', $id)->with(['favorite' => function ($query) use ($user) {
$user_id = empty($user) ? 0 : $user->id;
return $query->where(['user_id' => $user_id]);
}])->find();
if (!$detail || $detail->status === 'down') {
new Exception('商品不存在或已下架');
}
$append = ['service', 'coupons'];
// 多规格商品才加载规格属性
if (!empty($detail->is_sku)) {
$append[] = 'sku';
}
$detail = $detail->append($append);
// 处理活动规格
$detail = self::operActivity($detail, $detail->sku_price, $source);
return $detail;
}
/**
* 获取自提点
*/
public static function getGoodsStore($params)
{
$user = User::info();
$id = $params['id'] ?? 0;
$keyword = $params['keyword'] ?? '';
$latitude = $params['latitude'] ?? 0;
$longitude = $params['longitude'] ?? 0;
$per_page = $params['per_page'] ?? 10;
$detail = (new self)->where('id', $id)->find();
if (!$detail) {
new Exception('商品不存在');
}
if (strpos($detail['dispatch_type'], 'selfetch') === false) {
new Exception('商品不支持自提');
}
// 商品支持自提,查询自提模板
$dispatch = Dispatch::where('type', 'selfetch')->where('id', 'in', $detail['dispatch_ids'])->find();
if (!$dispatch) {
new Exception('自提模板不存在');
}
$dispatchSelfetch = DispatchSelfetch::where('id', 'in', $dispatch['type_ids'])->order('id', 'asc')->find();
if (!$dispatchSelfetch) {
new Exception('自提模板不存在');
}
// 查询自提点
$selfetch = Store::show()->where('selfetch', 1);
if ($dispatchSelfetch['store_ids']) {
// 部分门店
$selfetch = $selfetch->where('id', 'in', $dispatchSelfetch['store_ids']);
}
if ($latitude && $longitude) {
$selfetch = $selfetch->field('*, ' . getDistanceBuilder($latitude, $longitude))->order('distance', 'asc');
}
if ($keyword) {
$selfetch = $selfetch->where('name', 'like', '%' . $keyword . '%');
}
$selfetch = $selfetch->paginate($per_page);
return $selfetch;
}
/**
* 获取商品购买人
*
* @param integer $goods_id
* @param integer $activity_id
* @return array
*/
protected static function getGoodsBuyers($goods_id = 0, $activity_id = 0)
{
$where = [
'goods_id' => $goods_id
];
if ($activity_id) {
// 是否查询指定活动的购买人
$where['activity_id'] = $activity_id;
}
// 查询活动正在购买的人Goods
$orderItems = OrderItem::with(['user' => function ($query) {
return $query->field('id,nickname,avatar');
}])->whereExists(function ($query) {
$order_table_name = (new Order())->getQuery()->getTable();
$table_name = (new OrderItem())->getQuery()->getTable();
$query->table($order_table_name)->where($table_name . '.order_id=' . $order_table_name . '.id')->where('status', '>', 0);
})->field('user_id')->where($where)->group('user_id')->limit(3)->select();
$user = [];
foreach ($orderItems as $item) {
if ($item['user']) {
$user[] = $item['user'];
}
}
return $user;
}
public function getCouponsAttr($value, $data)
{
$user = User::info();
$goods_id = $data['id'];
// 只查可以领取的
$where = [
'gettimestart' => ['elt', time()],
'gettimeend' => ['egt', time()]
];
$coupons = Coupons::where(function ($query) use ($goods_id) {
$query->where('find_in_set(' . $goods_id . ',goods_ids)')
->whereOr('goods_ids', 0);
});
if ($user) {
// 关联用户状态
$coupons = $coupons->with(['userCoupons' => function ($query) use ($user) {
$query->where('user_id', $user->id);
}]);
}
$coupons = $coupons->where($where)->select();
// 判断优惠券,当前用户领取状态
foreach ($coupons as &$coupon) {
if ($user && $coupon->limit <= count($coupon->user_coupons)) {
$coupon->status_code = 'cannot_get';
$coupon->status_name = '已领取';
} else {
$coupon->status_code = 'can_get';
$coupon->status_name = '可以领取';
}
}
return $coupons;
}
protected function getSkuAttr($value, $data)
{
$sku = GoodsSku::all([
'goods_id' => $data['id'],
'pid' => 0,
]);
foreach ($sku as $s => &$k) {
$sku[$s]['content'] = GoodsSku::all([
'goods_id' => $data['id'],
'pid' => $k['id']
]);
}
return $sku;
}
private static function getSkuPrice($value, $data)
{
return GoodsSkuPrice::all([
'goods_id' => $data['id'],
'status' => 'up',
'deletetime' => null
]);
}
public function getParamsAttr($value, $data)
{
return $value ? json_decode($value, true) : [];
}
public function getServiceAttr($value, $data)
{
$value = $data['service_ids'];
$serviceData = [];
if (!empty($value)) {
$serviceArray = explode(',', $value);
$serviceData = [];
foreach ($serviceArray as $v) {
$serviceData[] = \addons\shopro\model\GoodsService::where(['id' => $v, 'status' => 'normal'])->find();
}
}
return $serviceData;
}
public function getImageAttr($value, $data)
{
if (!empty($value)) return cdnurl($value, true);
}
public function getImagesAttr($value, $data)
{
$imagesArray = [];
if (!empty($value)) {
$imagesArray = explode(',', $value);
foreach ($imagesArray as &$v) {
$v = cdnurl($v, true);
}
return $imagesArray;
}
return $imagesArray;
}
public function getContentAttr($value, $data)
{
$content = $data['content'];
$content = str_replace("<img src=\"/uploads", "<img style=\"width: 100%;!important\" src=\"" . cdnurl("/uploads", true), $content);
$content = str_replace("<video src=\"/uploads", "<video style=\"width: 100%;!important\" src=\"" . cdnurl("/uploads", true), $content);
return $content;
}
public function getDispatchTypeArrAttr($value, $data)
{
return array_filter(explode(',', $data['dispatch_type']));
}
public function favorite()
{
return $this->hasOne(\addons\shopro\model\UserFavorite::class, 'goods_id', 'id');
}
public function scoreGoodsSkuPrice()
{
return $this->hasMany(\addons\shopro\model\scoreGoodsSkuPrice::class, 'goods_id', 'id')
->where('status', 'up')->order('id', 'asc');
}
public function skuPrice()
{
return $this->hasMany(\addons\shopro\model\GoodsSkuPrice::class, 'goods_id', 'id')
->order('id', 'asc');
}
//商品列表排序
private static function getGoodsListOrder($orderStr)
{
$order = 'weigh desc';
$orderList = json_decode(htmlspecialchars_decode($orderStr), true);
extract($orderList);
if (isset($defaultOrder) && $defaultOrder === 1) {
$order = 'weigh desc';
}
if (isset($priceOrder) && $priceOrder === 1) {
$order = "convert(`price`, DECIMAL(10, 2)) asc";
} elseif (isset($priceOrder) && $priceOrder === 2) {
$order = "convert(`price`, DECIMAL(10, 2)) desc";
}
if (isset($salesOrder) && $salesOrder === 1) {
$order = 'total_sales desc';
}
if (isset($newProdcutOrder) && $newProdcutOrder === 1) {
$order = 'id desc';
}
return $order;
}
}