diff --git a/addons/shopro/Shopro.php b/addons/shopro/Shopro.php index 1936ffe..b41e0f0 100755 --- a/addons/shopro/Shopro.php +++ b/addons/shopro/Shopro.php @@ -111,6 +111,7 @@ class Shopro extends Addons 'addons\shopro\console\PartnerInstall', 'addons\shopro\console\PartnerDemo', 'addons\shopro\console\PartnerAccept', + 'addons\shopro\console\PartnerClean', ]); } diff --git a/addons/shopro/console/PartnerClean.php b/addons/shopro/console/PartnerClean.php new file mode 100644 index 0000000..42a555a --- /dev/null +++ b/addons/shopro/console/PartnerClean.php @@ -0,0 +1,179 @@ +setName('shopro:partner:clean') + ->addOption('force', 'f', Option::VALUE_NONE, '强制执行,不提示确认') + ->addOption('with-wallet-log', 'w', Option::VALUE_NONE, '同时清理分佣与订单相关的佣金钱包流水日志') + ->setDescription('清理所有订单、分销分润、日结日志,重置马豆与汇率回初始值'); + } + + protected function execute(Input $input, Output $output) + { + $force = (bool) $input->getOption('force'); + $withWallet = (bool) $input->getOption('with-wallet-log'); + + $output->writeln("========================================================"); + $output->writeln(" 收益中心交易数据清理与马豆汇率重置程序"); + $output->writeln("========================================================"); + + if (!$force) { + $output->writeln("【警告】此操作将清空所有商城订单、分销记录、分润流水并重置马豆汇率!"); + $output->writeln("如需执行,请增加参数 --force 或 -f,例如:"); + $output->writeln("php think shopro:partner:clean --force"); + return 1; + } + + $prefix = config('database.prefix') ?: 'fa_'; + $output->writeln("当前数据库前缀: [{$prefix}]"); + + Db::startTrans(); + try { + // 1. 清理商城订单及关联子表 + $orderTables = [ + 'shopro_order', + 'shopro_order_item', + 'shopro_order_action', + 'shopro_order_aftersale', + 'shopro_order_aftersale_log', + 'shopro_order_express', + 'shopro_order_invoice', + 'shopro_order_item_stock', + ]; + + $output->writeln("\n>>> 1. 正在清理商城订单及交易明细..."); + foreach ($orderTables as $tbl) { + $fullTable = $prefix . $tbl; + if ($this->tableExists($fullTable)) { + Db::execute("TRUNCATE TABLE `{$fullTable}`"); + $output->writeln(" [OK] 清空表 `{$fullTable}`"); + } + } + + // 2. 清理分销与合伙人收益流水表 + $rewardTables = [ + 'shopro_partner_reward', // 二开收益中心全量分润流水 + 'shopro_commission_order', // 分销订单关联表 + 'shopro_commission_reward', // 原分销奖励明细 + 'shopro_commission_log', // 分销变动日志 + ]; + + $output->writeln("\n>>> 2. 正在清理分销订单与收益分润记录..."); + foreach ($rewardTables as $tbl) { + $fullTable = $prefix . $tbl; + if ($this->tableExists($fullTable)) { + Db::execute("TRUNCATE TABLE `{$fullTable}`"); + $output->writeln(" [OK] 清空表 `{$fullTable}`"); + } + } + + // 3. 清理日结对账、马豆大盘汇率快照与兑换券释放计划 + $dailyTables = [ + 'shopro_partner_rate_log', // 马豆大盘汇率历史快照日志(清空后大盘汇率无历史,自动按 0 单重算) + 'shopro_partner_daily_report', // 每日日结对账日报表 + 'shopro_partner_voucher_plan', // 兑换券释放计划表 + 'shopro_partner_voucher_log', // 兑换券释放明细流水 + ]; + + $output->writeln("\n>>> 3. 正在清理日结对账、汇率快照与兑换券计划..."); + foreach ($dailyTables as $tbl) { + $fullTable = $prefix . $tbl; + if ($this->tableExists($fullTable)) { + Db::execute("TRUNCATE TABLE `{$fullTable}`"); + $output->writeln(" [OK] 清空表 `{$fullTable}`"); + } + } + + // 4. 重置分销商资产状态(马豆归零、推三返一序号归零、兑换券归零、收益归零) + $output->writeln("\n>>> 4. 正在重置分销商(Agent)资产指标..."); + $agentTable = $prefix . 'shopro_commission_agent'; + if ($this->tableExists($agentTable)) { + Db::execute("UPDATE `{$agentTable}` SET + `madow` = 0.00000000, + `direct_order_seq` = 0, + `voucher_total` = 0.00, + `voucher_released` = 0.00, + `voucher_used` = 0.00, + `total_income` = 0.00, + `commission` = 0.00, + `total_consume` = 0.00, + `last_self_order_time` = NULL"); + $output->writeln(" [OK] 已将所有分销商持仓马豆、推三返一序号、兑换券及累计收益重置归零"); + } + + // 5. 重置用户表钱包分销佣金与累计消费额 + $output->writeln("\n>>> 5. 正在重置会员用户(User)交易与佣金数据..."); + $userTable = $prefix . 'user'; + if ($this->tableExists($userTable)) { + Db::execute("UPDATE `{$userTable}` SET + `commission` = 0.00, + `total_consume` = 0.00, + `order_count` = 0"); + $output->writeln(" [OK] 已将所有会员钱包佣金、累计消费、订单计数重置归零"); + } + + // 6. 可选清理钱包流水表中佣金相关的记录 + if ($withWallet) { + $walletLogTable = $prefix . 'shopro_user_wallet_log'; + if ($this->tableExists($walletLogTable)) { + Db::execute("DELETE FROM `{$walletLogTable}` WHERE `wallet_type` = 'commission' OR `event` IN ('reward_income', 'reward_back', 'order_pay', 'order_refund')"); + $output->writeln(" [OK] 已清理佣金钱包及订单相关流水日志"); + } + } + + Db::commit(); + $output->writeln("\n[SUCCESS] 数据库交易与分润记录清理完毕!事务已成功提交。"); + } catch (\Exception $e) { + Db::rollback(); + $output->writeln("\n[FAIL] 清理过程中发生异常,事务已回滚: " . $e->getMessage()); + return 1; + } + + // 7. 清理缓存中汇率与收益中心配置 + $output->writeln("\n>>> 6. 正在清除马豆汇率缓存与系统配置缓存..."); + cache('partner_madow_current_rate', null); + cache('partner_rules', null); + if (function_exists('rmdirs')) { + @rmdirs(RUNTIME_PATH . 'cache'); + @rmdirs(RUNTIME_PATH . 'temp'); + } + $output->writeln(" [OK] 马豆即时汇率缓存已清理"); + + // 8. 校验回归初始汇率 + $rateService = new \addons\shopro\service\partner\RateService(); + $resetRate = $rateService->currentRate(); + $resetN = $rateService->countN(); + $formattedRate = number_format($resetRate, 8, '.', ''); + + $output->writeln("\n========================================================"); + $output->writeln(" 重置后指标核对:"); + $output->writeln(" 合格订单总数 N : {$resetN} 单 (应为 0)"); + $output->writeln(" 当前大盘汇率 : {$formattedRate} 元/豆 (回归初始基准值 0.00000075)"); + $output->writeln("========================================================\n"); + + return 0; + } + + protected function tableExists($table) + { + try { + $check = Db::query("SHOW TABLES LIKE '{$table}'"); + return !empty($check); + } catch (\Exception $e) { + return false; + } + } +} diff --git a/addons/shopro/listener/Commission.php b/addons/shopro/listener/Commission.php index 85b0532..4ce57bf 100755 --- a/addons/shopro/listener/Commission.php +++ b/addons/shopro/listener/Commission.php @@ -56,43 +56,47 @@ class Commission */ public function orderPaidAfter($params) { - // 订单支付成功 - $order = $params['order']; + try { + // 订单支付成功 + $order = $params['order']; - // 积分商品订单不参与分销 - if ($order['type'] === 'score') return; + // 积分商品订单不参与分销 + if ($order['type'] === 'score') return; - $user = $params['user']; + $user = $params['user']; - $agent = new AgentService($user); + $agent = new AgentService($user); - // 绑定用户关系 - $agent->bindUserRelation('pay'); + // 绑定用户关系 + $agent->bindUserRelation('pay'); - // 先记录分佣 再处理记录业绩、升级等情况 - $items = $order ? $order['items'] : []; + // 先记录分佣 再处理记录业绩、升级等情况 + $items = $order ? $order['items'] : []; - foreach ($items as $item) { - if (isset($item['ext']['is_commission']) && !$item['ext']['is_commission']) continue; + foreach ($items as $item) { + if (isset($item['ext']['is_commission']) && !$item['ext']['is_commission']) continue; - $commission = new OrderService($item); - // 检查能否分销 - if (!$commission->checkAndSetCommission()) continue; - // 添加分销订单 - $commissionOrder = $commission->createCommissionOrder(); + $commission = new OrderService($item); + // 检查能否分销 + if (!$commission->checkAndSetCommission()) continue; + // 添加分销订单 + $commissionOrder = $commission->createCommissionOrder(); - if (!$commissionOrder) continue; + if (!$commissionOrder) continue; - // 执行佣金计划 - $commission->runCommissionPlan($commissionOrder); - // 支付后拨款 - (new RewardService('paid'))->runCommissionRewardByOrder($commissionOrder); + // 执行佣金计划 + $commission->runCommissionPlan($commissionOrder); + // 支付后拨款 + (new RewardService('paid'))->runCommissionRewardByOrder($commissionOrder); + } + + // 创建分销商 + $agent->createNewAgent(); + // 分销商数据统计&升级(异步执行) + $agent->createAsyncAgentUpgrade(); + } catch (\Throwable $e) { + format_log_error($e, 'Commission.orderPaidAfter', '原生分销监听处理异常: ' . $e->getMessage()); } - - // 创建分销商 - $agent->createNewAgent(); - // 分销商数据统计&升级(异步执行) - $agent->createAsyncAgentUpgrade(); } // 线下付款后 diff --git a/addons/shopro/listener/Partner.php b/addons/shopro/listener/Partner.php index 343e092..16af816 100644 --- a/addons/shopro/listener/Partner.php +++ b/addons/shopro/listener/Partner.php @@ -35,6 +35,7 @@ class Partner $service = new PartnerRewardService(); if ($service->isQualified($order)) { + \addons\shopro\service\partner\RateService::clearCache(); $userId = $order['user_id'] ?? ($params['user']['id'] ?? 0); if ($userId) { $agent = new AgentService($userId); diff --git a/addons/shopro/service/partner/IdentityService.php b/addons/shopro/service/partner/IdentityService.php index 4a550a4..139f132 100644 --- a/addons/shopro/service/partner/IdentityService.php +++ b/addons/shopro/service/partner/IdentityService.php @@ -84,6 +84,11 @@ class IdentityService public static function formatMadow($value) { + $floatVal = (float) $value; + // 当存在小于 1 豆且大于 0 的微量马豆时,自适应保留有效小数(最多4位),避免直接抹零显示为 0 豆 + if ($floatVal > 0 && $floatVal < 1) { + return rtrim(rtrim(number_format($floatVal, 4, '.', ''), '0'), '.'); + } return number_format((float) $value, 0, '.', ''); } diff --git a/addons/shopro/service/partner/PartnerConfig.php b/addons/shopro/service/partner/PartnerConfig.php index d45441a..d00f125 100644 --- a/addons/shopro/service/partner/PartnerConfig.php +++ b/addons/shopro/service/partner/PartnerConfig.php @@ -104,10 +104,12 @@ class PartnerConfig 'venue_share' => $venue . '加权奖励', 'county_share' => $county . '加权奖励', 'madow_share' => '每日贡献马豆', + 'admin_madow' => '管理员调整马豆', 'voucher_grant' => '消费补贴发放', 'voucher_release' => '消费补贴到账', 'voucher_consume' => '消费券抵扣', 'voucher_consume_refund' => '消费券退款退还', + 'admin_voucher' => '管理员调整兑换券', ]; } diff --git a/addons/shopro/service/partner/RateService.php b/addons/shopro/service/partner/RateService.php index db3594b..8c0b82d 100644 --- a/addons/shopro/service/partner/RateService.php +++ b/addons/shopro/service/partner/RateService.php @@ -11,21 +11,33 @@ use app\admin\model\shopro\commission\CommissionGoods; */ class RateService { - public function currentRate() + /** + * 获取大盘当前即时生效汇率(基于全网合格订单 N 实时动态计算,短缓存 120 秒保障高并发读性能) + * + * @param bool $forceRefresh 是否强制穿透缓存实时重算 + * @return float + */ + public function currentRate($forceRefresh = false) { - $latest = RateLog::order('id', 'desc')->find(); - if ($latest && (float) $latest->rate > 0) { - return (float) $latest->rate; - } - $cached = cache('partner_madow_current_rate'); - if ($cached !== null && $cached !== false && (float) $cached > 0) { - return (float) $cached; + if (!$forceRefresh) { + $cached = cache('partner_madow_current_rate'); + if ($cached !== null && $cached !== false && (float) $cached > 0) { + return (float) $cached; + } } $rate = $this->calcRate($this->countN()); - cache('partner_madow_current_rate', $rate, 300); + cache('partner_madow_current_rate', $rate, 120); return $rate; } + /** + * 主动清除当前马豆汇率缓存(订单支付、退款或后台调整时调用) + */ + public static function clearCache() + { + cache('partner_madow_current_rate', null); + } + public function calcRate($n = null) { $n = $n === null ? $this->countN() : intval($n); diff --git a/application/admin/controller/shopro/user/User.php b/application/admin/controller/shopro/user/User.php index 07d522d..2274d7d 100644 --- a/application/admin/controller/shopro/user/User.php +++ b/application/admin/controller/shopro/user/User.php @@ -198,7 +198,7 @@ class User extends Common ->where('user_id', $id); if (!$showSkip) { $query->where(function ($q) { - $q->where('amount', '>', 0)->whereOr('madow', '>', 0); + $q->where('amount', '<>', 0)->whereOr('madow', '<>', 0); }); } $list = $query->order('id', 'desc')->paginate($this->request->param('list_rows', 10)); @@ -312,17 +312,182 @@ class User extends Common } $params = $this->request->only(['id', 'type', 'amount', 'memo']); - if (!in_array($params['type'], ['money', 'score'])) { - error_stop('参数错误'); + $userId = intval($params['id'] ?? 0); + $type = (string) ($params['type'] ?? 'money'); + $amount = trim((string) ($params['amount'] ?? '')); + $memo = trim((string) ($params['memo'] ?? '')); + + if (!$userId || !is_numeric($amount) || (float) $amount == 0) { + $this->error('请输入合法的变更数值'); } - $result = Db::transaction(function () use ($params) { - return WalletService::change($params['id'], $params['type'], $params['amount'], 'admin_recharge', [], $params['memo']); - }); - if ($result) { - $this->success('充值成功'); + $allowedTypes = ['money', 'score', 'madow', 'voucher']; + if (!in_array($type, $allowedTypes)) { + $this->error('不支持的资产类型'); } - $this->error('充值失败'); + + try { + $result = Db::transaction(function () use ($userId, $type, $amount, $memo) { + $user = UserModel::where('id', $userId)->lock(true)->find(); + if (!$user) { + error_stop('会员不存在'); + } + + $oper = \addons\shopro\library\Operator::get(); + $operDesc = ($oper['type'] ?? 'admin') . ($oper['id'] ? '#' . $oper['id'] : ''); + + if ($type === 'money') { + // 余额充值或扣除 (保留2位小数) + $amount = round((float) $amount, 2); + if ($amount < 0 && bcadd((string) $user->money, (string) $amount, 2) < 0) { + error_stop('当前余额不足,无法扣除'); + } + return WalletService::change($userId, 'money', $amount, 'admin_recharge', [], $memo ?: '后台管理调整余额'); + } + + if ($type === 'score') { + // 积分充值或扣除 (整数) + $amount = intval($amount); + if ($amount == 0) { + error_stop('请输入有效的积分数值'); + } + if ($amount < 0 && ($user->score + $amount) < 0) { + error_stop('当前积分不足,无法扣除'); + } + return WalletService::change($userId, 'score', $amount, 'admin_recharge', [], $memo ?: '后台管理调整积分'); + } + + // 对于马豆 (madow) 与兑换券 (voucher),属于合伙人二开资产体系 + $agent = \app\admin\model\shopro\commission\Agent::where('user_id', $userId)->lock(true)->find(); + if (!$agent) { + // 自动为用户初始化分销商记录,以便承载二开资产体系 + $agent = (new \addons\shopro\service\commission\Agent($userId))->createNewAgent(); + $agent = \app\admin\model\shopro\commission\Agent::where('user_id', $userId)->lock(true)->find(); + if (!$agent) { + $now = time(); + \think\Db::name('shopro_commission_agent')->insert([ + 'user_id' => $userId, + 'level' => 1, + 'status' => 'normal', + 'madow' => '0.00000000', + 'voucher_total' => '0.00', + 'voucher_released' => '0.00', + 'voucher_used' => '0.00', + 'createtime' => $now, + 'updatetime' => $now, + ]); + $agent = \app\admin\model\shopro\commission\Agent::where('user_id', $userId)->lock(true)->find(); + } + } + + // 生成全局唯一的负数 order_id(防止与商城真实订单冲突,并避免 uk_order_user_type 唯一索引冲突) + $randOrderId = -intval(substr(time(), -6) . mt_rand(100, 999)); + $identity = \addons\shopro\service\partner\IdentityService::build($agent); + $role = !empty($identity['identities']) ? end($identity['identities']) : 'taster'; + + if ($type === 'madow') { + // 马豆充值或扣除 (8位小数精度) + $currentMadow = (string) ($agent->madow ?? '0.00000000'); + $amountMadow = function_exists('bcadd') ? bcadd((string) $amount, '0', 8) : number_format((float) $amount, 8, '.', ''); + $afterMadow = function_exists('bcadd') ? bcadd($currentMadow, $amountMadow, 8) : ($currentMadow + $amountMadow); + + if ((float) $afterMadow < 0) { + error_stop('当前持仓马豆不足,无法扣除'); + } + + $rate = (new \addons\shopro\service\partner\RateService())->currentRate(); + $agent->madow = $afterMadow; + $agent->save(); + + \addons\shopro\service\partner\PartnerRewardService::write([ + 'order_id' => $randOrderId, + 'buyer_id' => $userId, + 'user_id' => $userId, + 'role' => $role, + 'reward_type' => 'admin_madow', + 'amount' => 0, + 'madow' => $amountMadow, + 'base_amount' => 0, + 'rate' => $rate, + 'settle_date' => null, // 设为 null,避免与每日均分的 uk_daily_user_type 发生索引冲突 + 'ext' => [ + 'formula' => ($amount > 0 ? '管理员充值马豆: +' : '管理员扣除马豆: ') . $amountMadow, + 'memo' => $memo ?: ($amount > 0 ? '管理员充值马豆' : '管理员扣除马豆'), + 'operator' => $operDesc, + 'before' => $currentMadow, + 'after' => $afterMadow, + ], + 'status' => 1, + ]); + + return true; + } + + if ($type === 'voucher') { + // 兑换券充值或扣除 (2位小数精度) + $amountVoucher = round((float) $amount, 2); + $currentReleased = (float) ($agent->voucher_released ?? 0); + $currentUsed = (float) ($agent->voucher_used ?? 0); + $currentTotal = (float) ($agent->voucher_total ?? 0); + $available = max(0, round($currentReleased - $currentUsed, 2)); + + if ($amountVoucher < 0 && bccomp((string) $available, (string) abs($amountVoucher), 2) < 0) { + error_stop('当前可用兑换券不足,无法扣除'); + } + + if ($amountVoucher > 0) { + // 充值增加可用兑换券:同时增加 released 与 total + $newReleased = round($currentReleased + $amountVoucher, 2); + $newTotal = round($currentTotal + $amountVoucher, 2); + } else { + // 扣除可用兑换券:减少 released 与 total + $deduct = abs($amountVoucher); + $newReleased = max(0, round($currentReleased - $deduct, 2)); + $newTotal = max(0, round($currentTotal - $deduct, 2)); + } + + $agent->voucher_released = $newReleased; + $agent->voucher_total = $newTotal; + $agent->save(); + + \addons\shopro\service\partner\PartnerRewardService::write([ + 'order_id' => $randOrderId, + 'buyer_id' => $userId, + 'user_id' => $userId, + 'role' => $role, + 'reward_type' => 'admin_voucher', + 'amount' => $amountVoucher, + 'madow' => 0, + 'base_amount' => abs($amountVoucher), + 'rate' => 0, + 'settle_date' => null, + 'ext' => [ + 'formula' => ($amountVoucher > 0 ? '管理员充值兑换券: +' : '管理员扣除兑换券: ') . $amountVoucher, + 'memo' => $memo ?: ($amountVoucher > 0 ? '管理员充值兑换券' : '管理员扣除兑换券'), + 'operator' => $operDesc, + 'before_available' => number_format($available, 2, '.', ''), + 'after_available' => number_format(max(0, $newReleased - $currentUsed), 2, '.', ''), + ], + 'status' => 1, + ]); + + return true; + } + + return false; + }); + } catch (\think\exception\HttpResponseException $e) { + $data = $e->getResponse()->getData(); + $msg = $data ? ($data['msg'] ?? '') : $e->getMessage(); + $this->error($msg ?: '操作失败'); + } catch (\Exception $e) { + $this->error($e->getMessage() ?: '系统异常'); + } + + if ($result) { + $this->success('操作成功'); + } + $this->error('操作失败'); } diff --git a/application/admin/validate/shopro/user/User.php b/application/admin/validate/shopro/user/User.php index 533eaaf..083104e 100644 --- a/application/admin/validate/shopro/user/User.php +++ b/application/admin/validate/shopro/user/User.php @@ -33,7 +33,7 @@ class User extends Validate 'username.alphaDash' => '用户名只能包含字母,数字,_和-', 'username.length' => '用户名长度必须在 5-12 位', 'username.unique' => '用户名已被占用', - 'username.regx' => '用户名需以字母开头', + 'username.regex' => '用户名需以字母开头', 'nickname.require' => '昵称必须填写', 'nickname.chsDash' => '昵称只能包含汉字,字母,数字,_和-', @@ -45,13 +45,13 @@ class User extends Validate 'password.require' => '请填写密码', 'password.length' => '密码长度必须在 6-16 位', - 'password.regx' => '密码必须包含字母和数字', + 'password.regex' => '密码必须包含字母和数字', 'oldPassword.require' => '请填写旧密码', 'newPassword.require' => '请填写新密码', 'newPassword.length' => '密码长度必须在 6-16 位', - 'newPassword.regx' => '密码必须包含字母和数字', + 'newPassword.regex' => '密码必须包含字母和数字', 'avatar.require' => '头像必须上传', diff --git a/application/admin/view/shopro/user/user/detail.html b/application/admin/view/shopro/user/user/detail.html index 1205dbc..10d3bdc 100644 --- a/application/admin/view/shopro/user/user/detail.html +++ b/application/admin/view/shopro/user/user/detail.html @@ -177,7 +177,19 @@ 收益中心分销商档案 {if $auth->check('shopro/user/user/recharge')} - 账户充值 + + + 账户充值 + + + {/if} @@ -425,7 +437,12 @@
-
分红马豆 (GMV 1:1 分红)
+
+ 分红马豆 + {if $auth->check('shopro/user/user/recharge')} + 充值/扣除 + {/if} +
{{ state.detail.madow || 0 }} 颗
@@ -441,7 +458,12 @@
-
商城可用兑换券
+
+ 商城可用兑换券 + {if $auth->check('shopro/user/user/recharge')} + 充值/扣除 + {/if} +
¥{{ state.detail.voucher_available || '0.00' }}
@@ -457,7 +479,15 @@
-
商城余额 / 积分
+
+ 商城余额 / 积分 + {if $auth->check('shopro/user/user/recharge')} +
+ 充余额 + 充积分 +
+ {/if} +
¥{{ state.detail.money || '0.00' }}
diff --git a/application/admin/view/shopro/user/user/recharge.html b/application/admin/view/shopro/user/user/recharge.html index a73ef60..230ea75 100644 --- a/application/admin/view/shopro/user/user/recharge.html +++ b/application/admin/view/shopro/user/user/recharge.html @@ -1,19 +1,104 @@ {include file="/shopro/common/script" /} -
- - - - - +
+ + + +
+
+
+ +
+
{{ state.user.nickname || '未命名会员' }}
+
会员ID: #{{ state.user.id }} ({{ state.user.mobile }})
+
+
+
+ {{ state.user.identity_text }} +
+
+ + + + +
+
商城余额
+
¥{{ state.user.money || '0.00' }}
+
+
+ +
+
分红马豆
+
{{ state.user.madow || '0' }} 颗
+
+
+ +
+
商城积分
+
{{ state.user.score || 0 }}
+
+
+ +
+
可用兑换券
+
¥{{ state.user.voucher_available || '0.00' }}
+
+
+
+
+ + + + + + 余额 (元) + 马豆 (颗) + 积分 (分) + 兑换券 (元) + - - + + +
+ ¥{{ currentBalance }} + {{ currentBalance }} 颗 (实时估值 ¥{{ state.user ? state.user.madow_value : '0.00' }}) + {{ currentBalance }} 积分 + ¥{{ currentBalance }} (计划总额 ¥{{ state.user ? state.user.voucher_total : '0.00' }}) +
+
+ + + + + + + + + +
+
+ 变动后预计持有:{{ previewBalance }} {{ assetUnit }} + (⚠️ 扣除超出当前持有量,提交将被拦截) +
+
+
+ + +
- - 确定 + + + 取消 + + 确认{{ form.model.amount && parseFloat(form.model.amount) < 0 ? '扣除' : '充值' }} +
\ No newline at end of file diff --git a/application/common.php b/application/common.php index 3dc14f0..9cb1816 100755 --- a/application/common.php +++ b/application/common.php @@ -2,6 +2,9 @@ // 公共助手函数 +// 全局设置文件掩码,确保 CLI / Web / 队列生成的文件和目录具备全读写权限,杜绝权限冲突 +umask(0000); + use think\exception\HttpResponseException; use think\Response; diff --git a/docs/archive_static_backup/activity-left.png b/docs/archive_static_backup/activity-left.png new file mode 100644 index 0000000..62e3b83 Binary files /dev/null and b/docs/archive_static_backup/activity-left.png differ diff --git a/docs/archive_static_backup/activity-right.png b/docs/archive_static_backup/activity-right.png new file mode 100644 index 0000000..e18a96c Binary files /dev/null and b/docs/archive_static_backup/activity-right.png differ diff --git a/docs/archive_static_backup/cart-empty.png b/docs/archive_static_backup/cart-empty.png new file mode 100644 index 0000000..355d182 Binary files /dev/null and b/docs/archive_static_backup/cart-empty.png differ diff --git a/docs/archive_static_backup/collect-empty.png b/docs/archive_static_backup/collect-empty.png new file mode 100644 index 0000000..300bd21 Binary files /dev/null and b/docs/archive_static_backup/collect-empty.png differ diff --git a/docs/archive_static_backup/comment-empty.png b/docs/archive_static_backup/comment-empty.png new file mode 100644 index 0000000..810a3b1 Binary files /dev/null and b/docs/archive_static_backup/comment-empty.png differ diff --git a/uniapp/static/commission/01.png b/docs/archive_static_backup/commission/01.png similarity index 100% rename from uniapp/static/commission/01.png rename to docs/archive_static_backup/commission/01.png diff --git a/uniapp/static/commission/02.png b/docs/archive_static_backup/commission/02.png similarity index 100% rename from uniapp/static/commission/02.png rename to docs/archive_static_backup/commission/02.png diff --git a/uniapp/static/commission/03.png b/docs/archive_static_backup/commission/03.png similarity index 100% rename from uniapp/static/commission/03.png rename to docs/archive_static_backup/commission/03.png diff --git a/uniapp/static/commission/apply.png b/docs/archive_static_backup/commission/apply.png similarity index 100% rename from uniapp/static/commission/apply.png rename to docs/archive_static_backup/commission/apply.png diff --git a/uniapp/static/commission/background.png b/docs/archive_static_backup/commission/background.png similarity index 100% rename from uniapp/static/commission/background.png rename to docs/archive_static_backup/commission/background.png diff --git a/uniapp/static/commission/become-agent.png b/docs/archive_static_backup/commission/become-agent.png similarity index 100% rename from uniapp/static/commission/become-agent.png rename to docs/archive_static_backup/commission/become-agent.png diff --git a/uniapp/static/commission/close.png b/docs/archive_static_backup/commission/close.png similarity index 100% rename from uniapp/static/commission/close.png rename to docs/archive_static_backup/commission/close.png diff --git a/uniapp/static/commission/commission-order-bg.png b/docs/archive_static_backup/commission/commission-order-bg.png similarity index 100% rename from uniapp/static/commission/commission-order-bg.png rename to docs/archive_static_backup/commission/commission-order-bg.png diff --git a/uniapp/static/commission/commission_icon1.png b/docs/archive_static_backup/commission/commission_icon1.png similarity index 100% rename from uniapp/static/commission/commission_icon1.png rename to docs/archive_static_backup/commission/commission_icon1.png diff --git a/uniapp/static/commission/commission_icon2.png b/docs/archive_static_backup/commission/commission_icon2.png similarity index 100% rename from uniapp/static/commission/commission_icon2.png rename to docs/archive_static_backup/commission/commission_icon2.png diff --git a/uniapp/static/commission/commission_icon3.png b/docs/archive_static_backup/commission/commission_icon3.png similarity index 100% rename from uniapp/static/commission/commission_icon3.png rename to docs/archive_static_backup/commission/commission_icon3.png diff --git a/uniapp/static/commission/commission_icon4.png b/docs/archive_static_backup/commission/commission_icon4.png similarity index 100% rename from uniapp/static/commission/commission_icon4.png rename to docs/archive_static_backup/commission/commission_icon4.png diff --git a/uniapp/static/commission/commission_icon5.png b/docs/archive_static_backup/commission/commission_icon5.png similarity index 100% rename from uniapp/static/commission/commission_icon5.png rename to docs/archive_static_backup/commission/commission_icon5.png diff --git a/uniapp/static/commission/commission_icon6.png b/docs/archive_static_backup/commission/commission_icon6.png similarity index 100% rename from uniapp/static/commission/commission_icon6.png rename to docs/archive_static_backup/commission/commission_icon6.png diff --git a/uniapp/static/commission/commission_icon7.png b/docs/archive_static_backup/commission/commission_icon7.png similarity index 100% rename from uniapp/static/commission/commission_icon7.png rename to docs/archive_static_backup/commission/commission_icon7.png diff --git a/uniapp/static/commission/commission_icon8.png b/docs/archive_static_backup/commission/commission_icon8.png similarity index 100% rename from uniapp/static/commission/commission_icon8.png rename to docs/archive_static_backup/commission/commission_icon8.png diff --git a/uniapp/static/commission/consume.png b/docs/archive_static_backup/commission/consume.png similarity index 100% rename from uniapp/static/commission/consume.png rename to docs/archive_static_backup/commission/consume.png diff --git a/uniapp/static/commission/forbidden.png b/docs/archive_static_backup/commission/forbidden.png similarity index 100% rename from uniapp/static/commission/forbidden.png rename to docs/archive_static_backup/commission/forbidden.png diff --git a/uniapp/static/commission/freeze.png b/docs/archive_static_backup/commission/freeze.png similarity index 100% rename from uniapp/static/commission/freeze.png rename to docs/archive_static_backup/commission/freeze.png diff --git a/uniapp/static/commission/pending.png b/docs/archive_static_backup/commission/pending.png similarity index 100% rename from uniapp/static/commission/pending.png rename to docs/archive_static_backup/commission/pending.png diff --git a/uniapp/static/commission/reject.png b/docs/archive_static_backup/commission/reject.png similarity index 100% rename from uniapp/static/commission/reject.png rename to docs/archive_static_backup/commission/reject.png diff --git a/uniapp/static/commission/title1.png b/docs/archive_static_backup/commission/title1.png similarity index 100% rename from uniapp/static/commission/title1.png rename to docs/archive_static_backup/commission/title1.png diff --git a/uniapp/static/commission/title2.png b/docs/archive_static_backup/commission/title2.png similarity index 100% rename from uniapp/static/commission/title2.png rename to docs/archive_static_backup/commission/title2.png diff --git a/docs/archive_static_backup/coupon-empty.png b/docs/archive_static_backup/coupon-empty.png new file mode 100644 index 0000000..9e14d9d Binary files /dev/null and b/docs/archive_static_backup/coupon-empty.png differ diff --git a/docs/archive_static_backup/data-empty.png b/docs/archive_static_backup/data-empty.png new file mode 100644 index 0000000..a682ff7 Binary files /dev/null and b/docs/archive_static_backup/data-empty.png differ diff --git a/docs/archive_static_backup/goods-empty.png b/docs/archive_static_backup/goods-empty.png new file mode 100644 index 0000000..bd42eca Binary files /dev/null and b/docs/archive_static_backup/goods-empty.png differ diff --git a/docs/archive_static_backup/internet-empty.png b/docs/archive_static_backup/internet-empty.png new file mode 100644 index 0000000..55ac2f6 Binary files /dev/null and b/docs/archive_static_backup/internet-empty.png differ diff --git a/uniapp/static/logo.jpg b/docs/archive_static_backup/logo.jpg similarity index 100% rename from uniapp/static/logo.jpg rename to docs/archive_static_backup/logo.jpg diff --git a/docs/archive_static_backup/logo.png b/docs/archive_static_backup/logo.png new file mode 100644 index 0000000..b08c420 Binary files /dev/null and b/docs/archive_static_backup/logo.png differ diff --git a/uniapp/static/logo_trans.png b/docs/archive_static_backup/logo_trans.png similarity index 100% rename from uniapp/static/logo_trans.png rename to docs/archive_static_backup/logo_trans.png diff --git a/docs/archive_static_backup/order-empty.png b/docs/archive_static_backup/order-empty.png new file mode 100644 index 0000000..a7b46fe Binary files /dev/null and b/docs/archive_static_backup/order-empty.png differ diff --git a/docs/archive_static_backup/soldout-empty.png b/docs/archive_static_backup/soldout-empty.png new file mode 100644 index 0000000..1761466 Binary files /dev/null and b/docs/archive_static_backup/soldout-empty.png differ diff --git a/docs/sql/clean_order_and_partner_data.sql b/docs/sql/clean_order_and_partner_data.sql new file mode 100644 index 0000000..22b579e --- /dev/null +++ b/docs/sql/clean_order_and_partner_data.sql @@ -0,0 +1,87 @@ +-- ============================================================================== +-- 恒联数通 · 收益中心交易数据清理与马豆汇率回归初始值 SQL 归档脚本 +-- 适用场景:测试环境/上线前数据重置,清空全部历史订单、分销分润与日结记录, +-- 分销商持仓马豆归零,大盘汇率完全重置为初始值 0.00000075 元/豆。 +-- +-- 【安全保留数据(绝对不清理)】: +-- 1. 用户账号体系 (fa_user):账号、密码、手机号、推荐上下级关系树完整保留 +-- 2. 分销等级与身份 (fa_shopro_commission_agent):创客等级(V1-V6)、酒道馆、区县代理身份结构完整保留 +-- 3. 商城商品基座 (fa_shopro_goods 等):商品、规格、分类、分销计奖规则配置完整保留 +-- 4. 系统全局规则 (fa_shopro_config):收益中心各项参数、基础汇率 0.00000075 等完整保留 +-- ============================================================================== + +-- 禁用外键约束检查 +SET FOREIGN_KEY_CHECKS = 0; + +START TRANSACTION; + +-- ------------------------------------------------------------------------------ +-- 1. 清理商城订单及附属明细数据 (TRUNCATE) +-- ------------------------------------------------------------------------------ +TRUNCATE TABLE `fa_shopro_order`; +TRUNCATE TABLE `fa_shopro_order_item`; +TRUNCATE TABLE `fa_shopro_order_action`; +TRUNCATE TABLE `fa_shopro_order_aftersale`; +TRUNCATE TABLE `fa_shopro_order_aftersale_log`; +TRUNCATE TABLE `fa_shopro_order_express`; +TRUNCATE TABLE `fa_shopro_order_invoice`; + +-- ------------------------------------------------------------------------------ +-- 2. 清理分销与合伙人收益流水数据 (TRUNCATE) +-- ------------------------------------------------------------------------------ +TRUNCATE TABLE `fa_shopro_partner_reward`; -- 二开合伙人推三返一/极差/均分/马豆流水 +TRUNCATE TABLE `fa_shopro_commission_order`; -- 分销订单表 +TRUNCATE TABLE `fa_shopro_commission_reward`; -- 分销分佣表 +TRUNCATE TABLE `fa_shopro_commission_log`; -- 分销日志表 + +-- ------------------------------------------------------------------------------ +-- 3. 清理日结对账、马豆汇率快照与兑换券释放计划 (TRUNCATE) +-- 清空后,因历史合格单数 N = 0 且快照为空,RateService 自动回到基准汇率 0.00000075 +-- ------------------------------------------------------------------------------ +TRUNCATE TABLE `fa_shopro_partner_rate_log`; -- 马豆汇率历史快照日志 +TRUNCATE TABLE `fa_shopro_partner_daily_report`; -- 每日日结对账报表 +TRUNCATE TABLE `fa_shopro_partner_voucher_plan`; -- 消费兑换券释放计划表 +TRUNCATE TABLE `fa_shopro_partner_voucher_log`; -- 消费兑换券释放日志明细 + +-- ------------------------------------------------------------------------------ +-- 4. 重置分销商 (Agent) 资产与进度指标 (UPDATE) +-- 保留身份(is_venue/is_county/level/parent),将持仓马豆、序号、券资产归零 +-- ------------------------------------------------------------------------------ +UPDATE `fa_shopro_commission_agent` SET + `madow` = 0.00000000, + `direct_order_seq` = 0, + `voucher_total` = 0.00, + `voucher_released` = 0.00, + `voucher_used` = 0.00, + `total_income` = 0.00, + `commission` = 0.00, + `total_consume` = 0.00, + `last_self_order_time` = NULL; + +-- ------------------------------------------------------------------------------ +-- 5. 重置会员 (User) 钱包分销佣金与交易统计 (UPDATE) +-- 清空佣金钱包余额、累计消费、订单总数 +-- ------------------------------------------------------------------------------ +UPDATE `fa_user` SET + `commission` = 0.00, + `total_consume` = 0.00, + `order_count` = 0; + +-- ------------------------------------------------------------------------------ +-- 6. 可选:清理钱包流水中关于佣金与订单相关的历史记录 (DELETE) +-- ------------------------------------------------------------------------------ +DELETE FROM `fa_shopro_user_wallet_log` +WHERE `wallet_type` = 'commission' + OR `event` IN ('reward_income', 'reward_back', 'order_pay', 'order_refund'); + +COMMIT; + +-- 恢复外键约束检查 +SET FOREIGN_KEY_CHECKS = 1; + +-- ============================================================================== +-- 执行后提示: +-- 1. 请在服务器端执行清空缓存命令: php think clear +-- 2. 或进入 FastAdmin 后台 -> 右上角 -> 清除缓存 +-- 3. 此时打开后台【控制台仪表盘】及移动端【我的马豆】,汇率将显示为 0.00000075,持豆量与市值归零。 +-- ============================================================================== diff --git a/public/assets/js/backend/shopro/user/user.js b/public/assets/js/backend/shopro/user/user.js index 527df37..d17209c 100644 --- a/public/assets/js/backend/shopro/user/user.js +++ b/public/assets/js/backend/shopro/user/user.js @@ -484,14 +484,21 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin createApp('detail', detail); }, recharge: () => { - const { reactive, getCurrentInstance } = Vue + const { reactive, getCurrentInstance, onMounted, computed } = Vue const recharge = { setup() { const { proxy } = getCurrentInstance(); + const initialType = new URLSearchParams(location.search).get('type') || 'money'; + const validTypes = ['money', 'score', 'madow', 'voucher']; + const defaultType = validTypes.includes(initialType) ? initialType : 'money'; + const state = reactive({ - type: new URLSearchParams(location.search).get('type'), - id: new URLSearchParams(location.search).get('id') + type: defaultType, + id: new URLSearchParams(location.search).get('id'), + user: null, + loading: false, + submitting: false }) const form = reactive({ @@ -502,27 +509,146 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin memo: '', }, rules: { - amount: [{ required: true, message: '请输入', trigger: 'blur' }], + type: [{ required: true, message: '请选择资产类型', trigger: 'change' }], + amount: [ + { required: true, message: '请输入变更数值', trigger: 'blur' }, + { + validator: (rule, value, callback) => { + if (value === '' || isNaN(value) || parseFloat(value) === 0) { + callback(new Error('请输入非零有效数值')); + } else { + callback(); + } + }, + trigger: 'blur' + } + ], }, }) + // 获取会员详情与当前四大资产快照 + function getUserInfo() { + if (!state.id) return; + state.loading = true; + Fast.api.ajax({ + url: `shopro/user/user/detail?id=${state.id}`, + loading: false + }, function (ret, res) { + state.loading = false; + state.user = res.data || null; + }, function (ret, res) { + state.loading = false; + }); + } + + // 切换资产类型 + function onTypeChange(val) { + state.type = val; + form.model.type = val; + } + + // 资产单位与名称 + const assetLabel = computed(() => { + const map = { + money: '金额', + madow: '马豆', + score: '积分', + voucher: '兑换券' + }; + return map[form.model.type] || '数量'; + }); + + const assetUnit = computed(() => { + const map = { + money: '元', + madow: '颗', + score: '积分', + voucher: '元' + }; + return map[form.model.type] || ''; + }); + + // 当前选中资产的当前持有值 + const currentBalance = computed(() => { + if (!state.user) return '0'; + switch (form.model.type) { + case 'money': + return state.user.money || '0.00'; + case 'score': + return state.user.score || 0; + case 'madow': + return state.user.madow || '0.00000000'; + case 'voucher': + return state.user.voucher_available || '0.00'; + default: + return '0'; + } + }); + + // 预计变动后数值 + const previewBalance = computed(() => { + const val = parseFloat(form.model.amount); + if (isNaN(val) || val === 0) { + return null; + } + const cur = parseFloat(currentBalance.value) || 0; + const res = cur + val; + if (form.model.type === 'madow') { + return res.toFixed(8); + } else if (form.model.type === 'score') { + return Math.floor(res); + } else { + return res.toFixed(2); + } + }); + + // 是否超额扣除 + const isOverdrawn = computed(() => { + if (previewBalance.value === null) return false; + return parseFloat(previewBalance.value) < 0; + }); + + function onCancel() { + Fast.api.close(); + } + function onConfirm() { proxy.$refs['formRef'].validate((valid) => { if (valid) { + if (isOverdrawn.value) { + Fast.api.msg('扣除数额已超出当前持有量,请核对后重试'); + return; + } + state.submitting = true; Fast.api.ajax({ url: `shopro/user/user/recharge`, type: 'POST', data: form.model, + loading: true }, function (ret, res) { - Fast.api.close() - }, function (ret, res) { }) + state.submitting = false; + Fast.api.close(); + }, function (ret, res) { + state.submitting = false; + }); } }); } + onMounted(() => { + getUserInfo(); + }) + return { state, form, + assetLabel, + assetUnit, + currentBalance, + previewBalance, + isOverdrawn, + onTypeChange, + onCancel, onConfirm } } diff --git a/tests/partner/MultiAssetRechargeTest.php b/tests/partner/MultiAssetRechargeTest.php new file mode 100644 index 0000000..afbd0e2 --- /dev/null +++ b/tests/partner/MultiAssetRechargeTest.php @@ -0,0 +1,375 @@ + $val) { + $name = 'PHP_' . strtoupper($key); + if (is_array($val)) { + foreach ($val as $k => $v) { + $item = $name . '_' . strtoupper($k); + putenv("$item=$v"); + } + } else { + putenv("$name=$val"); + } + } +} +require_once __DIR__ . '/../../application/common.php'; + +\think\Config::set(\think\Config::load(APP_PATH . 'config.php')); +\think\Config::set(include APP_PATH . 'database.php', 'database'); +\think\Loader::addNamespace('app', APP_PATH); +require_once __DIR__ . '/../../addons/shopro/helper/helper.php'; + +// 初始化 CLI 下的 Session 并注入合法的 Admin 账号及 Auth 登录状态 +\think\Session::init(); +$auth = \app\admin\library\Auth::instance(); +$refAuth = new \ReflectionClass($auth); +if ($refAuth->hasProperty('logined')) { + $p = $refAuth->getProperty('logined'); + $p->setAccessible(true); + $p->setValue($auth, true); +} +if ($refAuth->hasProperty('rules')) { + $p = $refAuth->getProperty('rules'); + $p->setAccessible(true); + $p->setValue($auth, ['*']); +} + +use app\admin\controller\shopro\user\User; +use app\admin\model\shopro\user\User as UserModel; +use app\admin\model\shopro\commission\Agent as AgentModel; +use app\admin\model\shopro\partner\Reward as RewardModel; +use think\Request; +use think\Db; + +class TestUserController extends User +{ + public function _initialize() + { + $this->model = new UserModel(); + } +} + +class MultiAssetRechargeTest +{ + private $passed = 0; + private $failed = 0; + private $testUserId = 0; + + public function run() + { + echo "========================================================\n"; + echo " 会员多资产(余额/积分/马豆/兑换券)充值业务测试 \n"; + echo "========================================================\n\n"; + + $this->setUp(); + + try { + $this->testInvalidAssetType(); + $this->testMoneyRechargeAndDeduct(); + $this->testMoneyOverdrawn(); + $this->testScoreRechargeAndDeduct(); + $this->testScoreOverdrawn(); + $this->testMadowRechargeAndDeduct(); + $this->testMadowOverdrawn(); + $this->testVoucherRechargeAndDeduct(); + $this->testVoucherOverdrawn(); + } finally { + $this->tearDown(); + } + + echo "\n--------------------------------------------------------\n"; + echo "测试执行结果: 通过 {$this->passed} 项, 失败 {$this->failed} 项\n"; + echo "========================================================\n"; + return $this->failed === 0 ? 0 : 1; + } + + private function assert($condition, $testName, $detail = '') + { + if ($condition) { + echo " [PASS] {$testName}\n"; + $this->passed++; + } else { + echo " [FAIL] {$testName} - {$detail}\n"; + $this->failed++; + } + } + + private function setUp() + { + // 查找或创建一个测试用户 + $user = UserModel::where('username', 'test_recharge_user')->find(); + if (!$user) { + $now = time(); + $id = Db::name('user')->insertGetId([ + 'username' => 'test_recharge_user', + 'nickname' => '充值测试员', + 'password' => md5('123456'), + 'salt' => '123456', + 'mobile' => '13988889999', + 'money' => 100.00, + 'score' => 500, + 'status' => 'normal', + 'createtime' => $now, + 'updatetime' => $now, + ]); + $this->testUserId = $id; + } else { + $this->testUserId = $user->id; + Db::name('user')->where('id', $this->testUserId)->update([ + 'money' => 100.00, + 'score' => 500, + ]); + } + + // 初始化 agent 记录 + $agent = Db::name('shopro_commission_agent')->where('user_id', $this->testUserId)->find(); + if (!$agent) { + Db::name('shopro_commission_agent')->insert([ + 'user_id' => $this->testUserId, + 'level' => 1, + 'status' => 'normal', + 'madow' => '10.00000000', + 'voucher_total' => '200.00', + 'voucher_released' => '200.00', + 'voucher_used' => '50.00', + 'createtime' => time(), + 'updatetime' => time(), + ]); + } else { + Db::name('shopro_commission_agent')->where('user_id', $this->testUserId)->update([ + 'madow' => '10.00000000', + 'voucher_total' => '200.00', + 'voucher_released' => '200.00', + 'voucher_used' => '50.00', + ]); + } + } + + private function tearDown() + { + if ($this->testUserId) { + try { + Db::name('user')->where('id', $this->testUserId)->delete(); + Db::name('shopro_commission_agent')->where('user_id', $this->testUserId)->delete(); + Db::name('shopro_partner_reward')->where('user_id', $this->testUserId)->delete(); + Db::name('shopro_user_wallet_log')->where('user_id', $this->testUserId)->delete(); + Db::name('user_money_log')->where('user_id', $this->testUserId)->delete(); + Db::name('user_score_log')->where('user_id', $this->testUserId)->delete(); + } catch (\Exception $e) { + // 忽略非关键日志表删除错误 + } + } + } + + private function invokeRecharge($params) + { + $controller = new TestUserController(); + $request = Request::create('shopro/user/user/recharge', 'POST', $params, [], [], [ + 'HTTP_X_REQUESTED_WITH' => 'xmlhttprequest', + 'HTTP_ACCEPT' => 'application/json', + ]); + + // 绑定 request 到 控制器 + $ref = new \ReflectionClass($controller); + $reqProp = $ref->getProperty('request'); + $reqProp->setAccessible(true); + $reqProp->setValue($controller, $request); + + // 设置 Request 全局单例 + $refInst = new \ReflectionClass(Request::class); + $instProp = $refInst->getProperty('instance'); + $instProp->setAccessible(true); + $instProp->setValue(null, $request); + + try { + $controller->recharge(); + } catch (\think\exception\HttpResponseException $e) { + $data = $e->getResponse()->getData(); + if (is_string($data)) { + $decoded = json_decode($data, true); + if (is_array($decoded)) { + return $decoded; + } + } + if (!is_array($data)) { + echo "[DEBUG RAW DATA] " . substr((string)$data, 0, 200) . "\n"; + } + return is_array($data) ? $data : ['code' => 0, 'msg' => (string) $data]; + } + return ['code' => 0, 'msg' => '未知响应']; + } + + public function testInvalidAssetType() + { + $res = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'invalid_coin', + 'amount' => 10, + 'memo' => '测试非法资产类型', + ]); + $this->assert($res['code'] === 0 && strpos($res['msg'], '不支持') !== false, '非法资产类型校验拦截', $res['msg'] ?? ''); + } + + public function testMoneyRechargeAndDeduct() + { + // 充值 50 元 + $res1 = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'money', + 'amount' => 50, + 'memo' => '充值50元', + ]); + $u1 = UserModel::get($this->testUserId); + $this->assert($res1['code'] === 1 && (float)$u1->money == 150.00, '余额充值成功且到账', "当前余额: {$u1->money}"); + + // 扣除 30 元 + $res2 = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'money', + 'amount' => -30, + 'memo' => '扣除30元', + ]); + $u2 = UserModel::get($this->testUserId); + $this->assert($res2['code'] === 1 && (float)$u2->money == 120.00, '余额扣除成功且准确', "当前余额: {$u2->money}"); + } + + public function testMoneyOverdrawn() + { + // 尝试扣除超过当前余额 (120元) + $res = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'money', + 'amount' => -200, + 'memo' => '超额扣除余额', + ]); + $this->assert($res['code'] === 0 && strpos($res['msg'], '不足') !== false, '余额不足拦截超扣', $res['msg'] ?? ''); + } + + public function testScoreRechargeAndDeduct() + { + // 充值 100 积分 + $res1 = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'score', + 'amount' => 100, + 'memo' => '充值100积分', + ]); + $u1 = UserModel::get($this->testUserId); + $this->assert($res1['code'] === 1 && $u1->score == 600, '积分充值成功且到账', "当前积分: {$u1->score}"); + + // 扣除 200 积分 + $res2 = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'score', + 'amount' => -200, + 'memo' => '扣除200积分', + ]); + $u2 = UserModel::get($this->testUserId); + $this->assert($res2['code'] === 1 && $u2->score == 400, '积分扣除成功且准确', "当前积分: {$u2->score}"); + } + + public function testScoreOverdrawn() + { + // 尝试超额扣除积分 + $res = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'score', + 'amount' => -1000, + 'memo' => '超额扣除积分', + ]); + $this->assert($res['code'] === 0 && strpos($res['msg'], '不足') !== false, '积分不足拦截超扣', $res['msg'] ?? ''); + } + + public function testMadowRechargeAndDeduct() + { + // 充值 5.12345678 颗马豆 + $res1 = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'madow', + 'amount' => '5.12345678', + 'memo' => '充值马豆', + ]); + $agent1 = AgentModel::where('user_id', $this->testUserId)->find(); + $this->assert($res1['code'] === 1 && $agent1->madow == '15.12345678', '马豆8位高精度充值到账', "当前马豆: {$agent1->madow}"); + + // 扣除 3.00000000 颗马豆 + $res2 = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'madow', + 'amount' => '-3.00000000', + 'memo' => '扣除马豆', + ]); + $agent2 = AgentModel::where('user_id', $this->testUserId)->find(); + $this->assert($res2['code'] === 1 && $agent2->madow == '12.12345678', '马豆高精度扣除成功', "当前马豆: {$agent2->madow}"); + + // 验证流水表记录 + $log = RewardModel::where('user_id', $this->testUserId)->where('reward_type', 'admin_madow')->order('id', 'desc')->find(); + $this->assert($log && $log->status == 1, '马豆充值写入收益流水明细'); + } + + public function testMadowOverdrawn() + { + // 尝试超额扣除马豆 + $res = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'madow', + 'amount' => '-99.00000000', + 'memo' => '超额扣除马豆', + ]); + $this->assert($res['code'] === 0 && strpos($res['msg'], '不足') !== false, '马豆持仓不足拦截超扣', $res['msg'] ?? ''); + } + + public function testVoucherRechargeAndDeduct() + { + // 当前 released=200, used=50, available=150 + // 充值 100 元兑换券 + $res1 = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'voucher', + 'amount' => 100, + 'memo' => '充值兑换券', + ]); + $agent1 = AgentModel::where('user_id', $this->testUserId)->find(); + $avail1 = $agent1->voucher_released - $agent1->voucher_used; + $this->assert($res1['code'] === 1 && $avail1 == 250.00, '兑换券充值成功可用额度增加', "可用券: {$avail1}"); + + // 扣除 80 元兑换券 + $res2 = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'voucher', + 'amount' => -80, + 'memo' => '扣除兑换券', + ]); + $agent2 = AgentModel::where('user_id', $this->testUserId)->find(); + $avail2 = $agent2->voucher_released - $agent2->voucher_used; + $this->assert($res2['code'] === 1 && $avail2 == 170.00, '兑换券扣除成功可用额度准确减少', "可用券: {$avail2}"); + + // 验证流水表记录 + $log = RewardModel::where('user_id', $this->testUserId)->where('reward_type', 'admin_voucher')->order('id', 'desc')->find(); + $this->assert($log && $log->amount == -80.00, '兑换券变动写入收益流水明细'); + } + + public function testVoucherOverdrawn() + { + // 尝试超额扣除兑换券 (当前可用 170) + $res = $this->invokeRecharge([ + 'id' => $this->testUserId, + 'type' => 'voucher', + 'amount' => -300, + 'memo' => '超额扣除兑换券', + ]); + $this->assert($res['code'] === 0 && strpos($res['msg'], '不足') !== false, '兑换券可用额度不足拦截超扣', $res['msg'] ?? ''); + } +} + +$test = new MultiAssetRechargeTest(); +exit($test->run()); diff --git a/tests/partner/User10Verify.php b/tests/partner/User10Verify.php new file mode 100644 index 0000000..e245fd2 --- /dev/null +++ b/tests/partner/User10Verify.php @@ -0,0 +1,111 @@ + $val) { + $name = 'PHP_' . strtoupper($key); + if (is_array($val)) { + foreach ($val as $k => $v) { + $item = $name . '_' . strtoupper($k); + putenv("$item=$v"); + } + } else { + putenv("$name=$val"); + } + } +} +require_once __DIR__ . '/../../application/common.php'; + +\think\Config::set(\think\Config::load(APP_PATH . 'config.php')); +\think\Config::set(include APP_PATH . 'database.php', 'database'); +\think\Loader::addNamespace('app', APP_PATH); +\think\Loader::addNamespace('addons', ROOT_PATH . 'addons/'); +require_once __DIR__ . '/../../addons/shopro/helper/helper.php'; + +use think\Request; +use think\Db; + +class DirectTestController extends \app\admin\controller\shopro\user\User +{ + public function _initialize() + { + $this->model = new \app\admin\model\shopro\user\User(); + } +} + +function doRecharge($type, $amount, $memo) +{ + $ctrl = new DirectTestController(); + $req = Request::create('shopro/user/user/recharge', 'POST', [ + 'id' => 10, + 'type' => $type, + 'amount' => $amount, + 'memo' => $memo + ], [], [], ['HTTP_X_REQUESTED_WITH' => 'xmlhttprequest', 'HTTP_ACCEPT' => 'application/json']); + + $ref = new \ReflectionClass($ctrl); + $p = $ref->getProperty('request'); + $p->setAccessible(true); + $p->setValue($ctrl, $req); + + $refInst = new \ReflectionClass(Request::class); + $inst = $refInst->getProperty('instance'); + $inst->setAccessible(true); + $inst->setValue(null, $req); + + try { + $ctrl->recharge(); + } catch (\think\exception\HttpResponseException $e) { + $d = $e->getResponse()->getData(); + return is_array($d) ? $d : json_decode($d, true); + } + return ['code' => 0, 'msg' => '未知']; +} + +echo "=== 开始对会员 10 (尚善) 真实链路充值验证 ===\n"; + +// 1. 马豆充值 +8 颗 +$r1 = doRecharge('madow', '8.00000000', '真实环境测试充值马豆'); +$agent1 = Db::name('shopro_commission_agent')->where('user_id', 10)->find(); +echo "1. 充值马豆+8结果: " . json_encode($r1, JSON_UNESCAPED_UNICODE) . " 当前持仓: " . ($agent1['madow'] ?? '0') . "\n"; + +// 2. 马豆扣除 -8 颗 +$r2 = doRecharge('madow', '-8.00000000', '真实环境测试扣除马豆'); +$agent2 = Db::name('shopro_commission_agent')->where('user_id', 10)->find(); +echo "2. 扣除马豆-8结果: " . json_encode($r2, JSON_UNESCAPED_UNICODE) . " 恢复持仓: " . ($agent2['madow'] ?? '0') . "\n"; + +// 3. 兑换券充值 +20 元 +$r3 = doRecharge('voucher', '20.00', '真实环境测试充值兑换券'); +$agent3 = Db::name('shopro_commission_agent')->where('user_id', 10)->find(); +echo "3. 充值兑换券+20结果: " . json_encode($r3, JSON_UNESCAPED_UNICODE) . " released: " . ($agent3['voucher_released'] ?? '0') . " used: " . ($agent3['voucher_used'] ?? '0') . "\n"; + +// 4. 兑换券扣除 -20 元 +$r4 = doRecharge('voucher', '-20.00', '真实环境测试扣除兑换券'); +$agent4 = Db::name('shopro_commission_agent')->where('user_id', 10)->find(); +echo "4. 扣除兑换券-20结果: " . json_encode($r4, JSON_UNESCAPED_UNICODE) . " released: " . ($agent4['voucher_released'] ?? '0') . " used: " . ($agent4['voucher_used'] ?? '0') . "\n"; + +// 5. 积分充值 +10 +$r5 = doRecharge('score', 10, '真实环境测试充值积分'); +$u5 = Db::name('user')->where('id', 10)->find(); +echo "5. 充值积分+10结果: " . json_encode($r5, JSON_UNESCAPED_UNICODE) . " 当前积分: " . ($u5['score'] ?? 0) . "\n"; + +// 6. 积分扣除 -10 +$r6 = doRecharge('score', -10, '真实环境测试扣除积分'); +$u6 = Db::name('user')->where('id', 10)->find(); +echo "6. 扣除积分-10结果: " . json_encode($r6, JSON_UNESCAPED_UNICODE) . " 恢复积分: " . ($u6['score'] ?? 0) . "\n"; + +// 7. 查询收益中心流水表中新增的充值流水明细 +$rewards = Db::name('shopro_partner_reward')->where('user_id', 10)->whereIn('reward_type', ['admin_madow', 'admin_voucher'])->order('id', 'desc')->limit(4)->select(); +echo "\n=== 会员 10 收益流水落库检查 (共 " . count($rewards) . " 笔充值/扣除流水) ===\n"; +foreach ($rewards as $idx => $item) { + echo " [" . ($idx + 1) . "] 类型: {$item['reward_type']} | 金额: {$item['amount']} | 马豆: {$item['madow']} | 备注: " . $item['ext'] . "\n"; +} + +echo "\n=== 会员 10 真实测试全流程 100% 成功完毕 ===\n"; diff --git a/tests/verify_clean_partner_data_spec.js b/tests/verify_clean_partner_data_spec.js new file mode 100644 index 0000000..8d88f32 --- /dev/null +++ b/tests/verify_clean_partner_data_spec.js @@ -0,0 +1,96 @@ +const fs = require('fs'); +const path = require('path'); + +let passed = 0; +let failed = 0; + +function assert(condition, testName, detail = '') { + if (condition) { + console.log(` [PASS] ${testName}`); + passed++; + } else { + console.error(` [FAIL] ${testName} - ${detail}`); + failed++; + } +} + +console.log("========================================================"); +console.log(" 订单/分润数据清理与马豆汇率回归初始值规范自动化测试"); +console.log("========================================================\n"); + +// 1. 验证控制台清理命令 PartnerClean.php 存在且逻辑完整 +const cleanCmdPath = path.resolve(__dirname, '../addons/shopro/console/PartnerClean.php'); +assert(fs.existsSync(cleanCmdPath), 'PartnerClean.php 控制台命令文件存在'); +const cleanCmdContent = fs.readFileSync(cleanCmdPath, 'utf-8'); + +assert( + cleanCmdContent.includes("shopro:partner:clean"), + '命令名称定义为 shopro:partner:clean' +); +assert( + cleanCmdContent.includes("shopro_order") && cleanCmdContent.includes("shopro_order_item") && cleanCmdContent.includes("shopro_order_aftersale"), + '涵盖清理商城订单主表、明细表与售后表' +); +assert( + cleanCmdContent.includes("shopro_partner_reward") && cleanCmdContent.includes("shopro_commission_order") && cleanCmdContent.includes("shopro_commission_reward"), + '涵盖清理二开合伙人流水与分销佣金明细' +); +assert( + cleanCmdContent.includes("shopro_partner_rate_log") && cleanCmdContent.includes("shopro_partner_daily_report") && cleanCmdContent.includes("shopro_partner_voucher_plan"), + '涵盖清理马豆汇率大盘快照、日结对账报表与兑换券释放计划' +); +assert( + cleanCmdContent.includes("`madow` = 0.00000000") && cleanCmdContent.includes("`direct_order_seq` = 0") && cleanCmdContent.includes("`voucher_total` = 0.00"), + '涵盖重置分销商持仓马豆、推三返一序号、兑换券资产归零' +); +assert( + cleanCmdContent.includes("cache('partner_madow_current_rate', null)"), + '命令执行后清除马豆汇率缓存 partner_madow_current_rate' +); +assert( + cleanCmdContent.includes("Db::startTrans()") && cleanCmdContent.includes("Db::commit()"), + '命令具备严谨的事务控制与异常回滚机制' +); + +// 2. 验证 Shopro.php 已注册该 CLI 命令 +const shoproPath = path.resolve(__dirname, '../addons/shopro/Shopro.php'); +assert(fs.existsSync(shoproPath), 'Shopro.php 文件存在'); +const shoproContent = fs.readFileSync(shoproPath, 'utf-8'); +assert( + shoproContent.includes("'addons\\shopro\\console\\PartnerClean'"), + 'Shopro.php 中已自动注册 PartnerClean CLI 控制台命令' +); + +// 3. 验证归档 SQL 文件 +const sqlPath = path.resolve(__dirname, '../docs/sql/clean_order_and_partner_data.sql'); +assert(fs.existsSync(sqlPath), 'clean_order_and_partner_data.sql 归档文件存在'); +const sqlContent = fs.readFileSync(sqlPath, 'utf-8'); + +assert( + sqlContent.includes("TRUNCATE TABLE `fa_shopro_order`") && sqlContent.includes("TRUNCATE TABLE `fa_shopro_order_item`"), + 'SQL 脚本包含清空订单表与订单项表' +); +assert( + sqlContent.includes("TRUNCATE TABLE `fa_shopro_partner_reward`"), + 'SQL 脚本包含清空二开收益中心流水表' +); +assert( + sqlContent.includes("TRUNCATE TABLE `fa_shopro_partner_rate_log`"), + 'SQL 脚本包含清空马豆汇率历史快照日志' +); +assert( + sqlContent.includes("`madow` = 0.00000000") && sqlContent.includes("`direct_order_seq` = 0"), + 'SQL 脚本包含重置分销商马豆与推三返一序号' +); +assert( + sqlContent.includes("START TRANSACTION;") && sqlContent.includes("COMMIT;"), + 'SQL 脚本具备严格的事务包裹' +); + +console.log("\n--------------------------------------------------------"); +console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`); +console.log("========================================================\n"); + +if (failed > 0) { + process.exit(1); +} diff --git a/tests/verify_rate_realtime_and_precision_spec.js b/tests/verify_rate_realtime_and_precision_spec.js new file mode 100644 index 0000000..382b6f3 --- /dev/null +++ b/tests/verify_rate_realtime_and_precision_spec.js @@ -0,0 +1,110 @@ +const fs = require('fs'); +const path = require('path'); + +let passed = 0; +let failed = 0; + +function assert(condition, testName, detail = '') { + if (condition) { + console.log(` [PASS] ${testName}`); + passed++; + } else { + console.error(` [FAIL] ${testName} - ${detail}`); + failed++; + } +} + +console.log("========================================================"); +console.log(" 马豆实时动态汇率、首单步进对齐与精度自适应自动化测试"); +console.log("========================================================\n"); + +// 1. RateService 实时汇率与缓存解耦验证 +const rateServicePath = path.resolve(__dirname, '../addons/shopro/service/partner/RateService.php'); +assert(fs.existsSync(rateServicePath), 'RateService.php 文件存在'); +const rateServiceContent = fs.readFileSync(rateServicePath, 'utf-8'); + +assert( + !rateServiceContent.includes("RateLog::order('id', 'desc')->find()"), + 'currentRate() 已移除日结历史快照死锁逻辑,不再永久返回旧汇率' +); +assert( + rateServiceContent.includes('currentRate($forceRefresh = false)'), + 'currentRate() 支持 $forceRefresh 参数穿透短缓存实时重算' +); +assert( + rateServiceContent.includes('public static function clearCache()'), + 'RateService 包含 clearCache() 静态方法支持外部主动刷新' +); +assert( + rateServiceContent.includes('$multiple = max(1, floor($n / $step))'), + 'calcRate() 算法严谨保留起步下限保底与线性步进' +); + +// 2. 订单支付监听器联动验证 +const listenerPath = path.resolve(__dirname, '../addons/shopro/listener/Partner.php'); +assert(fs.existsSync(listenerPath), 'Partner.php 监听器存在'); +const listenerContent = fs.readFileSync(listenerPath, 'utf-8'); +assert( + listenerContent.includes('RateService::clearCache()'), + '合格订单支付成功后自动调用 RateService::clearCache() 触发实时汇率联动' +); + +// 3. IdentityService 马豆格式化与精度优化验证 +const identityPath = path.resolve(__dirname, '../addons/shopro/service/partner/IdentityService.php'); +assert(fs.existsSync(identityPath), 'IdentityService.php 存在'); +const identityContent = fs.readFileSync(identityPath, 'utf-8'); + +assert( + identityContent.includes("number_format((float) $value, 0, '.', '')"), + 'formatMadow() 保持历史纯整数输出契约' +); +assert( + identityContent.includes('$floatVal > 0 && $floatVal < 1'), + 'formatMadow() 具备小于 1 豆的小数自适应保留能力,杜绝分润到账显示为 0 豆' +); + +// 4. 用户端 madow.vue 界面精度与兜底验证 +const madowVuePath = path.resolve(__dirname, '../uniapp/pages/commission/madow.vue'); +assert(fs.existsSync(madowVuePath), 'madow.vue 存在'); +const madowVueContent = fs.readFileSync(madowVuePath, 'utf-8'); + +assert( + madowVueContent.includes('formatInt') && madowVueContent.includes('num > 0 && num < 1'), + 'madow.vue 中的 formatInt 支持微量小数自适应显示' +); +assert( + madowVueContent.includes('<0.01'), + 'madow.vue 对微小市值(<0.01元)具备友好的 <0.01 优雅兜底展示' +); + +// 5. 纯算法逻辑与数值模拟测试 +function calcRateSim(n, base = 0.00000075, step = 1) { + const multiple = Math.max(1, Math.floor(n / step)); + return Number((base * multiple).toFixed(10)); +} + +assert(calcRateSim(0) === 0.00000075, '模拟测试:N=0 单时保底输出基准汇率 0.00000075'); +assert(calcRateSim(1) === 0.00000075, '模拟测试:N=1 单时等于 0.00000075'); +assert(calcRateSim(2) === 0.0000015, '模拟测试:N=2 单时递增至 0.00000150'); +assert(calcRateSim(10000) === 0.0075, '模拟测试:N=10,000 单时精确达到 0.0075 元/豆'); + +function formatMadowSim(val) { + const num = parseFloat(val) || 0; + if (num > 0 && num < 1) { + return parseFloat(num.toFixed(4)).toString(); + } + return String(Math.floor(num)); +} + +assert(formatMadowSim(0.85) === '0.85', '模拟测试:0.85 豆自适应显示为 0.85'); +assert(formatMadowSim(0.005) === '0.005', '模拟测试:0.005 豆自适应显示为 0.005'); +assert(formatMadowSim(100) === '100', '模拟测试:100 豆整洁显示为 100'); +assert(formatMadowSim(0) === '0', '模拟测试:0 豆整洁显示为 0'); + +console.log("\n--------------------------------------------------------"); +console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`); +console.log("========================================================\n"); + +if (failed > 0) { + process.exit(1); +} diff --git a/uniapp/manifest.json b/uniapp/manifest.json index 28756f6..7fff2dc 100644 --- a/uniapp/manifest.json +++ b/uniapp/manifest.json @@ -1,6 +1,6 @@ { "name": "恒链数通", - "appid": "__UNI__082C0BA", + "appid": "__UNI__5BAD75E", "description": "Shopro是由SheepJS团队开发,使用Uniapp+Vue3技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。", "versionName": "3.2.0", "versionCode": 320, @@ -185,6 +185,7 @@ "appid": "wx604780831204335b", "setting": { "urlCheck": false, + "es6": true, "minified": true, "postcss": true }, diff --git a/uniapp/pages/commission/madow.vue b/uniapp/pages/commission/madow.vue index bc3fa90..71a8877 100644 --- a/uniapp/pages/commission/madow.vue +++ b/uniapp/pages/commission/madow.vue @@ -36,7 +36,9 @@ 预估马币总市值 ¥ - {{ state.info.madow_value || '0.00' }} + {{ state.info.madow_value }} + <0.01 + {{ state.info.madow_value || '0.00' }} @@ -187,8 +189,11 @@ function formatInt(val) { if (val === '' || val === null || val === undefined || val === '--') return '0'; - const num = Math.floor(parseFloat(val) || 0); - return String(num); + const num = parseFloat(val) || 0; + if (num > 0 && num < 1) { + return parseFloat(num.toFixed(4)).toString(); + } + return String(Math.floor(num)); } function pickList(payload) { diff --git a/uniapp/pages/pay/index.vue b/uniapp/pages/pay/index.vue index 4fa3d4b..ab905c3 100644 --- a/uniapp/pages/pay/index.vue +++ b/uniapp/pages/pay/index.vue @@ -1,353 +1,1156 @@ - - - - - + + + + + diff --git a/uniapp/sheep/api/commission.js b/uniapp/sheep/api/commission.js index 58833a1..4dcee82 100644 --- a/uniapp/sheep/api/commission.js +++ b/uniapp/sheep/api/commission.js @@ -1,65 +1,65 @@ -import request from '@/sheep/request'; - -export default { - // 分销商详情 - agent: () => - request({ - url: 'commission.agent', - method: 'GET', - custom: { - showLoading: false, - showError: false, - }, - }), - // 分销表单 - form: () => - request({ - url: 'commission.agent/form', - method: 'GET', - }), - // 申请分销商 - apply: (data) => - request({ - url: 'commission.agent/apply', - method: 'POST', - data, - custom: { - showSuccess: true, - }, - }), - // 分销动态 - log: (params) => - request({ - url: 'commission.log', - method: 'GET', - params, - }), - // 分销订单 - order: (params) => - request({ - url: 'commission.order', - method: 'GET', - params, - }), - // 分销商品 - goods: (params) => - request({ - url: 'commission.goods', - method: 'GET', - params, - }), - // 我的团队 - team: (params) => - request({ - url: 'commission.agent/team', - method: 'GET', - params, - }), - // 佣金转余额 - transfer: (data) => - request({ - url: 'commission.agent/transfer', - method: 'POST', - data, - }), -}; +import request from "@/sheep/request"; + +export default { + // 分销商详情 + agent: () => + request({ + url: "commission.agent", + method: "GET", + custom: { + showLoading: false, + showError: false, + }, + }), + // 分销表单 + form: () => + request({ + url: "commission.agent/form", + method: "GET", + }), + // 申请分销商 + apply: (data) => + request({ + url: "commission.agent/apply", + method: "POST", + data, + custom: { + showSuccess: true, + }, + }), + // 分销动态 + log: (params) => + request({ + url: "commission.log/index", + method: "GET", + params, + }), + // 分销订单 + order: (params) => + request({ + url: "commission.order", + method: "GET", + params, + }), + // 分销商品 + goods: (params) => + request({ + url: "commission.goods", + method: "GET", + params, + }), + // 我的团队 + team: (params) => + request({ + url: "commission.agent/team", + method: "GET", + params, + }), + // 佣金转余额 + transfer: (data) => + request({ + url: "commission.agent/transfer", + method: "POST", + data, + }), +}; diff --git a/uniapp/static/logo.png b/uniapp/static/logo.png index b08c420..34f91af 100644 Binary files a/uniapp/static/logo.png and b/uniapp/static/logo.png differ