From 2761218f2aed23d3b9b343ee4358d5ecbec7fe56 Mon Sep 17 00:00:00 2001 From: youda Date: Sat, 12 Apr 2025 06:27:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/Config.php | 5 + app/admin/controller/User.php | 2 +- app/admin/controller/UserRank.php | 81 ++++ app/admin/route/app.php | 8 +- app/admin/view/Config/systemconfig.html | 72 +++- app/admin/view/Finance/record.html | 2 +- app/admin/view/SignConfig/index.html | 5 +- app/admin/view/SignConfig/reward_edit.html | 3 +- app/admin/view/user_rank/index.html | 141 +++++++ app/api/controller/Index.php | 272 +------------ app/api/controller/Warehouse.php | 12 + app/common/helper/ConfigHelper.php | 138 +++++-- app/common/service/PaymentCalculator.php | 121 +++--- app/common/service/RankService.php | 445 +++++++++++++++++++++ config/menu.php | 4 + public/storage/poster/share/21546.png | Bin 0 -> 16953 bytes 16 files changed, 940 insertions(+), 371 deletions(-) create mode 100644 app/admin/controller/UserRank.php create mode 100644 app/admin/view/user_rank/index.html create mode 100644 app/common/service/RankService.php create mode 100644 public/storage/poster/share/21546.png diff --git a/app/admin/controller/Config.php b/app/admin/controller/Config.php index b89d23f..484e9e3 100755 --- a/app/admin/controller/Config.php +++ b/app/admin/controller/Config.php @@ -212,6 +212,11 @@ class Config extends Base //清除排行榜设置缓存 \app\common\helper\ConfigHelper::clearRankSettingsCache(); } + if ($data['key'] == 'app_setting') { + //清除redis缓存:app_setting + $redis = new RedisHelper(); + ($redis->getRedis())->del('config:app_setting'); + } $result = setConfig($data['key'], $data); if ($result) { diff --git a/app/admin/controller/User.php b/app/admin/controller/User.php index 8e5502a..e4d138f 100755 --- a/app/admin/controller/User.php +++ b/app/admin/controller/User.php @@ -538,7 +538,7 @@ class User extends Base $profit_loss_total = 0; $money = 0; foreach ($data['list'] as $k => &$v) { - #公式 (充值余额 + 吧唧币 + 微信支付 - 余额 - 背包赏品 + #公式 (充值余额 + 币 + 微信支付 - 余额 - 背包赏品 $cz_money = ProfitMoney::field('change_money')->where('user_id', '=', $v['id'])->where('type', '=', 1)->where('change_money', '>', 0)->where($where)->sum('change_money'); $wx_money = ProfitMoney::field('change_money')->where('user_id', '=', $v['id'])->where('type', '=', 2)->where('change_money', '>', 0)->where($where)->sum('change_money'); $cz_yue = $cz_money + $wx_money; diff --git a/app/admin/controller/UserRank.php b/app/admin/controller/UserRank.php new file mode 100644 index 0000000..b509466 --- /dev/null +++ b/app/admin/controller/UserRank.php @@ -0,0 +1,81 @@ +param('type', 'invite'); + + // 页码和每页显示条数 + $page = request()->param('page/d', 1); + $limit = request()->param('limit/d', 15); + + // 排序字段,主要用于亏损排行榜可按亏损金额或亏损率排序 + $sortField = request()->param('sort_field', 'loss_money'); + + // 获取排行榜服务类 + $rankService = new RankService(); + + // 获取排行榜数据 + try { + // 亏损排行榜需要额外传入排序字段 + if ($sortField != 'loss_money') { + $data = $rankService->getRankList('loss_desc', $page, $limit); + } else { + $data = $rankService->getRankList($type, $page, $limit); + } + + // 向模板传递变量 + View::assign('list', $data); + View::assign('type', $type); + View::assign('page', $page); + View::assign('limit', $limit); + View::assign('sort_field', $sortField); + + return View::fetch('user_rank/index'); + } catch (\Exception $e) { + // 异常处理 + return $this->renderError($e->getMessage()); + } + } + + /** + * 异步加载排行榜数据接口 + */ + public function getRankData() + { + // 获取参数 + $type = request()->param('type', 'invite'); + $page = request()->param('page/d', 1); + $limit = request()->param('limit/d', 15); + $sortField = request()->param('sort_field', 'loss_money'); + + // 获取排行榜服务类 + $rankService = new RankService(); + + try { + // 亏损排行榜需要额外传入排序字段 + if ($type == 'loss') { + $data = $rankService->getLossRank(0, 0, $page, $limit, $sortField); + } else { + $data = $rankService->getRankList($type, $page, $limit); + } + + return $this->renderSuccess('获取成功', $data); + } catch (\Exception $e) { + return $this->renderError($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/admin/route/app.php b/app/admin/route/app.php index 54360bb..7310630 100755 --- a/app/admin/route/app.php +++ b/app/admin/route/app.php @@ -396,4 +396,10 @@ Route::post('sign_config_reward_edit', 'SignConfig/rewardEdit'); Route::post('sign_config_delete', 'SignConfig/delete'); Route::post('sign_config_sort', 'SignConfig/sort'); Route::post('sign_config_status', 'SignConfig/status'); -Route::get('sign_config_coupons', 'SignConfig/getCoupons'); \ No newline at end of file +Route::get('sign_config_coupons', 'SignConfig/getCoupons'); + +#============================ +#UserRank.php用户排行榜 +#============================ +Route::rule('user_rank', 'UserRank/index', 'GET'); +Route::rule('user_rank_data', 'UserRank/getRankData', 'GET'); \ No newline at end of file diff --git a/app/admin/view/Config/systemconfig.html b/app/admin/view/Config/systemconfig.html index 48d36b1..1a9a01b 100755 --- a/app/admin/view/Config/systemconfig.html +++ b/app/admin/view/Config/systemconfig.html @@ -29,7 +29,7 @@
- +
每日免费送抽奖的ID,设置为0表示不启用
+
+ +
+ +
每天允许盒柜兑换的次数,0表示不限制次数
+
+
+
+ +
+ +
每天允许使用优惠券的次数,0表示不限制次数
+
+
@@ -395,6 +413,40 @@ class="layui-input" placeholder="不填则不限制结束时间">
+ + +
+ 亏损补贴排行榜 +
+
+ +
+ + + + + +
+
+
+ +
+ +
+ +
+ +
+
@@ -788,6 +840,16 @@ type: 'date' }); + laydate.render({ + elem: '#loss_start_time', + type: 'date' + }); + + laydate.render({ + elem: '#loss_end_time', + type: 'date' + }); + // 监听排行榜统计方式的选择变化 form.on('radio(dadajuan_stat_type)', function(data){ if(data.value === 'custom'){ @@ -821,6 +883,14 @@ } }); + form.on('radio(loss_stat_type)', function(data){ + if(data.value === 'custom'){ + $('.loss-time-range').show(); + } else { + $('.loss-time-range').hide(); + } + }); + // 复制并下载JSON格式按钮点击事件 $('#copy-download-json').on('click', function () { // 获取app-setting-form表单的所有值 diff --git a/app/admin/view/Finance/record.html b/app/admin/view/Finance/record.html index 7b43dc6..19c1d90 100755 --- a/app/admin/view/Finance/record.html +++ b/app/admin/view/Finance/record.html @@ -40,7 +40,7 @@ 总金额 微信支付 消费余额 - 吧唧币 + UU币 diff --git a/app/admin/view/SignConfig/index.html b/app/admin/view/SignConfig/index.html index caaeb42..b6a8225 100755 --- a/app/admin/view/SignConfig/index.html +++ b/app/admin/view/SignConfig/index.html @@ -62,13 +62,12 @@ {{# if(item.reward_type == 1){ }} 钻石: {{item.reward_value}} {{# } else if(item.reward_type == 2){ }} - 货币1: {{item.reward_value}} + UU币: {{item.reward_value}} {{# } else if(item.reward_type == 3){ }} - 货币2: {{item.reward_value}} + 达达卷: {{item.reward_value}} {{# } else if(item.reward_type == 4){ }} 优惠券 {{# } }} - {{# }); }} {{# } else { }} 无奖励 diff --git a/app/admin/view/SignConfig/reward_edit.html b/app/admin/view/SignConfig/reward_edit.html index 0908200..11dfdaa 100755 --- a/app/admin/view/SignConfig/reward_edit.html +++ b/app/admin/view/SignConfig/reward_edit.html @@ -12,8 +12,7 @@
- - +
diff --git a/app/admin/view/user_rank/index.html b/app/admin/view/user_rank/index.html new file mode 100644 index 0000000..2f4852f --- /dev/null +++ b/app/admin/view/user_rank/index.html @@ -0,0 +1,141 @@ +{include file="Public:header2"/} + + +
+
+
+
+
用户排行榜
+
+ +
+
+
+ + + + + +
+
+
+ + +
+
+ +
+ +
+
+
+ + + + + + + + + + + + {if $type=='loss'} + + + + {/if} + + + + {volist name="list" id="vo"} + + + + + + + + {if $type=='loss'} + + + + {/if} + + {/volist} + +
排名用户ID用户昵称头像 + {if $type=='invite'}邀请人数 + {elseif $type=='loss'}亏损金额 + {elseif $type=='dadajuan'}达达卷数量 + {elseif $type=='diamond'}钻石数量 + {elseif $type=='integral'}UU币数量 + {/if} + 消耗金额出货金额亏损率
{$vo.rank}{$vo.user_id}{$vo.nickname} + + {$vo.value} {$vo.unit}{$vo.consume_money} 元{$vo.output_money} 元{$vo.loss_rate}%
+ + +
+
+
+
+
+
+ + {include file="Public:footer"/} + + + + + \ No newline at end of file diff --git a/app/api/controller/Index.php b/app/api/controller/Index.php index 4bd09e1..121a978 100755 --- a/app/api/controller/Index.php +++ b/app/api/controller/Index.php @@ -228,7 +228,7 @@ class Index extends Base /** * 获取排行榜数据 - * 支持:diamond(钻石排行榜)、integral(UU币排行榜)、dadajuan(达达卷排行榜)、invite(邀请新人排行榜) + * 支持:diamond(钻石排行榜)、integral(UU币排行榜)、dadajuan(达达卷排行榜)、invite(邀请新人排行榜)、loss(亏损补贴排行榜) * * @return \think\response\Json */ @@ -238,280 +238,18 @@ class Index extends Base $type = request()->param('type', ''); // 验证排行榜类型是否有效 - $validTypes = ['diamond', 'integral', 'dadajuan', 'invite']; + $validTypes = ['diamond', 'integral', 'dadajuan', 'invite', 'loss']; if (!in_array($type, $validTypes)) { return $this->renderError('无效的排行榜类型'); } - - // 从配置助手获取排行榜时间设置 - $timeSettings = \app\common\helper\ConfigHelper::getRankTime($type); - $startTime = !empty($timeSettings['start_time']) ? strtotime($timeSettings['start_time']) : 0; - $endTime = !empty($timeSettings['end_time']) ? strtotime($timeSettings['end_time']) : time(); - // 设置分页参数 $page = request()->param('page/d', 1); $limit = request()->param('limit/d', 10); - - // 初始化返回数据 - $data = []; - - // 根据不同排行榜类型查询数据 - switch ($type) { - case 'diamond': // 钻石排行榜 - $data = $this->getDiamondRank($startTime, $endTime, $page, $limit); - break; - - case 'integral': // UU币排行榜 - $data = $this->getIntegralRank($startTime, $endTime, $page, $limit); - break; - - case 'dadajuan': // 达达卷排行榜 - $data = $this->getDadajuanRank($startTime, $endTime, $page, $limit); - break; - - case 'invite': // 邀请新人排行榜 - $data = $this->getInviteRank($startTime, $endTime, $page, $limit); - break; - } - + // 初始化排行榜服务类 + $rankService = new \app\common\service\RankService(); + $data = $rankService->getRankList($type, $page, $limit); // 返回数据 return $this->renderSuccess('请求成功', $data); } - /** - * 获取钻石排行榜数据 - * - * @param int $startTime 开始时间戳 - * @param int $endTime 结束时间戳 - * @param int $page 页码 - * @param int $limit 每页数量 - * @return array 排行榜数据 - */ - private function getDiamondRank($startTime, $endTime, $page, $limit) - { - // 构建查询条件 - $where = [ - ['status', '=', 1], - ['use_money', '>', 0], - [ - 'user_id', - 'not in', - function ($query) { - $query->name('user')->where('istest', '>', 0)->where('status', '=', 1)->field('id'); - } - ] - ]; - - // 添加时间范围条件 - if ($startTime > 0) { - $where[] = ['pay_time', '>=', $startTime]; - } - if ($endTime > 0) { - $where[] = ['pay_time', '<=', $endTime]; - } - - // 查询数据 - $list = Order::where($where) - ->field('user_id, sum(use_money) as use_money') - ->group('user_id') - ->order('use_money desc') - ->page($page, $limit) - ->select() - ->toArray(); - - // 处理用户信息 - $rankList = []; - foreach ($list as $index => $item) { - $userInfo = User::field('nickname, headimg, mobile')->where('id', $item['user_id'])->find(); - if ($userInfo) { - $rankList[] = [ - 'rank' => ($page - 1) * $limit + $index + 1, - 'user_id' => $item['user_id'], - 'nickname' => $userInfo['nickname'], - 'headimg' => imageUrl($userInfo['headimg']), - 'value' => $item['use_money'], - 'unit' => '钻石' - ]; - } - } - - return $rankList; - } - - /** - * 获取UU币排行榜数据 - * - * @param int $startTime 开始时间戳 - * @param int $endTime 结束时间戳 - * @param int $page 页码 - * @param int $limit 每页数量 - * @return array 排行榜数据 - */ - private function getIntegralRank($startTime, $endTime, $page, $limit) - { - // 构建查询条件 - $where = [ - ['status', '=', 1], - ['use_integral', '>', 0], - [ - 'user_id', - 'not in', - function ($query) { - $query->name('user')->where('istest', '>', 0)->where('status', '=', 1)->field('id'); - } - ] - ]; - - // 添加时间范围条件 - if ($startTime > 0) { - $where[] = ['pay_time', '>=', $startTime]; - } - if ($endTime > 0) { - $where[] = ['pay_time', '<=', $endTime]; - } - - // 查询数据 - $list = Order::where($where) - ->field('user_id, sum(use_integral) as use_money') - ->group('user_id') - ->order('use_money desc') - ->page($page, $limit) - ->select() - ->toArray(); - - // 处理用户信息 - $rankList = []; - foreach ($list as $index => $item) { - $userInfo = User::field('nickname, headimg, mobile')->where('id', $item['user_id'])->find(); - if ($userInfo) { - $rankList[] = [ - 'rank' => ($page - 1) * $limit + $index + 1, - 'user_id' => $item['user_id'], - 'nickname' => $userInfo['nickname'], - 'headimg' => imageUrl($userInfo['headimg']), - 'value' => $item['use_money'], - 'unit' => 'UU币' - ]; - } - } - - return $rankList; - } - - /** - * 获取达达卷排行榜数据 - * - * @param int $startTime 开始时间戳 - * @param int $endTime 结束时间戳 - * @param int $page 页码 - * @param int $limit 每页数量 - * @return array 排行榜数据 - */ - private function getDadajuanRank($startTime, $endTime, $page, $limit) - { - // 构建查询条件 - $where = [ - ['status', '=', 1], - ['use_money2', '>', 0], - [ - 'user_id', - 'not in', - function ($query) { - $query->name('user')->where('istest', '>', 0)->where('status', '=', 1)->field('id'); - } - ] - ]; - - // 添加时间范围条件 - if ($startTime > 0) { - $where[] = ['pay_time', '>=', $startTime]; - } - if ($endTime > 0) { - $where[] = ['pay_time', '<=', $endTime]; - } - - // 查询数据 - $list = Order::where($where) - ->field('user_id, sum(use_money2) as use_money') - ->group('user_id') - ->order('use_money desc') - ->page($page, $limit) - ->select() - ->toArray(); - - // 处理用户信息 - $rankList = []; - foreach ($list as $index => $item) { - $userInfo = User::field('nickname, headimg, mobile')->where('id', $item['user_id'])->find(); - if ($userInfo) { - $rankList[] = [ - 'rank' => ($page - 1) * $limit + $index + 1, - 'user_id' => $item['user_id'], - 'nickname' => $userInfo['nickname'], - 'headimg' => imageUrl($userInfo['headimg']), - 'value' => $item['use_money'], - 'unit' => '达达卷' - ]; - } - } - - return $rankList; - } - - /** - * 获取邀请新人排行榜数据 - * - * @param int $startTime 开始时间戳 - * @param int $endTime 结束时间戳 - * @param int $page 页码 - * @param int $limit 每页数量 - * @return array 排行榜数据 - */ - private function getInviteRank($startTime, $endTime, $page, $limit) - { - // 构建查询条件 - $where = [ - ['pid', '>', 0], - ['istest', '=', 0], // 排除测试用户 - ['status', '=', 1] // 只查询状态正常的用户 - ]; - - // 添加时间范围条件 - if ($startTime > 0) { - $where[] = ['addtime', '>=', $startTime]; - } - if ($endTime > 0) { - $where[] = ['addtime', '<=', $endTime]; - } - - // 查询数据 - $list = User::where($where) - ->field('pid, COUNT(1) as invite_count') - ->group('pid') - ->having('invite_count > 0') - ->order('invite_count desc') - ->page($page, $limit) - ->select() - ->toArray(); - - // 处理用户信息 - $rankList = []; - foreach ($list as $index => $item) { - $userInfo = User::field('nickname, headimg, mobile')->where('id', $item['pid'])->find(); - if ($userInfo) { - $rankList[] = [ - 'rank' => ($page - 1) * $limit + $index + 1, - 'user_id' => $item['pid'], - 'nickname' => $userInfo['nickname'], - 'headimg' => imageUrl($userInfo['headimg']), - 'value' => $item['invite_count'], - 'unit' => '人' - ]; - } - } - - return $rankList; - } - - } diff --git a/app/api/controller/Warehouse.php b/app/api/controller/Warehouse.php index f8b3b5f..a135eb2 100755 --- a/app/api/controller/Warehouse.php +++ b/app/api/controller/Warehouse.php @@ -582,6 +582,18 @@ class Warehouse extends Base if (empty($recovery_info)) { return $this->renderError("请选择兑换的赏品"); } + $cabinet_exchange_limit = \app\common\helper\ConfigHelper::getAppSettingKey("cabinet_exchange_limit"); + if($cabinet_exchange_limit>0){ + $today_start = strtotime(date('Y-m-d 00:00:00', time())); + $today_end = strtotime(date('Y-m-d 23:59:59', time())); + $today_count = OrderListRecovery::where('user_id', '=', $user_id) + ->where('addtime', '>=', $today_start) + ->where('addtime', '<=', $today_end) + ->count(); + if($today_count>=$cabinet_exchange_limit){ + return $this->renderError("今日兑换次数已达上限"); + } + } #装换结构 $recovery_info = json_decode($recovery_info, true); $order_list_id = []; diff --git a/app/common/helper/ConfigHelper.php b/app/common/helper/ConfigHelper.php index b7e2faa..213bf35 100755 --- a/app/common/helper/ConfigHelper.php +++ b/app/common/helper/ConfigHelper.php @@ -18,14 +18,54 @@ class ConfigHelper * @var int|null */ private static $infiniteMultiple = null; - + /** * 静态属性,用于存储排行榜设置 * * @var array|null */ private static $rankSettings = null; - + + private static $appSetting = null; + + /** + * 获取应用设置 + * + * @return array 应用设置 + */ + public static function getAppSetting() + { + if (self::$appSetting !== null) { + return self::$appSetting; + } + $redis = new RedisHelper(); + $cachedValue = $redis->get('config:app_setting'); + if ($cachedValue !== false) { + self::$appSetting = json_decode($cachedValue, true); + return self::$appSetting; + } + $app_setting = getConfig('app_setting'); + if ($app_setting) { + self::$appSetting = $app_setting; + $redis->set('config:app_setting', json_encode($app_setting), 86400); + return self::$appSetting; + } + return []; + } + /** + * 获取应用设置的key值 + * + * @param string $key 配置键名 + * @return mixed 配置值 + */ + public static function getAppSettingKey($key) + { + $appSetting = self::getAppSetting(); + return $appSetting[$key] ?? null; + } + + + /** * 获取无限赏抽奖倍数 * 从数据库中查询key为infinite_multiple的配置,获取multiple字段 @@ -33,47 +73,47 @@ class ConfigHelper * * @return int 抽奖倍数,默认为1000 */ - public static function getInfiniteMultiple() - { + public static function getInfiniteMultiple( + ) { // 如果静态属性已有值,直接返回 if (self::$infiniteMultiple !== null) { return self::$infiniteMultiple; } - + // 实例化Redis助手 $redis = new RedisHelper(); - + // 设置Redis键名 $redisKey = 'config:infinite_multiple'; - + // 尝试从Redis获取 $cachedValue = $redis->get($redisKey); if ($cachedValue !== false) { // 缓存结果到静态属性 - self::$infiniteMultiple = (int)$cachedValue; + self::$infiniteMultiple = (int) $cachedValue; return self::$infiniteMultiple; } - + // Redis中不存在,从数据库获取 $config = Db::name('config') ->where('key', 'infinite_multiple') ->value('value'); - + // 解析JSON数据 $configArray = json_decode($config, true); - + // 获取倍数值,默认为10000 - $multiple = isset($configArray['multiple']) ? (int)$configArray['multiple'] : 10000; - + $multiple = isset($configArray['multiple']) ? (int) $configArray['multiple'] : 10000; + // 存入Redis,过期时间为1天(86400秒) $redis->set($redisKey, $multiple, 86400); - + // 缓存结果到静态属性 self::$infiniteMultiple = $multiple; - + return self::$infiniteMultiple; } - + /** * 获取排行榜设置 * 从数据库中查询key为rank_setting的配置 @@ -86,13 +126,13 @@ class ConfigHelper if (self::$rankSettings !== null) { return self::$rankSettings; } - + // 实例化Redis助手 $redis = new RedisHelper(); - + // 设置Redis键名 $redisKey = 'config:rank_settings'; - + // 尝试从Redis获取 $cachedValue = $redis->get($redisKey); if ($cachedValue !== false) { @@ -100,24 +140,24 @@ class ConfigHelper self::$rankSettings = json_decode($cachedValue, true); return self::$rankSettings; } - + // Redis中不存在,从数据库获取 $config = Db::name('config') ->where('key', 'rank_setting') ->value('value'); - + // 解析JSON数据 $configArray = json_decode($config, true) ?: []; - + // 存入Redis,过期时间为1小时(3600秒) $redis->set($redisKey, json_encode($configArray), 3600); - + // 缓存结果到静态属性 self::$rankSettings = $configArray; - + return self::$rankSettings; } - + /** * 获取特定排行榜的时间设置 * @@ -128,12 +168,12 @@ class ConfigHelper { // 获取所有排行榜设置 $settings = self::getRankSettings(); - + // 根据类型设置默认的键名 $statTypeKey = ''; $startKey = ''; $endKey = ''; - + switch ($type) { case 'dadajuan': $statTypeKey = 'dadajuan_stat_type'; @@ -155,6 +195,16 @@ class ConfigHelper $startKey = 'invite_start_time'; $endKey = 'invite_end_time'; break; + case 'loss': + $statTypeKey = 'loss_stat_type'; + $startKey = 'loss_start_time'; + $endKey = 'loss_end_time'; + break; + case 'loss_desc': + $statTypeKey = 'loss_stat_type'; + $startKey = 'loss_start_time'; + $endKey = 'loss_end_time'; + break; default: return [ 'stat_type' => 'daily', @@ -162,47 +212,47 @@ class ConfigHelper 'end_time' => '' ]; } - + // 获取统计方式,默认为每天统计 $statType = isset($settings[$statTypeKey]) ? $settings[$statTypeKey] : 'daily'; - + // 根据统计方式计算时间范围 $startTime = ''; $endTime = ''; - + // 当前时间 $now = time(); - + switch ($statType) { case 'daily': // 每天统计:当天0点到第二天0点 $startTime = date('Y-m-d 00:00:00', $now); $endTime = date('Y-m-d 00:00:00', strtotime('+1 day', $now)); break; - + case 'weekly': // 每周统计:本周一0点到下周一0点 $weekStart = strtotime('this week monday', $now); $startTime = date('Y-m-d 00:00:00', $weekStart); $endTime = date('Y-m-d 00:00:00', strtotime('+1 week', $weekStart)); break; - + case 'monthly': // 每月统计:本月1号0点到下月1号0点 $startTime = date('Y-m-01 00:00:00', $now); $endTime = date('Y-m-01 00:00:00', strtotime('+1 month', $now)); break; - + case 'yearly': // 每年统计:本年1月1号0点到下年1月1号0点 $startTime = date('Y-01-01 00:00:00', $now); $endTime = date('Y-01-01 00:00:00', strtotime('+1 year', $now)); break; - + case 'custom': // 自定义时间范围:使用设置的时间 - $startTime = isset($settings[$startKey]) && !empty($settings[$startKey]) ? - $settings[$startKey] : ''; - $endTime = isset($settings[$endKey]) && !empty($settings[$endKey]) ? - $settings[$endKey] : ''; + $startTime = isset($settings[$startKey]) && !empty($settings[$startKey]) ? + $settings[$startKey] : ''; + $endTime = isset($settings[$endKey]) && !empty($settings[$endKey]) ? + $settings[$endKey] : ''; break; } - + // 返回排行榜配置 return [ 'stat_type' => $statType, @@ -210,7 +260,7 @@ class ConfigHelper 'end_time' => $endTime ]; } - + /** * 清除排行榜设置的Redis缓存 * 在排行榜设置更新时调用,确保获取最新数据 @@ -221,12 +271,12 @@ class ConfigHelper { // 重置静态属性 self::$rankSettings = null; - + // 清除Redis缓存 $redis = new RedisHelper(); $redisKey = 'config:rank_settings'; - + // 删除缓存,返回是否成功 return $redis->delete($redisKey); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/app/common/service/PaymentCalculator.php b/app/common/service/PaymentCalculator.php index c035354..f7c0854 100644 --- a/app/common/service/PaymentCalculator.php +++ b/app/common/service/PaymentCalculator.php @@ -34,19 +34,19 @@ class PaymentCalculator $goods = GoodsModel::field('id,title,imgurl_detail,type,price,status,is_shou_zhe,quanju_xiangou,daily_xiangou,choujiang_xianzhi') ->where(['id' => $goods_id]) ->find(); - + if (!$goods) { return ['status' => 0, 'msg' => '盒子不存在']; } - + if ($goods['status'] != 1) { return ['status' => 0, 'msg' => '盒子已下架']; } } - + // 获取盒子类型 $goods_type = $goods['type']; - + // 如果未传入扩展信息,则从数据库获取 if (!$goodsExtend) { $goodsExtend = GoodsExtend::getGoodsExtendByGoodsId($goods_id, $goods_type); @@ -54,13 +54,13 @@ class PaymentCalculator return ['status' => 0, 'msg' => '盒子类型配置不存在']; } } - + // 处理图片URL $goods['imgurl_detail'] = imageUrl($goods['imgurl_detail']); - + // 盒子单价 $box_price = $goods['price']; - + // 首抽五折处理 $shou_zhe_price = 0; if ($goods['type'] != 5 && $goods_type != 10 && $goods_type != 15) { @@ -70,36 +70,54 @@ class PaymentCalculator $shou_zhe_price = bcmul("$box_price", "0.5", 2); } } - + // 计算总价 $goods['shou_zhe_price'] = $shou_zhe_price; $price = bcmul("$box_price", "$prize_num", 2); $price = bcsub("$price", "$shou_zhe_price", 2); - + // 订单金额 $order_total = $order_zhe_total = $price; - + // 初始化变量 $coupon_price = 0; $use_money = 0; // 余额抵扣 $use_integral = 0; // 货币1抵扣 $use_money2 = 0; // 货币2抵扣 $zhe = 0; // 会员折扣 - + $daily_coupon_limit = \app\common\helper\ConfigHelper::getAppSettingKey("daily_coupon_limit"); // 优惠券处理 if ($shou_zhe_price <= 0 && !empty($coupon_id) && $goodsExtend['pay_coupon'] == 1) { - // 获取优惠券信息 - $coupon = CouponReceiveModel::where([ - 'id' => $coupon_id, - 'status' => 0, - 'user_id' => $user['id'] - ])->where('man_price', '<=', $price) - ->where('end_time', '>', time()) // 确保优惠券未过期 - ->find(); + //是否限制每日优惠券次数 + $is_daily_coupon = true; + if ($daily_coupon_limit > 0) { + $today_start = strtotime(date('Y-m-d 00:00:00', time())); + $today_end = strtotime(date('Y-m-d 23:59:59', time())); + $today_count = CouponReceiveModel::where('user_id', '=', $user['id']) + ->where('addtime', '>=', $today_start) + ->where('addtime', '<=', $today_end) + ->count(); + if ($today_count >= $daily_coupon_limit) { + // return ['status' => 0, 'msg' => '今日优惠券次数已达上限']; + $is_daily_coupon = false; + } + } + if ($is_daily_coupon) { + // 获取优惠券信息 + $coupon = CouponReceiveModel::where([ + 'id' => $coupon_id, + 'status' => 0, + 'user_id' => $user['id'] + ])->where('man_price', '<=', $price) + ->where('end_time', '>', time()) // 确保优惠券未过期 + ->find(); - if ($coupon) { - $coupon_price = $coupon['price']; - } else { + if ($coupon) { + $coupon_price = $coupon['price']; + } else { + $coupon_id = 0; + } + }else{ $coupon_id = 0; } } else { @@ -111,11 +129,11 @@ class PaymentCalculator $price = 0; } $order_zhe_total = $price; - + // 如果不是首抽五折,处理各种支付抵扣 if ($shou_zhe_price <= 0) { $iszhifu = 0; - + // 余额抵扣 if ($use_money_is == 1 && $goodsExtend['pay_balance'] == 1) { if ($goodsExtend['is_deduction'] == 1) { @@ -140,7 +158,7 @@ class PaymentCalculator } } } - + // 货币1抵扣 if ($use_integral_is == 1 && $goodsExtend['pay_currency'] == 1) { $price_in_currency = $price * 100; // 1:100比例 @@ -166,7 +184,7 @@ class PaymentCalculator } } } - + // 货币2抵扣 if ($use_money2_is == 1 && $goodsExtend['pay_currency2'] == 1) { $price_in_currency2 = $price * 100; // 1:100比例 @@ -192,16 +210,16 @@ class PaymentCalculator } } } - + // 如果是支付模式但未选择任何支付方式 if ($goodsExtend['is_deduction'] == 0 && $iszhifu == 0 && $goodsExtend['pay_wechat'] == 0) { return ['status' => 0, 'msg' => '请选择支付方式']; } } - + // 设置抽奖数量 $goods['prize_num'] = $prize_num; - + // 组装返回数据 $data = [ 'status' => 1, @@ -220,15 +238,16 @@ class PaymentCalculator 'coupon_id' => $coupon_id, 'coupon_price' => round($coupon_price, 2), 'goods_extend' => $goodsExtend, - 'use_money2' => $use_money2 + 'use_money2' => $use_money2, + 'daily_coupon_limit' => $daily_coupon_limit ]; - + // 添加首抽五折状态 $data['is_shou_zhe'] = $shou_zhe_price > 0 ? 1 : 0; - + return $data; } - + /** * 验证抽奖限制 * @@ -242,7 +261,7 @@ class PaymentCalculator // 验证抽奖门槛 $user_id = $user['id']; $choujiang_xianzhi = $goods['choujiang_xianzhi']; - + if ($choujiang_xianzhi && $choujiang_xianzhi > 0) { // 验证福利屋活动 if ($goods['type'] == 15) { @@ -252,34 +271,34 @@ class PaymentCalculator $goods['flw_start_time'], $goods['flw_end_time'] ); - + if ($consumptionData['total_consumed'] < $choujiang_xianzhi) { return [ - 'status' => 0, + 'status' => 0, 'msg' => "需在指定时间" . date('Y-m-d H:i:s', $goods['flw_start_time']) . "-" - . date('Y-m-d H:i:s', $goods['flw_end_time']) . "消耗达到" . $choujiang_xianzhi - . "钻石,即可加入房间,还需" . round(($choujiang_xianzhi - $consumptionData['total_consumed']), 2) . "钻石." + . date('Y-m-d H:i:s', $goods['flw_end_time']) . "消耗达到" . $choujiang_xianzhi + . "钻石,即可加入房间,还需" . round(($choujiang_xianzhi - $consumptionData['total_consumed']), 2) . "钻石." ]; } } else { // 常规消费验证 $user_price = Order::where('user_id', '=', $user_id)->where('status', '=', 1)->sum('price'); - + if ($user_price < $choujiang_xianzhi) { if ($user['istest'] > 0) { $user_price = Order::where('user_id', '=', $user_id)->where('status', '=', 1)->sum('order_zhe_total'); } - + if ($user_price < $choujiang_xianzhi) { return [ - 'status' => 0, + 'status' => 0, 'msg' => "消费满" . $choujiang_xianzhi . "元可参与 已消费" . round($user_price, 2) . "元" ]; } } } } - + // 验证全局限购 if ($goods['quanju_xiangou'] > 0) { $user_quanju_count = \app\common\model\OrderList::field('id') @@ -287,20 +306,20 @@ class PaymentCalculator ->where('user_id', '=', $user_id) ->where('parent_goods_list_id', '=', 0) ->count(); - + if ($user_quanju_count >= $goods['quanju_xiangou']) { return ['status' => 0, 'msg' => '当前限购' . $goods['quanju_xiangou'] . '次']; } - + $now_prize_num = $prize_num + $user_quanju_count; if ($now_prize_num > $goods['quanju_xiangou']) { return [ - 'status' => 0, + 'status' => 0, 'msg' => '购买超出限制,还允许购买' . ($goods['quanju_xiangou'] - $user_quanju_count) . '次' ]; } } - + // 验证每日限购 if ($goods['daily_xiangou'] > 0) { $todayMidnight = strtotime('today'); @@ -310,20 +329,20 @@ class PaymentCalculator ->where('parent_goods_list_id', '=', 0) ->where('addtime', '>=', $todayMidnight) ->count(); - + if ($user_toDay_count >= $goods['daily_xiangou']) { return ['status' => 0, 'msg' => '今日限购' . $goods['daily_xiangou'] . '次']; } - + $now_prize_num = $prize_num + $user_toDay_count; if ($now_prize_num > $goods['daily_xiangou']) { return [ - 'status' => 0, + 'status' => 0, 'msg' => '购买超出限制,今日还允许购买' . ($goods['daily_xiangou'] - $user_toDay_count) . '次' ]; } } - + return ['status' => 1, 'msg' => '验证通过']; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/app/common/service/RankService.php b/app/common/service/RankService.php new file mode 100644 index 0000000..9e4e0e5 --- /dev/null +++ b/app/common/service/RankService.php @@ -0,0 +1,445 @@ +getDiamondRank($startTime, $endTime, $page, $limit); + break; + + case 'integral': // UU币排行榜 + $data = $rankService->getIntegralRank($startTime, $endTime, $page, $limit); + break; + + case 'dadajuan': // 达达卷排行榜 + $data = $rankService->getDadajuanRank($startTime, $endTime, $page, $limit); + break; + + case 'invite': // 邀请新人排行榜 + $data = $rankService->getInviteRank($startTime, $endTime, $page, $limit); + break; + + case 'loss': // 亏损补贴排行榜 + $data = $rankService->getLossRank($startTime, $endTime, $page, $limit); + break; + case 'loss_desc'://亏损率排行榜 + $data = $rankService->getLossRank($startTime, $endTime, $page, $limit,'loss_rate'); + break; + } + + // 返回数据 + return $data; + } + +/** + * 获取钻石排行榜数据 + * + * @param int $startTime 开始时间戳 + * @param int $endTime 结束时间戳 + * @param int $page 页码 + * @param int $limit 每页数量 + * @return array 排行榜数据 + */ + public function getDiamondRank($startTime, $endTime, $page, $limit) + { + // 构建查询条件 + $where = [ + ['status', '=', 1], + ['use_money', '>', 0], + [ + 'user_id', + 'not in', + function ($query) { + $query->name('user')->where('istest', '>', 0)->where('status', '=', 1)->field('id'); + } + ] + ]; + + // 添加时间范围条件 + if ($startTime > 0) { + $where[] = ['pay_time', '>=', $startTime]; + } + if ($endTime > 0) { + $where[] = ['pay_time', '<=', $endTime]; + } + + // 查询数据 + $list = Order::where($where) + ->field('user_id, sum(use_money) as use_money') + ->group('user_id') + ->order('use_money desc') + ->page($page, $limit) + ->select() + ->toArray(); + + // 处理用户信息 + $rankList = []; + foreach ($list as $index => $item) { + $userInfo = User::field('nickname, headimg, mobile')->where('id', $item['user_id'])->find(); + if ($userInfo) { + $rankList[] = [ + 'rank' => ($page - 1) * $limit + $index + 1, + 'user_id' => $item['user_id'], + 'nickname' => $userInfo['nickname'], + 'headimg' => imageUrl($userInfo['headimg']), + 'value' => $item['use_money'], + 'unit' => '钻石' + ]; + } + } + + return $rankList; + } + + /** + * 获取UU币排行榜数据 + * + * @param int $startTime 开始时间戳 + * @param int $endTime 结束时间戳 + * @param int $page 页码 + * @param int $limit 每页数量 + * @return array 排行榜数据 + */ + public function getIntegralRank($startTime, $endTime, $page, $limit) + { + // 构建查询条件 + $where = [ + ['status', '=', 1], + ['use_integral', '>', 0], + [ + 'user_id', + 'not in', + function ($query) { + $query->name('user')->where('istest', '>', 0)->where('status', '=', 1)->field('id'); + } + ] + ]; + + // 添加时间范围条件 + if ($startTime > 0) { + $where[] = ['pay_time', '>=', $startTime]; + } + if ($endTime > 0) { + $where[] = ['pay_time', '<=', $endTime]; + } + + // 查询数据 + $list = Order::where($where) + ->field('user_id, sum(use_integral) as use_money') + ->group('user_id') + ->order('use_money desc') + ->page($page, $limit) + ->select() + ->toArray(); + + // 处理用户信息 + $rankList = []; + foreach ($list as $index => $item) { + $userInfo = User::field('nickname, headimg, mobile')->where('id', $item['user_id'])->find(); + if ($userInfo) { + $rankList[] = [ + 'rank' => ($page - 1) * $limit + $index + 1, + 'user_id' => $item['user_id'], + 'nickname' => $userInfo['nickname'], + 'headimg' => imageUrl($userInfo['headimg']), + 'value' => $item['use_money'], + 'unit' => 'UU币' + ]; + } + } + + return $rankList; + } + + /** + * 获取达达卷排行榜数据 + * + * @param int $startTime 开始时间戳 + * @param int $endTime 结束时间戳 + * @param int $page 页码 + * @param int $limit 每页数量 + * @return array 排行榜数据 + */ + public function getDadajuanRank($startTime, $endTime, $page, $limit) + { + // 构建查询条件 + $where = [ + ['status', '=', 1], + ['use_money2', '>', 0], + [ + 'user_id', + 'not in', + function ($query) { + $query->name('user')->where('istest', '>', 0)->where('status', '=', 1)->field('id'); + } + ] + ]; + + // 添加时间范围条件 + if ($startTime > 0) { + $where[] = ['pay_time', '>=', $startTime]; + } + if ($endTime > 0) { + $where[] = ['pay_time', '<=', $endTime]; + } + + // 查询数据 + $list = Order::where($where) + ->field('user_id, sum(use_money2) as use_money') + ->group('user_id') + ->order('use_money desc') + ->page($page, $limit) + ->select() + ->toArray(); + + // 处理用户信息 + $rankList = []; + foreach ($list as $index => $item) { + $userInfo = User::field('nickname, headimg, mobile')->where('id', $item['user_id'])->find(); + if ($userInfo) { + $rankList[] = [ + 'rank' => ($page - 1) * $limit + $index + 1, + 'user_id' => $item['user_id'], + 'nickname' => $userInfo['nickname'], + 'headimg' => imageUrl($userInfo['headimg']), + 'value' => $item['use_money'], + 'unit' => '达达卷' + ]; + } + } + + return $rankList; + } + + /** + * 获取邀请新人排行榜数据 + * + * @param int $startTime 开始时间戳 + * @param int $endTime 结束时间戳 + * @param int $page 页码 + * @param int $limit 每页数量 + * @return array 排行榜数据 + */ + public function getInviteRank($startTime, $endTime, $page, $limit) + { + // 构建查询条件 + $where = [ + ['pid', '>', 0], + ['istest', '=', 0], // 排除测试用户 + ['status', '=', 1] // 只查询状态正常的用户 + ]; + + // 添加时间范围条件 + if ($startTime > 0) { + $where[] = ['addtime', '>=', $startTime]; + } + if ($endTime > 0) { + $where[] = ['addtime', '<=', $endTime]; + } + + // 查询数据 + $list = User::where($where) + ->field('pid, COUNT(1) as invite_count') + ->group('pid') + ->having('invite_count > 0') + ->order('invite_count desc') + ->page($page, $limit) + ->select() + ->toArray(); + + // 处理用户信息 + $rankList = []; + foreach ($list as $index => $item) { + $userInfo = User::field('nickname, headimg, mobile')->where('id', $item['pid'])->find(); + if ($userInfo) { + $rankList[] = [ + 'rank' => ($page - 1) * $limit + $index + 1, + 'user_id' => $item['pid'], + 'nickname' => $userInfo['nickname'], + 'headimg' => imageUrl($userInfo['headimg']), + 'value' => $item['invite_count'], + 'unit' => '人' + ]; + } + } + + return $rankList; + } + + /** + * 获取亏损补贴排行榜数据 + * + * @param int $startTime 开始时间戳 + * @param int $endTime 结束时间戳 + * @param int $page 页码 + * @param int $limit 每页数量 + * @param string $sortField 排序字段:loss_money(亏损金额)或loss_rate(亏损率) + * @return array 排行榜数据 + */ + public function getLossRank($startTime, $endTime, $page, $limit, $sortField = 'loss_money') + { + // 构建订单查询条件 + $orderWhere = [ + ['status', '=', 1], + [ + 'user_id', + 'not in', + function ($query) { + $query->name('user')->where('istest', '>', 0)->where('status', '=', 1)->field('id'); + } + ] + ]; + + // 添加订单时间范围条件 + if ($startTime > 0) { + $orderWhere[] = ['pay_time', '>=', $startTime]; + } + if ($endTime > 0) { + $orderWhere[] = ['pay_time', '<=', $endTime]; + } + + // 构建订单详情查询条件 + $orderListWhere = []; + if ($startTime > 0) { + $orderListWhere[] = ['addtime', '>=', $startTime]; + } + if ($endTime > 0) { + $orderListWhere[] = ['addtime', '<=', $endTime]; + } + + // 查询用户订单总金额(消耗金额) + $orderSubQuery = Order::where($orderWhere) + ->field('user_id, SUM(price) + SUM(use_money) as money') + ->group('user_id') + ->buildSql(); + + // 查询用户出货金额 + $usersWithOutputMoney = []; + $users = Order::where($orderWhere)->field('DISTINCT user_id')->select()->toArray(); + + foreach ($users as $user) { + $userId = $user['user_id']; + + // 构建特定用户的查询条件 + $userOrderListWhere = $orderListWhere; + $userOrderListWhere[] = ['user_id', '=', $userId]; + + // 查询用户出货金额 + $outputMoney = \app\common\model\OrderList::where($userOrderListWhere) + ->sum('goodslist_money'); + + if ($outputMoney > 0) { + $usersWithOutputMoney[$userId] = $outputMoney; + } + } + + // 计算亏损数据 + $rankList = []; + $index = 0; + + // 从订单总金额查询结果中获取数据 + $moneyResult = Db::table($orderSubQuery . ' as t')->where('money', '>', 0)->select()->toArray(); + + foreach ($moneyResult as $item) { + $userId = $item['user_id']; + $consumeMoney = $item['money']; // 消耗金额 + + // 如果有出货金额数据 + if (isset($usersWithOutputMoney[$userId])) { + $outputMoney = $usersWithOutputMoney[$userId]; // 出货金额 + + // 计算亏损金额和亏损率 + $lossMoney = $consumeMoney - $outputMoney; + $lossRate = $consumeMoney > 0 ? round(($lossMoney / $consumeMoney) * 100, 2) : 0; + + // 只有亏损的才纳入排行 + if ($lossMoney > 0) { + $rankList[] = [ + 'user_id' => $userId, + 'consume_money' => $consumeMoney, // 消耗金额 + 'output_money' => $outputMoney, // 出货金额 + 'loss_money' => $lossMoney, // 亏损金额 + 'loss_rate' => $lossRate // 亏损率(%) + ]; + } + } + } + + // 根据排序字段对结果进行排序 + if ($sortField == 'loss_rate') { + // 按亏损率降序排序 + usort($rankList, function($a, $b) { + return $b['loss_rate'] <=> $a['loss_rate']; + }); + } else { + // 默认按亏损金额降序排序 + usort($rankList, function($a, $b) { + return $b['loss_money'] <=> $a['loss_money']; + }); + } + + // 分页处理 + $offset = ($page - 1) * $limit; + $rankList = array_slice($rankList, $offset, $limit); + + // 处理用户信息和排名 + $result = []; + foreach ($rankList as $index => $item) { + $userInfo = User::field('nickname, headimg, mobile')->where('id', $item['user_id'])->find(); + if ($userInfo) { + $result[] = [ + 'rank' => $offset + $index + 1, + 'user_id' => $item['user_id'], + 'nickname' => $userInfo['nickname'], + 'headimg' => imageUrl($userInfo['headimg']), + 'consume_money' => $item['consume_money'], + 'output_money' => $item['output_money'], + 'value' => $item['loss_money'], // 主要展示值为亏损金额 + 'loss_rate' => $item['loss_rate'], // 亏损率 + 'unit' => '元' + ]; + } + } + + return $result; + } + +} \ No newline at end of file diff --git a/config/menu.php b/config/menu.php index ce479ae..60e2b70 100755 --- a/config/menu.php +++ b/config/menu.php @@ -16,6 +16,10 @@ return [ 'url' => '/admin/record', 'name' => '流水排行', ], + [ + 'url' => '/admin/user_rank', + 'name' => '用户排行榜', + ], // [ // 'url' => '/admin/vip', // 'name' => 'VIP管理', diff --git a/public/storage/poster/share/21546.png b/public/storage/poster/share/21546.png new file mode 100644 index 0000000000000000000000000000000000000000..0bd3d8802e2ee4987f29d024e2471b0680a95427 GIT binary patch literal 16953 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�`xi3hWbj&JFe0 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ðLwpt%^hVxgw>C~r@$P>n#-I)6E5WdNC=Z+} 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 D?S2_K literal 0 HcmV?d00001