-
商城余额 / 积分
+
+
商城余额 / 积分
+ {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' }})
+
+
+
+
+
+ {{ assetUnit }}
+
+
+
+
+
+
+
+ 变动后预计持有:{{ previewBalance }} {{ assetUnit }}
+ (⚠️ 扣除超出当前持有量,提交将被拦截)
+
+
+
+
+
+
-
\ 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' }}