StoreListForm.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <?php
  2. /**
  3. * 重庆赤晓店信息科技有限公司
  4. * https://www.chixiaodian.com
  5. * Copyright (c) 2023 赤店商城 All rights reserved.
  6. */
  7. namespace app\modules\client\models\v1;
  8. use app\constants\OptionSetting;
  9. use app\models\Banner;
  10. use app\models\District;
  11. use app\models\Goods;
  12. use app\models\Option;
  13. use app\models\Order;
  14. use app\models\SaasCategory;
  15. use app\models\Store;
  16. use app\plugins\food\models\FoodGoods;
  17. use app\plugins\food\models\FoodOrder;
  18. use yii\base\Model;
  19. use yii\helpers\Json;
  20. class StoreListForm extends Model
  21. {
  22. public $page = 1;
  23. public $limit = 12;
  24. // 经度
  25. public $longitude;
  26. // 纬度
  27. public $latitude;
  28. // 距离
  29. public $distance;
  30. // 0:默认排序 1:距离优先 2:好评优先 3:销量优先 4:低价优先 5:高价优先
  31. public $sort_type;
  32. // 搜索关键词
  33. public $keyword;
  34. // 分类id
  35. public $common_cat_id;
  36. // 城市id
  37. public $city_id;
  38. // 区县id
  39. public $district_id;
  40. // 筛选option信息
  41. public $option;
  42. public function rules()
  43. {
  44. return [
  45. [['longitude', 'latitude', 'keyword', 'option'], 'string'],
  46. [['longitude', 'latitude', 'keyword'], 'trim'],
  47. [['page', 'limit', 'common_cat_id', 'distance', 'sort_type', 'city_id', 'district_id'], 'integer'],
  48. [['page'], 'default', 'value' => 1],
  49. [['limit'], 'default', 'value' => 12],
  50. ['option', function ($attr, $params) {
  51. $data = Json::decode($this->option);
  52. if (!$data) {
  53. $this->addError($attr, "{$attr}数据格式错误。");
  54. }
  55. $this->option = $data;
  56. }],
  57. ['option', function ($attr, $params) {
  58. if (!is_array($this->option)) {
  59. $this->addError($attr, "{$attr}必须是一个数组。");
  60. return;
  61. }
  62. foreach ($this->option as $key => $opt) {
  63. if (!isset($opt['name']) || !isset($opt['value'])) {
  64. $this->addError($attr, "{$attr}[{$key}]['name']或{$attr}[{$key}]['value']不存在。");
  65. return;
  66. }
  67. }
  68. }],
  69. ];
  70. }
  71. public function list() {
  72. if (!$this->validate()) {
  73. return [
  74. 'code' => 1,
  75. 'msg' => $this->getErrorSummary(false)[0]
  76. ];
  77. }
  78. $query = Store::find()->where(['is_delete' => 0, 'is_alliance' => 1]);
  79. if ($this->keyword) {
  80. $query->andWhere(['like', 'name', $this->keyword]);
  81. }
  82. if ($this->common_cat_id > 0) {
  83. $query->andWhere(['like', 'category_id', $this->common_cat_id]);
  84. }
  85. if ($this->city_id) {
  86. $query->andWhere(['city_id' => $this->city_id]);
  87. }
  88. if ($this->district_id) {
  89. $query->andWhere(['district_id' => $this->district_id]);
  90. }
  91. if (is_array($this->option)) {
  92. foreach ($this->option as $choose) {
  93. $query->andWhere("JSON_CONTAINS(info,JSON_OBJECT('name',\"{$choose['name']}\"))")
  94. ->andWhere("JSON_CONTAINS(info, JSON_OBJECT('list',JSON_ARRAY(\"{$choose['value']}\")))");
  95. }
  96. }
  97. //供应链排除独立运行的店铺
  98. if (\Yii::$app->prod_is_dandianpu()) {
  99. $filter_store_id = Option::find()->where(['name' => 'self_mini', 'value' => 1])->select('store_id')->column();
  100. $query->andWhere(['NOT IN', 'id', $filter_store_id]);
  101. }
  102. $latitude = $this->latitude ?: 0;
  103. $longitude = $this->longitude ?: 0;
  104. $query->select(['id', 'name', 'logo', 'coordinate', 'created_at', 'category_id', 'tags', 'sales', 'rank', 'per_spend', 'address', 'distance' => "acos(cos({$latitude}*pi()/180 )*cos(latitude*pi()/180)*cos({$longitude}*pi()/180 -longitude*pi()/180)+sin({$latitude}*pi()/180 )*sin(latitude*pi()/180))*6370996.81"]);
  105. // 0:默认排序 1:距离优先 2:好评优先 3:销量优先 4:低价优先 5:高价优先
  106. if (empty($this->sort_type)) {
  107. // $list = $query->orderBy('created_at desc')->asArray()->all();
  108. $list = $query->orderBy('distance asc')->asArray()->all();
  109. }
  110. if ($this->sort_type == 2) {
  111. $list = $query->orderBy('rank desc')->asArray()->all();
  112. }
  113. if ($this->sort_type == 3) {
  114. $list = $query->orderBy('sales desc')->asArray()->all();
  115. }
  116. if ($this->sort_type == 4) {
  117. $list = $query->orderBy('per_spend asc')->asArray()->all();
  118. }
  119. if ($this->sort_type == 5) {
  120. $list = $query->orderBy('per_spend desc')->asArray()->all();
  121. }
  122. if (empty($list)) {
  123. $list = $query->asArray()->all();
  124. }
  125. $distance = array();
  126. if (!empty($this->sort_type)) {
  127. foreach ($list as $index => $item) {
  128. $list[$index]['distance'] = -1;
  129. if (!empty($item['coordinate'])) {
  130. list($lati, $long) = explode(',', $item['coordinate']);
  131. if ($long && $this->longitude) {
  132. $from = [$this->longitude, $this->latitude];
  133. $to = [$long, $lati];
  134. $list[$index]['distance'] = $this->get_distance($from, $to, false, 2);
  135. if ((($list[$index]['distance'] > $this->distance) && $this->distance > 0) || $list[$index]['distance'] == -1){
  136. unset($list[$index]);
  137. continue;
  138. }
  139. }
  140. }
  141. $distance[] = $list[$index]['distance'];
  142. // if ($list[$index]['distance'] == -1){
  143. // unset($list[$index]);
  144. // } else {
  145. // $distance[] = $list[$index]['distance'];
  146. // }
  147. }
  148. }
  149. if ($this->sort_type == 1) {
  150. $list = array_values($list);
  151. array_multisort($distance, SORT_ASC, $list);
  152. }
  153. $data = array_slice($list, ($this->page - 1) * $this->limit, $this->limit);
  154. foreach ($data as $index => $item) {
  155. $list[$index]['distance'] = -1;
  156. $data[$index]['goods_list'] = $this->goods_list($item);
  157. if ($this->distance($item['distance']) != -1) {
  158. $data[$index]['distance'] = '距离' . $this->distance($item['distance']);
  159. } else {
  160. $data[$index]['distance'] = '';
  161. }
  162. $data[$index]['tags'] = !empty($item['tags']) ? Json::decode($item['tags']) : [];
  163. }
  164. $res = [
  165. 'list' => $data,
  166. 'page_count' => ceil(count($list) / $this->limit),
  167. 'row_count' => count($list)
  168. ];
  169. return [
  170. 'code' => 0,
  171. 'msg' => 'success',
  172. 'data' => $res
  173. ];
  174. }
  175. /**
  176. * 获取当前定位信息
  177. */
  178. private function locationInfo() {
  179. $tencent_map_key = Option::get('tencent_map_key', 0, 'saas', '')['value'];
  180. if (get_store_id() > 0) {
  181. $tencent_map_key = Option::get(OptionSetting::TENCENT_MAP_KEY, get_store_id(), 'pay', Option::get(OptionSetting::TENCENT_MAP_KEY, get_store_id(), 'store', '')['value'] ?: $tencent_map_key)['value'];
  182. }
  183. $place_url = 'https://apis.map.qq.com/ws/geocoder/v1/?location=' . $this->latitude . ',' . $this->longitude . '&key=' . $tencent_map_key;
  184. $json_place = file_get_contents($place_url);
  185. $place_arr = Json::decode($json_place);
  186. if ($place_arr && isset($place_arr['result'])) {
  187. $address = $place_arr['result']['ad_info'];
  188. $city = District::find()->select('id, name')->where(['name' => $address['city']])->one();
  189. $district_list = District::find()->select('id, name')->where(['parent_id' => $city['id']])->asArray()->all();
  190. $data['city'] = $city;
  191. $data['district_list'] = $district_list;
  192. $data['address'] = $address;
  193. $data['detail'] = $place_arr['result']['formatted_addresses']['recommend'];
  194. return [
  195. 'code' => 0,
  196. 'msg' => '定位成功',
  197. 'data' => $data
  198. ];
  199. } else {
  200. return [
  201. 'code' => 1,
  202. 'msg' => '定位失败'
  203. ];
  204. }
  205. }
  206. public function config() {
  207. if (!$this->validate()) {
  208. return [
  209. 'code' => 1,
  210. 'msg' => $this->getErrorSummary(false)[0]
  211. ];
  212. }
  213. $location = $this->locationInfo();
  214. if ($location['code'] > 0) {
  215. return $location;
  216. }
  217. $data = [
  218. 'code' => 0,
  219. 'msg' => 'success',
  220. 'data' => [
  221. 'location' => $location['data']
  222. ]
  223. ];
  224. $saas_category = SaasCategory::find()->where(['is_delete' => 0])->orderBy('sort DESC')->asArray()->all();
  225. $category_data = [];
  226. foreach ($saas_category as $category) {
  227. $category_data[] = [
  228. 'cat_num' => Store::find()->where(['is_delete' => 0, 'category_id' => $category['id']])->count(),
  229. 'common_cat_id' => $category['id'],
  230. 'cat_name' => $category['name'],
  231. 'pic' => $category['icon'],
  232. 'config' => Json::decode($category['option'])
  233. ];
  234. }
  235. $data['data']['search'] = $category_data;
  236. $data['data']['banner'] = Banner::find()->andWhere(['is_delete' => 0, 'type' => Banner::TYPE_SAAS])->select('pic_url, title, page_url, open_type')->orderBy('sort ASC')->asArray()->all();
  237. return $data;
  238. }
  239. /**
  240. * 根据起点坐标和终点坐标测距离
  241. * @param [array] $from [起点坐标(经纬度),例如:array(118.012951,36.810024)]
  242. * @param [array] $to [终点坐标(经纬度)]
  243. * @param [bool] $km 是否以公里为单位 false:米 true:公里(千米)
  244. * @param [int] $decimal 精度 保留小数位数
  245. * @return [string] 距离数值
  246. */
  247. public function get_distance($from, $to, $km = true, $decimal = 2)
  248. {
  249. sort($from);
  250. sort($to);
  251. $EARTH_RADIUS = 6370.996; // 地球半径系数
  252. $distance = $EARTH_RADIUS * 2 * asin(sqrt(pow(sin(($from[0] * pi() / 180 - $to[0] * pi() / 180) / 2), 2) + cos($from[0] * pi() / 180) * cos($to[0] * pi() / 180) * pow(sin(($from[1] * pi() / 180 - $to[1] * pi() / 180) / 2), 2))) * 1000;
  253. if ($km) {
  254. $distance = $distance / 1000;
  255. }
  256. return round($distance, $decimal);
  257. }
  258. private static function distance($distance)
  259. {
  260. if ($distance == -1) {
  261. return -1;
  262. }
  263. if ($distance > 1000) {
  264. $distance = round($distance / 1000, 2) . 'km';
  265. } else {
  266. if ($distance <= 0) {
  267. $distance = '0m';
  268. } else {
  269. $distance .= 'm';
  270. }
  271. }
  272. return $distance;
  273. }
  274. private function goods_list($params){
  275. if ($params['category_id'] == 1) {
  276. $goods_list = FoodGoods::find()->where(['store_id' => $params['id'], 'is_delete' => 0])->select(['name', 'JSON_UNQUOTE(JSON_EXTRACT(cover_pic, \'$[0].url\')) as pic', 'price', 'original_price'])->orderBy('virtual_sales desc, sort desc')->limit(6)->asArray()->all();
  277. } else {
  278. $goods_list = Goods::find()->where(['store_id' => $params['id'], 'is_delete' => 0, 'status' => 1])->select(['name', 'price', 'original_price', 'cover_pic'])->orderBy('virtual_sales desc, sort desc')->limit(6)->asArray()->all();
  279. }
  280. return $goods_list;
  281. }
  282. public function sales() {
  283. // 上月第一天
  284. // $last_month_first_day = strtotime(date('Y-m-01') . ' -1 month');
  285. // 上月最后一天
  286. // $last_month_last_day = strtotime(date('Y-m-01') . ' -1 day');
  287. // 本月第一天
  288. $start_time = strtotime(date("Y-m-01"));
  289. // 当前时间
  290. $end_time = time();
  291. $stores = Store::findAll(['is_delete' => 0]);
  292. if ($stores) {
  293. foreach ($stores as $store) {
  294. $cache_key = 'cache_store_sales_num_' . $store->id;
  295. // 点餐
  296. if ($store->category_id == 1) {
  297. $count = FoodOrder::find()->where(['is_pay' => 1, 'store_id' => $store->id])->andWhere(['>=', 'created_at', $start_time])->andWhere(['<=', 'created_at', $end_time])->count();
  298. cache()->set($cache_key, $count);
  299. } else {
  300. $normal_count = Order::find()->where(['is_pay' => 1, 'store_id' => $store->id])->andWhere(['>=', 'created_at', $start_time])->andWhere(['<=', 'created_at', $end_time])->count();
  301. $scan_count = \app\plugins\scanCodePay\models\Order::find()->where(['is_pay' => 1, 'store_id' => $store->id])->andWhere(['>=', 'created_at', $start_time])->andWhere(['<=', 'created_at', $end_time])->count();
  302. cache()->set($cache_key, $normal_count + $scan_count);
  303. }
  304. // var_dump(cache()->get($cache_key));
  305. }
  306. }
  307. exit();
  308. }
  309. }