| | |
| | | ->order($sort) |
| | | ->paginate($param); |
| | | $product_model = new ProductModel(); |
| | | foreach ($list as $key => &$v) { |
| | | foreach ($list as &$v) { |
| | | $productList = $product_model->with(['image.file']) |
| | | ->where([ |
| | | 'shop_supplier_id' => $v['shop_supplier_id'], |
| | |
| | | } |
| | | |
| | | /** |
| | | * 获取团贯商户列表(用于移动端团购组件) |
| | | * 获取团购商户列表(用于移动端团购组件) |
| | | * @param array $param 查询参数 |
| | | * @return \think\Paginator |
| | | */ |
| | | public function getGroupBuyList($param) |
| | | { |
| | | // 获取用户经纬度(用于计算距离) |
| | | $user_latitude = isset($param['latitude']) ? floatval($param['latitude']) : 0; |
| | | $user_longitude = isset($param['longitude']) ? floatval($param['longitude']) : 0; |
| | | |
| | | // 排序规则 |
| | | $sort = []; |
| | | if (isset($param['sortType'])) { |
| | | if ($param['sortType'] === 'all') { |
| | | $sort = ['s.create_time' => 'desc']; |
| | | } else if ($param['sortType'] === 'sales') { |
| | | $sort = ['product_sales' => 'desc']; |
| | | } else if ($param['sortType'] === 'score') { |
| | | $sortType = isset($param['sortType']) ? $param['sortType'] : 'all'; |
| | | |
| | | // 初始化排序标志 |
| | | $needDistanceSort = false; |
| | | $needPriceSort = false; |
| | | $needDistanceFilter = false; |
| | | $needPriceFilter = false; |
| | | $needCouponFilter = false; |
| | | $needTopRankedFilter = false; |
| | | |
| | | if ($sortType === 'distance') { |
| | | // 使用子查询计算距离并排序 |
| | | $sort = ['avg_price' => 'asc']; // 临时字段,实际使用子查询 |
| | | $needDistanceSort = true; |
| | | } else if ($sortType === 'price_low' || $sortType === 'price_asc') { |
| | | // 使用子查询计算人均价格并升序排序 |
| | | $sort = ['avg_price' => 'asc']; |
| | | $needPriceSort = true; |
| | | $priceSortOrder = 'asc'; |
| | | } else if ($sortType === 'price_high' || $sortType === 'price_desc') { |
| | | // 使用子查询计算人均价格并降序排序 |
| | | $sort = ['avg_price' => 'desc']; |
| | | $needPriceSort = true; |
| | | $priceSortOrder = 'desc'; |
| | | } else if ($sortType === 'score') { |
| | | $sort = ['server_score' => 'desc']; |
| | | } |
| | | $needDistanceSort = false; |
| | | } else { |
| | | $sort = ['s.create_time' => 'desc']; |
| | | $needDistanceSort = false; |
| | | } |
| | | |
| | | $model = $this; |
| | | |
| | | // 分类筛选 |
| | |
| | | $model = $model->whereIn('shop_supplier_id', $supplier_ids); |
| | | } |
| | | |
| | | // 最高减价筛选(仅看有券) |
| | | if (isset($param['has_coupon']) && $param['has_coupon'] == 1) { |
| | | $needCouponFilter = true; |
| | | } |
| | | |
| | | // 好评榜筛选(前10名) |
| | | if (isset($param['top_ranked']) && $param['top_ranked'] == 1) { |
| | | $needTopRankedFilter = true; |
| | | } |
| | | |
| | | // 最低评分筛选 |
| | | $minScore = isset($param['min_score']) ? floatval($param['min_score']) : 0; |
| | | // 只在明确设置了评分限制时才进行筛选 |
| | | if (isset($param['min_score']) && $minScore > 0) { |
| | | $model = $model->where('server_score', '>=', $minScore); |
| | | } |
| | | |
| | | // 价格范围筛选 - 改为前置筛选,使用子查询 |
| | | $priceMin = isset($param['price_min']) ? floatval($param['price_min']) : 0; |
| | | $priceMax = isset($param['price_max']) ? floatval($param['price_max']) : -1; |
| | | // 只有当价格参数有效时才进行筛选 |
| | | if ((isset($param['price_min']) && $priceMin > 0) || (isset($param['price_max']) && $priceMax > 0)) { |
| | | $needPriceFilter = true; |
| | | } |
| | | |
| | | // 最大距离筛选 |
| | | $maxDistance = isset($param['max_distance']) ? floatval($param['max_distance']) : 0; |
| | | // 只在明确设置了距离限制时才进行筛选 |
| | | if (isset($param['max_distance']) && $maxDistance > 0) { |
| | | $needDistanceFilter = true; |
| | | } |
| | | |
| | | // 获取用户经纬度(用于计算距离) |
| | | $user_latitude = isset($param['latitude']) ? floatval($param['latitude']) : 0; |
| | | $user_longitude = isset($param['longitude']) ? floatval($param['longitude']) : 0; |
| | | |
| | | // 查询列表数据 |
| | | $list = $model->alias('s') |
| | | // 构建基础查询 |
| | | $baseQuery = $model->alias('s') |
| | | ->with(['logo', 'category']) |
| | | ->where('s.supplier_type',20) |
| | | ->where('s.is_delete', '=', '0') |
| | | ->where('s.is_recycle', '=', 0) |
| | | //->where('s.supplier_type', '=', 20) |
| | | ->where('s.is_recycle', '=', 0); |
| | | |
| | | // 添加评分排名子查询(用于好评榜筛选) |
| | | if ($needTopRankedFilter) { |
| | | // 先获取按评分排序的商户列表,获取前N名的ID |
| | | $rankSubQuery = $this->alias('s') |
| | | ->field('shop_supplier_id') |
| | | ->where('s.is_delete', '=', '0') |
| | | ->where('s.is_recycle', '=', 0); |
| | | |
| | | // 应用分类筛选 |
| | | if (isset($param['category_id']) && $param['category_id']) { |
| | | $rankSubQuery = $rankSubQuery->where('category_id', '=', $param['category_id']); |
| | | } |
| | | |
| | | // 应用城市商户筛选 |
| | | if (!empty($param['city_supplier_ids'])) { |
| | | $supplier_ids = is_array($param['city_supplier_ids']) ? $param['city_supplier_ids'] : explode(',', $param['city_supplier_ids']); |
| | | $rankSubQuery = $rankSubQuery->whereIn('shop_supplier_id', $supplier_ids); |
| | | } |
| | | |
| | | $rankSubQuery = $rankSubQuery |
| | | ->order(['server_score' => 'desc', 'product_sales' => 'desc', 'create_time' => 'asc']) |
| | | ->limit(10) // 好评榜前10 |
| | | ->buildSql(); |
| | | |
| | | // 在基础查询中添加对排名子查询的限制 |
| | | $baseQuery = $baseQuery->join([$rankSubQuery => 'rank_top'], 's.shop_supplier_id = rank_top.shop_supplier_id', 'INNER'); |
| | | } |
| | | |
| | | // 添加优惠券筛选子查询 |
| | | if ($needCouponFilter) { |
| | | // 检查是否存在全平台通用优惠券(shop_supplier_id = 0) |
| | | $platformCouponCount = \app\common\model\plus\coupon\Coupon::where('shop_supplier_id', '=', 0) |
| | | ->where('is_delete', '=', 0) |
| | | ->where(function ($query) { |
| | | // 领取后生效的优惠券 |
| | | $query->whereOr('expire_type', '=', 10); |
| | | // 或者固定时间且未过期的优惠券 |
| | | $query->whereOr(function ($q) { |
| | | $q->where('expire_type', '=', 20) |
| | | ->where('end_time', '>=', time() - 86400); |
| | | }); |
| | | }) |
| | | ->where(function ($query) { |
| | | // 数量不限制或者还有剩余 |
| | | $query->whereOr('total_num', '=', -1); |
| | | $query->whereOr(function ($q) { |
| | | $q->whereRaw('receive_num < total_num'); |
| | | }); |
| | | }) |
| | | ->count(); |
| | | |
| | | $hasPlatformCoupons = $platformCouponCount > 0; |
| | | |
| | | if ($hasPlatformCoupons) { |
| | | // 如果存在全平台通用优惠券,则所有商户都符合条件,不需要额外筛选 |
| | | // 保持baseQuery不变,所有商户都能通过筛选 |
| | | } else { |
| | | // 如果没有全平台通用优惠券,则只筛选有自己专属优惠券或参与其他商户优惠券活动的商户 |
| | | // 注意:商户可以参加其他商户发起的优惠券活动,因此需要更精确的逻辑 |
| | | |
| | | // 创建子查询获取所有有有效优惠券的商户(包括自己发起的和其他商户发起但本商户参与的) |
| | | $couponSubQuery = \app\common\model\plus\coupon\Coupon::alias('c') |
| | | ->field('DISTINCT shop_supplier_id') |
| | | ->where('c.is_delete', '=', 0) |
| | | ->where(function ($query) { |
| | | // 领取后生效的优惠券 |
| | | $query->whereOr('expire_type', '=', 10); |
| | | // 或者固定时间且未过期的优惠券 |
| | | $query->whereOr(function ($q) { |
| | | $q->where('expire_type', '=', 20) |
| | | ->where('end_time', '>=', time() - 86400); |
| | | }); |
| | | }) |
| | | ->where(function ($query) { |
| | | // 数量不限制或者还有剩余 |
| | | $query->whereOr('total_num', '=', -1); |
| | | $query->whereOr(function ($q) { |
| | | $q->whereRaw('receive_num < total_num'); |
| | | }); |
| | | }) |
| | | ->buildSql(); |
| | | |
| | | // 在基础查询中添加对优惠券子查询的限制 |
| | | $baseQuery = $baseQuery->join([$couponSubQuery => 'coupon_valid'], 's.shop_supplier_id = coupon_valid.shop_supplier_id', 'INNER'); |
| | | } |
| | | } |
| | | |
| | | // 查询列表数据 |
| | | if ($needPriceSort || $needPriceFilter) { |
| | | // 使用子查询计算人均价格并排序或筛选 |
| | | $subQuery = \app\common\model\order\Order::alias('o') |
| | | ->field('shop_supplier_id, AVG(pay_price) as avg_price') |
| | | ->where('pay_status', '=', 20) // 已支付 |
| | | ->where('order_status', '=', 30) // 已完成 |
| | | ->where('is_delete', '=', 0) |
| | | ->group('shop_supplier_id') |
| | | ->buildSql(); |
| | | |
| | | $listRaw = $baseQuery |
| | | ->join([$subQuery => 'avg_sub'], 's.shop_supplier_id = avg_sub.shop_supplier_id', 'LEFT') |
| | | ->field("s.shop_supplier_id,s.supplier_type,s.name as supplier_name,s.fav_count,logo_id,category_id,server_score,product_sales,address,link_phone,longitude,latitude,supplier_type, |
| | | COALESCE(avg_sub.avg_price, 0) as avg_price") |
| | | ->with(['logo', 'category']); |
| | | |
| | | // 添加价格范围筛选条件 |
| | | if ($needPriceFilter) { |
| | | if ($priceMin > 0) { |
| | | $listRaw = $listRaw->where('avg_sub.avg_price', '>=', $priceMin); |
| | | } |
| | | if ($priceMax > 0) { |
| | | $listRaw = $listRaw->where('avg_sub.avg_price', '<=', $priceMax); |
| | | } |
| | | } |
| | | |
| | | $listRaw = $listRaw->order('avg_sub.avg_price ' . ($priceSortOrder ?? 'desc') . ', s.create_time desc') |
| | | ->paginate($param); |
| | | } else if ($needDistanceSort) { |
| | | // 使用子查询计算距离并排序(需要数据库支持地理函数) |
| | | $listRaw = $baseQuery |
| | | ->field("s.shop_supplier_id,s.supplier_type,s.name as supplier_name,s.fav_count,logo_id,category_id,server_score,product_sales,address,link_phone,longitude,latitude,supplier_type, |
| | | IF(s.latitude > 0 AND s.longitude > 0, " . $user_latitude . ", 99999) as dist_sort") |
| | | ->with(['logo', 'category']) |
| | | ->order('dist_sort asc, s.create_time desc') |
| | | ->paginate($param); |
| | | } else { |
| | | // 普通查询 |
| | | $listRaw = $baseQuery |
| | | ->field("s.shop_supplier_id,s.supplier_type,s.name as supplier_name,s.fav_count,logo_id,category_id,server_score,product_sales,address,link_phone,longitude,latitude,supplier_type") |
| | | ->order($sort) |
| | | ->paginate($param); |
| | | } |
| | | |
| | | // 计算评分排名:先查询所有符合条件商户的评分 |
| | | $rankModel = $this; |
| | |
| | | $allSuppliers = $rankModel |
| | | ->where('is_delete', '=', '0') |
| | | ->where('is_recycle', '=', 0) |
| | | //->where('supplier_type', '=', 20) |
| | | ->where('supplier_type', '=', 20) |
| | | ->field('shop_supplier_id,server_score,product_sales') |
| | | ->order(['server_score' => 'desc', 'product_sales' => 'desc', 'create_time' => 'asc']) |
| | | ->select() |
| | |
| | | // 获取商品模型 |
| | | $product_model = new ProductModel(); |
| | | |
| | | // 将集合转为数组,避免间接修改警告 |
| | | $listData = $list->toArray(); |
| | | // 将集合转为数组 |
| | | $listData = $listRaw->toArray(); |
| | | |
| | | foreach ($listData['data'] as $key => &$v) { |
| | | foreach ($listData['data'] as &$v) { |
| | | // 获取商户的团购商品列表(最多3个) |
| | | $productList = $product_model->with(['image.file']) |
| | | ->where([ |
| | |
| | | ]; |
| | | } |
| | | |
| | | // 计算人均消费:根据商户历史订单计算 |
| | | $average_price = $this->calculateAverageConsumption($v['shop_supplier_id']); |
| | | // 使用子查询返回的人均价格(如果存在) |
| | | $average_price = isset($v['avg_price']) && $v['avg_price'] > 0 |
| | | ? round($v['avg_price'], 2) |
| | | : $this->calculateAverageConsumption($v['shop_supplier_id']); |
| | | |
| | | // 计算与用户的距离 |
| | | $distance = $this->calculateDistance( |
| | |
| | | $v['max_reduce_price'] = $max_reduce_price; // 最高减价,根据优惠券计算 |
| | | // 根据评分获取排名 |
| | | $v['ranking'] = isset($rankMap[$v['shop_supplier_id']]) ? $rankMap[$v['shop_supplier_id']] : 999; |
| | | // 存储距离的数值用于排序 |
| | | $v['distance_value'] = $this->parseDistanceValue($distance); |
| | | |
| | | unset($v['logo']); |
| | | unset($v['category']); |
| | | } |
| | | |
| | | // 后置筛选 - 现在只需要处理不需要子查询的筛选 |
| | | $filteredData = []; |
| | | foreach ($listData['data'] as $item) { |
| | | // 距离筛选 |
| | | if (isset($needDistanceFilter) && $needDistanceFilter === true) { |
| | | $distanceKm = $item['distance_value']; |
| | | if ($distanceKm > $maxDistance) continue; |
| | | } |
| | | |
| | | $filteredData[] = $item; |
| | | } |
| | | |
| | | $listData['data'] = $filteredData; |
| | | |
| | | // 距离排序仍然在PHP中处理(因为距离计算涉及复杂公式) |
| | | if ($needDistanceSort) { |
| | | usort($listData['data'], function($a, $b) { |
| | | return $a['distance_value'] <=> $b['distance_value']; |
| | | }); |
| | | // 重新分页 |
| | | $page = isset($param['page']) ? intval($param['page']) : 1; |
| | | $pageSize = isset($param['list_rows']) ? intval($param['list_rows']) : 10; |
| | | $offset = ($page - 1) * $pageSize; |
| | | $listData['total'] = count($listData['data']); |
| | | $listData['data'] = array_slice($listData['data'], $offset, $pageSize); |
| | | } |
| | | |
| | | // 更新分页数据 |
| | |
| | | } |
| | | |
| | | /** |
| | | * 解析距离字符串返回数值(用于排序) |
| | | * @param string $distanceStr 距离字符串(如:"1.2km" 或 "500m") |
| | | * @return float 距离数值(千米) |
| | | */ |
| | | private function parseDistanceValue($distanceStr) |
| | | { |
| | | if (empty($distanceStr) || $distanceStr === '--') { |
| | | return 99999; |
| | | } |
| | | |
| | | $value = 0; |
| | | if (strpos($distanceStr, 'km') !== false) { |
| | | $value = floatval(str_replace('km', '', $distanceStr)); |
| | | } else if (strpos($distanceStr, 'm') !== false) { |
| | | $value = floatval(str_replace('m', '', $distanceStr)) / 1000; |
| | | } |
| | | |
| | | return $value; |
| | | } |
| | | |
| | | /** |
| | | * 计算商户的最高减价(根据优惠券) |
| | | * @param int $shop_supplier_id 商户ID |
| | | * @return float 最高减价金额 |