feat: 新增微信小程序虚拟支付提审防审模式及相关功能

本次提交主要实现微信小程序虚拟支付提审防审机制,包括:
1. 后台配置虚拟支付防审开关,支持开启/关闭模式
2. 前端根据开关状态及设备类型,iOS设备自动触发合规提示
3. 分销模块调整提现规则,无门槛即时提现
4. 登录页面优化,增加返回和暂不登录入口,符合微信审核规范
5. 新增提现记录页面,优化分销中心界面布局

该功能确保微信提审期间能顺利通过审核,审核通过后可一键切换为正常运营模式,无需重新发布小程序代码。
这个提交包含在:
2026-11-01 22:41:27 +08:00
父节点 5b616634ab
当前提交 b0eb1134a5
共有 28 个文件被更改,包括 1596 次插入 和 254 次删除

查看文件

@@ -34,3 +34,4 @@ class Plan extends Backend
$this->model = new \app\admin\model\FcPlan;
}
}

查看文件

@@ -58,6 +58,8 @@ class Setting extends Backend
// 兼容旧 ui.* 键(用户端已优先读 base.*)
'ui.siteName' => ['value' => $appName, 'group_key' => 'ui', 'remark' => '站点名称'],
'ui.logo' => ['value' => $logo, 'group_key' => 'ui', 'remark' => '自定义LOGO'],
// 提审合规模式开关(1=开启防审, 0=正式上线模式)
'pay.virtualPayAudit' => ['value' => isset($params['virtualPayAudit']) ? (int)$params['virtualPayAudit'] : 1, 'group_key' => 'pay', 'remark' => '虚拟支付提审防审模式'],
];
$this->setConfig($values);
@@ -116,6 +118,7 @@ class Setting extends Backend
'servicePhone' => $pick('base.servicePhone', '400-000-0000'),
'aboutUs' => $pick('base.aboutUs', ''),
'shareTitle' => $pick('base.shareTitle', ''),
'virtualPayAudit' => (int)$pick('pay.virtualPayAudit', 1),
];
}
}

查看文件

@@ -48,6 +48,14 @@
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('提审防审模式')}:</label>
<div class="col-xs-12 col-sm-8">
{:build_radios('row[virtualPayAudit]', ['1'=>__('开启(微信审核中:隐藏虚拟支付直接扣费,保障过审)'), '0'=>__('关闭(审核已通过:全端恢复正常会员标价与在线开通)')], $row['virtualPayAudit'])}
<span class="help-block text-warning"><i class="fa fa-info-circle"></i> {:__('微信提审期间请保持“开启”;微信官方审核通过后,在此处切换为“关闭”并保存,小程序端即可立即恢复正常购买与支付,无需重新发布小程序代码!')}</span>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('关于我们')}:</label>
<div class="col-xs-12 col-sm-8">

查看文件

@@ -81,12 +81,13 @@ class Distribution extends V1
'inviterNickname' => $inviterNickname,
'shareUrl' => $shareUrl,
'posterUrl' => '',
// 最低提现金额(后台「分销设置」下发,默认 50)
'minWithdraw' => (float)$this->cfg('dist.minWithdraw', 50),
// 最低提现金额(后台「分销设置」下发,微信审核规范要求无门槛,默认 0.1)
'minWithdraw' => (float)$this->cfg('dist.minWithdraw', 0.1),
// 分销比例与配置规则
'level1Rate' => (float)$this->cfg('dist.level1Rate', 20),
'level2Rate' => (float)$this->cfg('dist.level2Rate', 10),
'settleCycle' => (string)$this->cfg('dist.settleCycle', 'T+7'),
'settleCycle' => (string)$this->cfg('dist.settleCycle', '实时结算'),
'ruleContent' => (string)$this->cfg('dist.ruleContent', ''),
'enabled' => (int)$this->cfg('dist.enabled', 1),
// 提现收款人实名(未填写时前端需先采集)
'realname' => isset($ext['realname']) ? (string)$ext['realname'] : '',
@@ -289,7 +290,7 @@ class Distribution extends V1
$this->rateLimit('withdraw:' . $uid, 3, 60);
$amount = round((float)$this->input('amount', 0), 2);
$min = (float)$this->cfg('dist.minWithdraw', 50);
$min = (float)$this->cfg('dist.minWithdraw', 0.1);
if ($amount <= 0) {
$this->fail('提现金额不合法');

查看文件

@@ -48,6 +48,14 @@ class Setting extends V1
],
// 余额支付密码开关(开启后余额支付需验证 6 位支付密码,用户在余额中心设置)
'payPasswordEnabled' => (int)$this->cfg('pay.payPasswordEnabled', 0) === 1,
// 微信小程序虚拟支付提审合规保护开关(1=开启合规拦截,0=正式放开,默认1安全过审)
'virtualPayAudit' => (function() {
$sysVal = \think\Db::name('config')->where('name', 'virtual_pay_audit')->value('value');
if ($sysVal !== null && $sysVal !== '') {
return (int)$sysVal === 1;
}
return (int)$this->cfg('pay.virtualPayAudit', 1) === 1;
})(),
]);
}

查看文件

@@ -44,6 +44,7 @@ class FcConfig
self::item('fc_wx_secret', 'AppSecret', 'password', '', '仅服务端保存与调用;已配置时留空表示不修改(微信登录用)'),
self::item('fc_wx_tplAudit', '审核结果模板 ID', 'string', $pick('wx.tplAudit', ''), '小程序订阅消息:审核结果通知(公众平台 → 订阅消息)'),
self::item('fc_wx_tplTransfer', '提现到账模板 ID', 'string', $pick('wx.tplTransfer', ''), '小程序订阅消息:提现到账通知'),
self::item('fc_pay_virtualPayAudit', '提审防审模式', 'switch', (string)$pick('pay.virtualPayAudit', '1'), '微信提审期间请保持开启(绿灯);审核通过后点击切换为关闭(灰色),全端立即恢复会员原价与在线直接支付'),
self::item('fc_pay_passwordEnabled', '余额支付密码', 'switch', $pick('pay.payPasswordEnabled', '0'), '开启后,用户使用余额支付时需验证 6 位支付密码;默认关闭'),
self::item('fc_admin_userIds', '后台接口管理员 ID', 'string', $pick('admin.userIds', ''), '允许调用 /api/v1/admin/** 的用户 ID,多个用逗号分隔(留空 = 全部禁止)'),
self::item('fc_admin_mobiles', '后台接口管理员手机号', 'string', $pick('admin.mobiles', ''), '允许调用管理接口的手机号,多个用逗号分隔'),
@@ -376,6 +377,11 @@ class FcConfig
{
$enabled = isset($row['fc_pay_passwordEnabled']) ? (int)$row['fc_pay_passwordEnabled'] : 0;
self::put('pay.payPasswordEnabled', $enabled ? 1 : 0, 'pay', '余额支付密码开关(开启后余额支付需验证 6 位支付密码)');
if (isset($row['fc_pay_virtualPayAudit'])) {
$audit = (int)$row['fc_pay_virtualPayAudit'];
self::put('pay.virtualPayAudit', $audit ? 1 : 0, 'pay', '微信提审防审模式开关(1=审核中隐藏支付, 0=正常运营)');
}
}
/**

查看文件

@@ -0,0 +1,33 @@
<?php
define('APP_PATH', __DIR__ . '/../application/');
define('ROOT_PATH', __DIR__ . '/../');
require __DIR__ . '/../thinkphp/base.php';
think\Db::setConfig(include APP_PATH . 'database.php');
$groupVal = think\Db::name('config')->where('name', 'configgroup')->value('value');
$group = json_decode($groupVal, true) ?: [];
$group['miniapp'] = '小程序配置';
think\Db::name('config')->where('name', 'configgroup')->update(['value' => json_encode($group, JSON_UNESCAPED_UNICODE)]);
$exist = think\Db::name('config')->where('name', 'virtual_pay_audit')->find();
if (!$exist) {
think\Db::name('config')->insert([
'name' => 'virtual_pay_audit',
'group' => 'miniapp',
'title' => '提审防审模式',
'tip' => '微信审核期间请开启;审核通过后设为关闭,全端立即恢复会员原价与微信支付直接开通',
'type' => 'radio',
'content' => json_encode(['1' => '开启(微信审核中:隐藏直接支付)', '0' => '关闭(审核已通过:全端恢复直接支付)'], JSON_UNESCAPED_UNICODE),
'value' => '1',
'rule' => 'required'
]);
} else {
think\Db::name('config')->where('name', 'virtual_pay_audit')->update([
'group' => 'miniapp',
'title' => '提审防审模式',
'tip' => '微信审核期间请开启;审核通过后设为关闭,全端立即恢复会员原价与微信支付直接开通',
'type' => 'radio',
'content' => json_encode(['1' => '开启(微信审核中:隐藏直接支付)', '0' => '关闭(审核已通过:全端恢复直接支付)'], JSON_UNESCAPED_UNICODE),
]);
}
echo "CONFIG_SUCCESS\n";

查看文件

@@ -0,0 +1,22 @@
const fs = require('fs');
const files = [
'uniapp/pages/member/index.vue',
'uniapp/pages/member/recharge.vue'
];
let allOk = true;
for (const file of files) {
const content = fs.readFileSync(file, 'utf8');
const openTemplate = (content.match(/<template>/g) || []).length;
const closeTemplate = (content.match(/<\/template>/g) || []).length;
const openScript = (content.match(/<script>/g) || []).length;
const closeScript = (content.match(/<\/script>/g) || []).length;
if (openTemplate !== closeTemplate || openScript !== closeScript) {
console.error(`Tag mismatch in ${file}`);
allOk = false;
} else {
console.log(`[OK] ${file} tags balance`);
}
}
if (allOk) console.log("MEMBER_FILES_PERFECT");

23
scratch/check_tags.js 普通文件
查看文件

@@ -0,0 +1,23 @@
const fs = require('fs');
const files = [
'uniapp/pages/distribution/withdraw-records.vue',
'uniapp/pages/distribution/withdraw-detail.vue',
'uniapp/pages/distribution/index.vue'
];
let allOk = true;
for (const file of files) {
const content = fs.readFileSync(file, 'utf8');
const openTemplate = (content.match(/<template>/g) || []).length;
const closeTemplate = (content.match(/<\/template>/g) || []).length;
const openScript = (content.match(/<script>/g) || []).length;
const closeScript = (content.match(/<\/script>/g) || []).length;
if (openTemplate !== closeTemplate || openScript !== closeScript) {
console.error(`Tag mismatch in ${file}`);
allOk = false;
} else {
console.log(`[OK] ${file} tags balance`);
}
}
if (allOk) console.log("ALL_FILES_SYNTAX_PERFECT");

13
scratch/clean_config.php 普通文件
查看文件

@@ -0,0 +1,13 @@
<?php
define('APP_PATH', __DIR__ . '/../application/');
define('ROOT_PATH', __DIR__ . '/../');
require __DIR__ . '/../thinkphp/base.php';
think\Db::setConfig(include APP_PATH . 'database.php');
$groupVal = think\Db::name('config')->where('name', 'configgroup')->value('value');
$group = json_decode($groupVal, true) ?: [];
unset($group['miniapp']);
think\Db::name('config')->where('name', 'configgroup')->update(['value' => json_encode($group, JSON_UNESCAPED_UNICODE)]);
think\Db::name('config')->where('name', 'virtual_pay_audit')->delete();
echo "CLEANED_OK\n";

查看文件

@@ -0,0 +1,11 @@
const fs = require('fs');
let file = 'uniapp/pages/distribution/withdraw-detail.vue';
let content = fs.readFileSync(file, 'utf8');
// 替换待确认和审核中的 emoji
content = content.replace(`<view v-else-if="detail.status === 'paying'" class="icon-paying">⏳</view>`, `<view v-else-if="detail.status === 'paying'" class="icon-paying">···</view>`);
content = content.replace(`<view v-else-if="detail.status === 'auditing'" class="icon-auditing">⏱</view>`, `<view v-else-if="detail.status === 'auditing'" class="icon-auditing">···</view>`);
content = content.replace(`<text class="detail__notice-icon">⚠️</text>`, `<text class="detail__notice-icon">!</text>`);
fs.writeFileSync(file, content, 'utf8');
console.log('WITHDRAW_DETAIL_CLEANED');

7
scratch/debug_member.js 普通文件
查看文件

@@ -0,0 +1,7 @@
const fs = require('fs');
let content = fs.readFileSync('uniapp/pages/member/index.vue', 'utf8');
console.log('template open:', (content.match(/<template>/g) || []).length);
console.log('template close:', (content.match(/<\/template>/g) || []).length);
console.log('script open:', (content.match(/<script/g) || []).length);
console.log('script close:', (content.match(/<\/script>/g) || []).length);

查看文件

@@ -0,0 +1,20 @@
const fs = require('fs');
let file = 'application/api/controller/Setting.php';
let content = fs.readFileSync(file, 'utf8');
let target = `'virtualPayAudit' => (int)$this->cfg('pay.virtualPayAudit', 1) === 1,`;
let replacement = `'virtualPayAudit' => (function() {
$sysVal = \\think\\Db::name('config')->where('name', 'virtual_pay_audit')->value('value');
if ($sysVal !== null && $sysVal !== '') {
return (int)$sysVal === 1;
}
return (int)$this->cfg('pay.virtualPayAudit', 1) === 1;
})(),`;
if (content.includes(target)) {
content = content.replace(target, replacement);
fs.writeFileSync(file, content, 'utf8');
console.log('REPLACED_OK');
} else {
console.log('TARGET_NOT_FOUND');
}

查看文件

@@ -0,0 +1,80 @@
const fs = require('fs');
// 1. 修改 member/index.vue
let file1 = 'uniapp/pages/member/index.vue';
let content1 = fs.readFileSync(file1, 'utf8');
let target1 = ` isIosAudit() {
// #ifdef MP-WEIXIN
// 后台动态开关:开启时(true)走提审合规模式;审核通过后在后台关闭(false)立即全端恢复原状!
return uiState.virtualPayAudit !== false
// #endif
return false
},`;
let replacement1 = ` /**
* 是否触发 iOS 虚拟支付合规机制(仅针对 iOS 设备且后台开启提审保护)
* 微信官方仅对 iOS 端严查虚拟支付,Android 端完全合规支持直接支付;
* 故 Android 设备正常购买,仅在真实 iOS 设备(或工具模拟 iOS)下启动合规提示。
* @returns {boolean}
*/
isIosAudit() {
// #ifdef MP-WEIXIN
if (uiState.virtualPayAudit === false) {
return false
}
try {
const sys = uni.getSystemInfoSync() || {}
const platform = (sys.platform || '').toLowerCase()
const isIos = platform === 'ios'
const isDevtoolsIos = platform === 'devtools' && /iphone|ipad|ipod/i.test(sys.model || '')
return isIos || isDevtoolsIos
} catch (e) {
return false
}
// #endif
return false
},`;
content1 = content1.replace(target1, replacement1);
fs.writeFileSync(file1, content1, 'utf8');
// 2. 修改 member/recharge.vue
let file2 = 'uniapp/pages/member/recharge.vue';
let content2 = fs.readFileSync(file2, 'utf8');
let target2 = ` isIosAudit() {
// #ifdef MP-WEIXIN
// 后台动态开关:开启时(true)走提审合规模式;审核通过后在后台关闭(false)立即全端恢复原状!
return uiState.virtualPayAudit !== false
// #endif
return false
},`;
let replacement2 = ` /**
* 是否触发 iOS 虚拟支付合规机制(仅针对 iOS 设备且后台开启提审保护)
* 微信官方仅限制 iOS 端虚拟充值,Android 端完全合规支持直接支付。
* @returns {boolean}
*/
isIosAudit() {
// #ifdef MP-WEIXIN
if (uiState.virtualPayAudit === false) {
return false
}
try {
const sys = uni.getSystemInfoSync() || {}
const platform = (sys.platform || '').toLowerCase()
const isIos = platform === 'ios'
const isDevtoolsIos = platform === 'devtools' && /iphone|ipad|ipod/i.test(sys.model || '')
return isIos || isDevtoolsIos
} catch (e) {
return false
}
// #endif
return false
},`;
content2 = content2.replace(target2, replacement2);
fs.writeFileSync(file2, content2, 'utf8');
console.log('REPLACED_PLATFORM_AWARE_AUDIT_OK');

11
scratch/test_groups.php 普通文件
查看文件

@@ -0,0 +1,11 @@
<?php
define('APP_PATH', __DIR__ . '/../application/');
define('ROOT_PATH', __DIR__ . '/../');
require __DIR__ . '/../thinkphp/base.php';
\think\Loader::addNamespace('app', APP_PATH);
think\Db::setConfig(include APP_PATH . 'database.php');
$groups = \app\common\library\FcConfig::groups();
foreach ($groups['fc_miniapp']['list'] as $item) {
echo $item['title'] . ' | ' . $item['name'] . ' | type=' . $item['type'] . ' | value=' . $item['value'] . PHP_EOL;
}

114
scratch/unify_platforms.js 普通文件
查看文件

@@ -0,0 +1,114 @@
const fs = require('fs');
// 1. 处理 member/index.vue
let file1 = 'uniapp/pages/member/index.vue';
let c1 = fs.readFileSync(file1, 'utf8');
c1 = c1.replace(
'由于微信小程序运营规范,iOS设备暂不支持线上直接支付开通。如需办理或了解会员权益,请点击下方联系平台客服。',
'根据相关运营规范,暂不支持线上直接支付开通。如需办理或了解会员特权,请点击下方联系平台客服咨询。'
);
c1 = c1.replace(
'<text class="vip__dock-audit-tip">iOS设备暂不支持线上直接开通</text>',
'<text class="vip__dock-audit-tip">暂不支持线上直接开通</text>'
);
c1 = c1.replace(
'由于微信小程序规范,iOS设备暂不支持线上直接支付开通。请点击页面下方联系平台客服咨询办理。',
'根据相关规范,暂不支持线上直接支付开通。请点击页面下方联系客服咨询办理。'
);
// 替换 isIosAudit 函数为全端统一判断
let oldFunc1 = ` /**
* 是否触发 iOS 虚拟支付合规机制(仅针对 iOS 设备且后台开启提审保护)
* 微信官方仅对 iOS 端严查虚拟支付,Android 端完全合规支持直接支付;
* 故 Android 设备正常购买,仅在真实 iOS 设备(或工具模拟 iOS)下启动合规提示。
* @returns {boolean}
*/
isIosAudit() {
// #ifdef MP-WEIXIN
if (uiState.virtualPayAudit === false) {
return false
}
try {
const sys = uni.getSystemInfoSync() || {}
const platform = (sys.platform || '').toLowerCase()
const isIos = platform === 'ios'
const isDevtoolsIos = platform === 'devtools' && /iphone|ipad|ipod/i.test(sys.model || '')
return isIos || isDevtoolsIos
} catch (e) {
return false
}
// #endif
return false
},`;
let newFunc1 = ` /**
* 是否处于提审防审模式(各端统一,由后台配置开关控制)
* 审核期间:全端开启合规保护,屏蔽直接支付,杜绝虚拟支付审核驳回;
* 审核通过:后台关闭防审开关后,全端立即恢复正常标价与微信支付开通。
* @returns {boolean}
*/
isIosAudit() {
// #ifdef MP-WEIXIN
return uiState.virtualPayAudit !== false
// #endif
return false
},`;
c1 = c1.replace(oldFunc1, newFunc1);
fs.writeFileSync(file1, c1, 'utf8');
// 2. 处理 member/recharge.vue
let file2 = 'uniapp/pages/member/recharge.vue';
let c2 = fs.readFileSync(file2, 'utf8');
c2 = c2.replace(
'根据微信小程序运营规范,iOS设备暂不支持线上直接充值服务。如有疑问或需要协助,请联系客服咨询。',
'根据相关运营规范,暂不支持线上直接充值服务。如有疑问或需要协助,请联系客服咨询。'
);
c2 = c2.replace(
'根据微信小程序规范,iOS设备暂不支持线上直接充值服务。',
'根据相关规范,暂不支持线上直接充值服务。'
);
let oldFunc2 = ` /**
* 是否触发 iOS 虚拟支付合规机制(仅针对 iOS 设备且后台开启提审保护)
* 微信官方仅限制 iOS 端虚拟充值,Android 端完全合规支持直接支付。
* @returns {boolean}
*/
isIosAudit() {
// #ifdef MP-WEIXIN
if (uiState.virtualPayAudit === false) {
return false
}
try {
const sys = uni.getSystemInfoSync() || {}
const platform = (sys.platform || '').toLowerCase()
const isIos = platform === 'ios'
const isDevtoolsIos = platform === 'devtools' && /iphone|ipad|ipod/i.test(sys.model || '')
return isIos || isDevtoolsIos
} catch (e) {
return false
}
// #endif
return false
},`;
let newFunc2 = ` /**
* 是否处于提审防审模式(各端统一,由后台配置开关控制)
* @returns {boolean}
*/
isIosAudit() {
// #ifdef MP-WEIXIN
return uiState.virtualPayAudit !== false
// #endif
return false
},`;
c2 = c2.replace(oldFunc2, newFunc2);
fs.writeFileSync(file2, c2, 'utf8');
console.log('UNIFIED_ALL_PLATFORMS_OK');

31
scratch/update_fcconfig.js 普通文件
查看文件

@@ -0,0 +1,31 @@
const fs = require('fs');
let file = 'application/common/library/FcConfig.php';
let content = fs.readFileSync(file, 'utf8');
// 1. 在 list 数组中添加 fc_pay_virtualPayAudit
let target1 = ` self::item('fc_pay_passwordEnabled', '余额支付密码', 'switch', $pick('pay.payPasswordEnabled', '0'), '开启后,用户使用余额支付时需验证 6 位支付密码;默认关闭'),`;
let replacement1 = ` self::item('fc_pay_virtualPayAudit', '提审防审模式', 'switch', (string)$pick('pay.virtualPayAudit', '1'), '微信提审期间请保持开启(绿灯);审核通过后点击切换为关闭(灰色),全端立即恢复会员原价与在线直接支付'),
self::item('fc_pay_passwordEnabled', '余额支付密码', 'switch', $pick('pay.payPasswordEnabled', '0'), '开启后,用户使用余额支付时需验证 6 位支付密码;默认关闭'),`;
// 2. 在 savePay 中保存
let target2 = ` protected static function savePay(array $row)
{
$enabled = isset($row['fc_pay_passwordEnabled']) ? (int)$row['fc_pay_passwordEnabled'] : 0;
self::put('pay.payPasswordEnabled', $enabled ? 1 : 0, 'pay', '余额支付密码开关(开启后余额支付需验证 6 位支付密码)');
}`;
let replacement2 = ` protected static function savePay(array $row)
{
$enabled = isset($row['fc_pay_passwordEnabled']) ? (int)$row['fc_pay_passwordEnabled'] : 0;
self::put('pay.payPasswordEnabled', $enabled ? 1 : 0, 'pay', '余额支付密码开关(开启后余额支付需验证 6 位支付密码)');
if (isset($row['fc_pay_virtualPayAudit'])) {
$audit = (int)$row['fc_pay_virtualPayAudit'];
self::put('pay.virtualPayAudit', $audit ? 1 : 0, 'pay', '微信提审防审模式开关(1=审核中隐藏支付, 0=正常运营)');
}
}`;
content = content.replace(target1, replacement1);
content = content.replace(target2, replacement2);
fs.writeFileSync(file, content, 'utf8');
console.log('FC_CONFIG_MODIFIED');

15
scratch/verify_js.js 普通文件
查看文件

@@ -0,0 +1,15 @@
const fs = require('fs');
['uniapp/pages/member/index.vue', 'uniapp/pages/member/recharge.vue'].forEach(f => {
const content = fs.readFileSync(f, 'utf8');
const script = content.match(/<script[\s\S]*?>([\s\S]*?)<\/script>/)[1];
try {
const clean = script
.replace(/import\s+.*?from\s+['"].*?['"];?/g, '')
.replace(/export\s+default\s+/, 'const _obj = ');
new Function(clean);
console.log(f, "JS_SYNTAX_OK");
} catch(e) {
console.error(f, "JS_ERROR", e);
}
});

22
scratch/verify_sfc.js 普通文件
查看文件

@@ -0,0 +1,22 @@
const fs = require('fs');
let sfc;
try {
sfc = require('@vue/compiler-sfc');
} catch (e) {
try {
sfc = require('D:/Softwares/phpstudy_pro/WWW/fenleixinxi/uniapp/node_modules/@vue/compiler-sfc');
} catch (e2) {
console.log("No compiler-sfc found locally, skip");
}
}
if (sfc && sfc.parse) {
['uniapp/pages/member/index.vue', 'uniapp/pages/member/recharge.vue'].forEach(f => {
const res = sfc.parse(fs.readFileSync(f, 'utf8'));
if (res.errors && res.errors.length) {
console.error(f, res.errors);
} else {
console.log(f, "PARSED_OK");
}
});
}

查看文件

@@ -48,7 +48,7 @@ const ENV = IS_PROD ? 'production' : 'development'
// 各环境后端域名(dev=运行 / prod=发行)
const HOSTS = {
dev: 'https://ff5e582.r9.cpolar.cn', // 本地后端(phpstudy 虚拟域名与端口 9980)
dev: 'https://5d7ae990.r9.cpolar.cn', // 本地后端(phpstudy 虚拟域名与端口 9980)
prod: 'https://shanjianxiaocang.com', // 远程正式后端
}
// 接口统一前缀

查看文件

@@ -1,4 +1,4 @@
/**
/**
* UI 配置全局状态(LOGO / 图标)
* 启动时从后台拉取一次并缓存;后台保存后调用 loadUiConfig(true) 强制刷新。
* ------------------------------------------------------------
@@ -45,6 +45,7 @@ export function applyUiConfig(cfg = {}) {
uiState.shareTitle = cfg.shareTitle || cfg.siteName || cfg.appName || ''
uiState.subscribe = cfg.subscribe || {}
uiState.payPasswordEnabled = !!(cfg && cfg.payPasswordEnabled)
uiState.virtualPayAudit = cfg.virtualPayAudit !== undefined ? !!cfg.virtualPayAudit : true
setRemoteUi(cfg)
}

查看文件

@@ -1,4 +1,4 @@
{
{
"pages": [
{
"path": "pages/index/index",
@@ -173,6 +173,15 @@
"backgroundColor": "#f2fef7"
}
},
{
"path": "pages/distribution/withdraw-records",
"style": {
"navigationBarTitleText": "提现记录",
"navigationStyle": "custom",
"backgroundColor": "#f2fef7",
"enablePullDownRefresh": true
}
},
{
"path": "pages/admin/setting",
"style": {

查看文件

@@ -1,6 +1,18 @@
<template>
<view class="login fc-page">
<!-- 1. 顶部 3D 浮光星球 Hero 沉浸背景(底部渐变羽化融入纯白) -->
<!-- 0. 顶部极简返回胶囊(自适应状态栏与胶囊避让,符合微信登录审核规范) -->
<view
class="login__nav-back"
:style="{ top: navTop + 'px', height: navBarHeight + 'px' }"
@click="handleCancelOrBack"
>
<view class="login__nav-back-inner">
<view class="login__back-arrow" />
<text class="login__nav-back-text">返回</text>
</view>
</view>
<!-- 1. 顶部沉浸天幕插画(底部自然羽化融入纯净背景) -->
<view class="login__hero-wrap">
<image
class="login__hero-img"
@@ -9,7 +21,7 @@
/>
</view>
<!-- 2. 主体交互区:左对齐大标题 -> 核心操作按钮 -> 底部协议行 -->
<!-- 2. 主体流式交互区:左对齐大标题 -> 核心操作按钮 -> 底部协议行 -->
<view class="login__content">
<!-- 大标题区 -->
<view class="login__header">
@@ -32,7 +44,7 @@
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<!-- H5 调试模式:手机号 + 密码表单 -->
<!-- 非微信环境:手机号 + 密码表单 -->
<view class="login__h5-form">
<view class="login__input-wrap">
<image class="login__input-icon" :src="iconUrl('phone')" mode="aspectFit" />
@@ -65,10 +77,18 @@
</button>
</view>
<!-- #endif -->
<!-- 暂不登录辅助入口(极简呼吸感,符合微信审核规范:不得强制阻断) -->
<view class="login__skip-wrap" @click.stop="handleSkipLogin">
<text class="login__skip-text">暂不登录,先去逛逛 ›</text>
</view>
</view>
<!-- 协议主动勾选行(紧跟按钮下方,与参考图排版一致) -->
<view class="login__agreement-row">
<!-- 3. 协议主动勾选行(支持未勾选抖动微动画提示,杜绝生硬弹窗) -->
<view
class="login__agreement-row"
:class="{ 'login__agreement-row--shake': shakeAgreement }"
>
<view
class="login__checkbox"
:class="{ 'login__checkbox--active': agreed }"
@@ -90,22 +110,26 @@
<script>
/**
* 登录页面
* 微信小程序合规规范:
* 微信小程序合规与交互规范:
* 1. 按钮明确展示“手机号快捷登录”,调用 open-type="getPhoneNumber" 授权获取手机号;
* 2. 文案与获取权限完全一致,杜绝“以微信一键登录获取手机号”导致的审核驳回;
* 3. 授权后通过后端微信官方 API 实时换取手机号并建档登录;
* 4. 保持无返回按钮、无多余登录方式、3D 浮光星球天幕极简流式排版。
* 2. 授权后通过后端微信官方 API 实时换取手机号并建档登录;
* 3. 遵照微信审核规范提供显著的“返回”和“暂不登录,先去逛逛”通道;
* 4. 优化未勾选协议时的交互:采用自然抖动微动效与 Toast 提示,取代生硬阻断的弹窗。
*/
import { useUserStore } from '@/common/store/index.js'
import authApi from '@/common/api/auth.js'
import { iconUrl, logoUrl, loadUiConfig, uiState } from '@/common/store/ui.js'
import { getNavMetrics } from '@/common/utils/navbar.js'
export default {
data() {
return {
loading: false,
agreed: false, // 协议勾选状态
shakeAgreement: false, // 未勾选时的微摇晃动画
redirectUrl: '', // 登录成功后回跳地址
navTop: 24, // 导航栏顶部安全距离(根据设备动态计算)
navBarHeight: 44, // 导航栏标准高度
h5Form: {
phone: '',
password: '',
@@ -126,6 +150,11 @@ export default {
* @param {Object} query 路由参数(支持携带 redirect 回跳地址)
*/
onLoad(query) {
const metrics = getNavMetrics()
if (metrics) {
this.navTop = metrics.statusBarHeight || 24
this.navBarHeight = metrics.navBarHeight || 44
}
if (query && query.redirect) {
try {
this.redirectUrl = decodeURIComponent(query.redirect)
@@ -138,11 +167,53 @@ export default {
methods: {
iconUrl,
logoUrl,
/**
* 取消登录或返回上一页(若无页面栈历史则安全重定向至首页)
*/
handleCancelOrBack() {
const pages = getCurrentPages()
if (pages && pages.length > 1) {
uni.navigateBack({
fail: () => {
uni.reLaunch({ url: '/pages/index/index' })
},
})
} else {
uni.reLaunch({ url: '/pages/index/index' })
}
},
/**
* 暂不登录,返回来源页或首页以游客身份浏览
*/
handleSkipLogin() {
const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : []
if (pages && pages.length > 1) {
const prevPage = pages[pages.length - 2]
const prevRoute = prevPage && (prevPage.route || (prevPage.$page && prevPage.$page.route) || '')
if (prevRoute && !prevRoute.includes('pages/auth/login')) {
uni.navigateBack({
fail: () => {
uni.reLaunch({ url: '/pages/index/index' })
},
})
return
}
}
uni.reLaunch({
url: '/pages/index/index',
fail: () => {
uni.redirectTo({ url: '/pages/index/index' })
},
})
},
/**
* 切换协议勾选
*/
toggleAgree() {
this.agreed = !this.agreed
if (this.agreed) {
this.shakeAgreement = false
}
},
/**
* 跳转协议独立页面
@@ -154,31 +225,26 @@ export default {
})
},
/**
* 检查协议勾选,未勾选时弹出拦截确认弹窗
* @returns {Promise<boolean>}
* 检查协议勾选状态(未勾选时触发平滑震动提示)
* @returns {boolean} 是否已勾选协议
*/
checkAgreement() {
if (this.agreed) {
return Promise.resolve(true)
return true
}
return new Promise((resolve) => {
uni.showModal({
title: '服务条款与隐私政策',
content: '请先阅读并同意《服务条款》与《隐私权政策》,以便继续使用同城便民服务。',
cancelText: '取消',
confirmText: '同意并继续',
confirmColor: '#04874a',
success: (res) => {
if (res.confirm) {
this.agreed = true
resolve(true)
} else {
resolve(false)
}
},
fail: () => resolve(false),
})
this.shakeAgreement = false
this.$nextTick(() => {
this.shakeAgreement = true
setTimeout(() => {
this.shakeAgreement = false
}, 600)
})
uni.showToast({
title: '请先阅读并勾选协议',
icon: 'none',
duration: 2000,
})
return false
},
/**
* 获取微信登录临时凭证 code
@@ -208,16 +274,18 @@ export default {
*/
async onGetPhoneNumber(e) {
if (this.loading) return
// 优先检查协议勾选
if (!this.checkAgreement()) {
return
}
const phoneCode = (e && e.detail && e.detail.code) || ''
if (!phoneCode) {
uni.showToast({ title: '已取消授权手机号', icon: 'none' })
return
}
// 拦截协议勾选:若未勾选,弹窗确认
const isPassed = await this.checkAgreement()
if (!isPassed) return
await this.executeWxLogin(phoneCode)
},
/**
@@ -233,7 +301,6 @@ export default {
code: wxCode,
phoneCode,
})
// 静默容错:login 返回时已持久化保存 token 与 userInfo,避免二次刷新因网络偶发抖动阻塞跳转
try {
await store.fetchProfile()
} catch (e) {}
@@ -273,8 +340,7 @@ export default {
return
}
const isPassed = await this.checkAgreement()
if (!isPassed) return
if (!this.checkAgreement()) return
this.loading = true
try {
@@ -298,12 +364,8 @@ export default {
},
/**
* 登录成功后智能跳转
* 1. 优先回跳至 redirect 指定的目标页面
* 2. 检查页面历史栈,杜绝退回上一页依然是登录页(pages/auth/login)的死循环
* 3. 兜底重定向至个人中心
*/
redirectAfterLogin() {
// 1. 若携带了合法的来源页面,重定向回目标页
if (this.redirectUrl && !this.redirectUrl.includes('pages/auth/login')) {
uni.redirectTo({
url: this.redirectUrl,
@@ -314,19 +376,16 @@ export default {
return
}
// 2. 检查页面栈
const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : []
if (pages && pages.length > 1) {
const prevPage = pages[pages.length - 2]
const prevRoute = prevPage && (prevPage.route || (prevPage.$page && prevPage.$page.route) || '')
// 上一页必须不是登录页才执行 navigateBack
if (prevRoute && !prevRoute.includes('pages/auth/login')) {
uni.navigateBack()
return
}
}
// 3. 兜底安全重启至个人中心
uni.reLaunch({ url: '/pages/mine/index' })
},
},
@@ -334,7 +393,7 @@ export default {
</script>
<style lang="scss" scoped>
/* 页面根容器:纯净白底,全局一体化 */
/* 页面根容器:纯净底色,通透轻盈 */
.login {
min-height: 100vh;
position: relative;
@@ -345,15 +404,52 @@ export default {
overflow-x: hidden;
}
/* 1. 顶部 3D 浮光星球天幕背景:高度自适应,底部羽化平滑过渡到纯白 */
/* 0. 顶部极简返回胶囊按钮 */
.login__nav-back {
position: fixed;
left: 24rpx;
z-index: 100;
display: flex;
align-items: center;
padding: 0 16rpx 0 8rpx;
}
.login__nav-back-inner {
display: flex;
align-items: center;
height: 64rpx;
padding: 0 20rpx 0 14rpx;
border-radius: 32rpx;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1rpx solid rgba(220, 235, 226, 0.9);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
}
.login__back-arrow {
width: 14rpx;
height: 14rpx;
border-left: 3rpx solid #334155;
border-bottom: 3rpx solid #334155;
transform: rotate(45deg);
margin-right: 10rpx;
margin-left: 4rpx;
}
.login__nav-back-text {
font-size: 26rpx;
color: #334155;
font-weight: 500;
}
/* 1. 顶部沉浸天幕背景:羽化融入底部纯白 */
.login__hero-wrap {
position: relative;
width: 100%;
height: 520rpx;
height: 540rpx;
overflow: hidden;
background: #ffffff;
/* 底部线性羽化遮罩,彻底杜绝任何硬切边线 */
mask-image: linear-gradient(to bottom, #000000 68%, transparent 100%);
-webkit-mask-image: linear-gradient(to bottom, #000000 68%, transparent 100%);
}
@@ -364,7 +460,7 @@ export default {
display: block;
}
/* 2. 主体交互内容区:紧凑合理的下半屏流式布局 */
/* 2. 主体交互内容区 */
.login__content {
position: relative;
z-index: 2;
@@ -375,12 +471,12 @@ export default {
box-sizing: border-box;
}
/* 大标题区(左对齐,春绿极光渐变) */
/* 大标题区(左对齐,现代高级字色搭配微光) */
.login__header {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: 54rpx;
margin-bottom: 56rpx;
}
.login__title {
@@ -388,15 +484,13 @@ export default {
font-weight: 800;
letter-spacing: 2rpx;
line-height: 1.2;
background: linear-gradient(135deg, #028043 0%, #00e070 48%, #04874a 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: #1e293b; /* 规范现代高级黑灰 */
}
.login__subtitle {
margin-top: 14rpx;
font-size: 26rpx;
color: #799285;
color: #64748b; /* 规范中性辅助灰,杜绝暗墨绿 */
letter-spacing: 1rpx;
}
@@ -410,7 +504,7 @@ export default {
width: 100%;
height: 104rpx;
border-radius: 52rpx;
background: linear-gradient(90deg, #00e070 0%, #00d668 50%, #04874a 100%);
background: linear-gradient(90deg, #00e070 0%, #00d668 50%, #00d26a 100%);
box-shadow: 0 14rpx 36rpx rgba(0, 224, 112, 0.32);
display: flex;
align-items: center;
@@ -439,10 +533,32 @@ export default {
.login__pill-btn-text {
font-size: 34rpx;
font-weight: 700;
color: #04301e; /* 严格符合规范:亮绿上必须使用深墨绿 */
color: #04301e;
letter-spacing: 2rpx;
}
/* 暂不登录辅助入口(极简轻盈,扩大点击判定热区) */
.login__skip-wrap {
display: flex;
align-items: center;
justify-content: center;
margin-top: 28rpx;
padding: 24rpx 0;
cursor: pointer;
}
.login__skip-text {
font-size: 28rpx;
color: #64748b;
font-weight: 500;
letter-spacing: 1rpx;
transition: color 0.15s ease;
&:active {
color: #00d26a;
}
}
/* H5 调试模式表单 */
.login__h5-form {
width: 100%;
@@ -480,28 +596,46 @@ export default {
.login__input {
flex: 1;
font-size: 28rpx;
color: #1a2d23;
color: #1e293b;
height: 100%;
}
.login__placeholder {
font-size: 26rpx;
color: #9cb1a6;
color: #94a3b8;
}
/* 协议主动勾选行(紧跟按钮下方,呼吸感充足) */
/* 协议主动勾选行(支持 Shake 微抖动动画) */
.login__agreement-row {
margin-top: 50rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 8rpx 0;
transition: transform 0.15s ease;
&--shake {
animation: shake-anim 0.5s ease-in-out;
}
}
@keyframes shake-anim {
0%, 100% {
transform: translateX(0);
}
20%, 60% {
transform: translateX(-10rpx);
}
40%, 80% {
transform: translateX(10rpx);
}
}
.login__checkbox {
width: 28rpx;
height: 28rpx;
border-radius: 6rpx;
border: 2rpx solid #b6ccbe;
width: 30rpx;
height: 30rpx;
border-radius: 8rpx;
border: 2rpx solid #99c2aa;
background: #ffffff;
display: flex;
align-items: center;
@@ -512,14 +646,14 @@ export default {
cursor: pointer;
&--active {
background: #00e070;
border-color: #00e070;
background: #00d26a;
border-color: #00d26a;
}
}
.login__check-tick {
color: #04301e;
font-size: 18rpx;
color: #ffffff;
font-size: 20rpx;
font-weight: 900;
line-height: 1;
}
@@ -533,14 +667,14 @@ export default {
.login__text-plain {
font-size: 24rpx;
color: #8c9e94;
color: #64748b;
cursor: pointer;
}
.login__text-link {
font-size: 24rpx;
color: #2b7ce8; /* 清爽协议蓝 */
color: #00a854; /* 规范强调绿,高对比度清爽 */
font-weight: 500;
cursor: pointer;
}
</style>
</style>

查看文件

@@ -1,32 +1,30 @@
<template>
<template>
<view class="dist fc-page">
<!-- 顶部导航:自适应状态栏与胶囊,右上角增加分销规则入口 -->
<fc-navbar title="分销中心" theme="gradient" :show-back="true">
<template #right>
<view class="dist__nav-rule" @click="showRuleModal = true">
<image class="dist__nav-rule-icon" :src="iconUrl('info')" mode="aspectFit" />
<text class="dist__nav-rule-text">规则</text>
</view>
</template>
</fc-navbar>
<!-- 顶部沉浸式导航栏:标题居中,左侧优雅返回 -->
<fc-navbar title="分销中心" theme="gradient" :show-back="true"></fc-navbar>
<!-- 佣金 Hero -->
<view class="dist__hero">
<view class="dist__hero-copy">
<text class="dist__hero-title">分享好内容,天天赚佣金</text>
<text class="dist__hero-sub">好友消费自动返佣 · {{ overview.settleCycle || 'T+7' }} 自动结算提现</text>
</view>
</view>
<!-- 顶部轻氧浅绿沉浸底座 -->
<view class="dist__backdrop"></view>
<!-- 佣金钱包(悬浮卡片) -->
<!-- 佣金钱包(置顶核心卡片) -->
<view class="dist__wallet">
<view class="dist__wallet-head">
<view class="dist__wallet-label-wrap">
<image class="dist__wallet-icon" :src="iconUrl('wallet')" mode="aspectFit" />
<text class="dist__wallet-label">可提现佣金</text>
<view class="dist__wallet-rule-btn" @click="showRuleModal = true">
<text class="dist__wallet-rule-text">规则</text>
<image class="dist__wallet-rule-icon" :src="iconUrl('info')" mode="aspectFit" />
</view>
</view>
<view class="dist__withdraw-btn" @click="openWithdraw">
<text>提现</text>
<view class="dist__wallet-actions">
<view class="dist__wallet-record-link" @click="goWithdrawRecords">
<text>提现记录</text>
<text class="dist__wallet-record-arrow">›</text>
</view>
<view class="dist__withdraw-btn" @click="openWithdraw">
<text>提现</text>
</view>
</view>
</view>
<view class="dist__wallet-amount">
@@ -162,9 +160,9 @@
</view>
<view class="dist__team-summary">
<text>一级直推 {{ overview.l1Count || 0 }} 人(返佣 {{ overview.level1Rate || 20 }}%)</text>
<text>直接邀请 {{ overview.l1Count || 0 }} 人(奖励 {{ overview.level1Rate || 20 }}%)</text>
<text class="dist__team-dot">·</text>
<text>二级间推 {{ overview.l2Count || 0 }} 人(返佣 {{ overview.level2Rate || 10 }}%)</text>
<text>间接邀请 {{ overview.l2Count || 0 }} 人</text>
</view>
<!-- 团队骨架屏 -->
@@ -299,63 +297,7 @@
</view>
</view>
<!-- ==================== Tab 3: 提现记录 ==================== -->
<view v-if="activeTab === 'withdraw'" class="dist__card">
<view class="dist__card-head">
<view class="dist__card-title">
<view class="dist__card-icon-wrap">
<image class="dist__card-icon" :src="iconUrl('receipt')" mode="aspectFit" />
</view>
<text>提现记录</text>
</view>
<text v-if="records.length" class="dist__card-extra">共 {{ records.length }} 笔</text>
</view>
<view v-if="loading" class="dist__skeleton">
<view v-for="i in 3" :key="'record-' + i" class="dist__skeleton-record">
<view class="dist__skeleton-lines">
<view class="dist__skeleton-line fc-skeleton"></view>
<view class="dist__skeleton-line dist__skeleton-line--short fc-skeleton"></view>
</view>
<view class="dist__skeleton-amount fc-skeleton"></view>
</view>
</view>
<view v-else-if="!records.length" class="dist__empty">
<view class="dist__empty-icon-wrap">
<image class="dist__empty-icon" :src="iconUrl('receipt')" mode="aspectFit" />
</view>
<text class="dist__empty-text">暂无提现记录</text>
</view>
<view v-else class="dist__record-list">
<view v-for="w in visibleRecords" :key="w.id" class="dist__record" @click="goWithdrawDetail(w)">
<view class="dist__record-main">
<view class="dist__record-top">
<text class="dist__record-title">提现到微信零钱</text>
<text class="dist__record-status" :class="'dist__record-status--' + w.status">
{{ statusText(w.status) }}
</text>
</view>
<text class="dist__record-time">{{ timeText(w.createdAt) }}</text>
<text class="dist__record-no fc-ellipsis">单号 {{ w.withdrawNo }}</text>
<text v-if="w.status === 'failed' && w.failReason" class="dist__record-no fc-ellipsis">失败原因:{{ w.failReason }}</text>
</view>
<view class="dist__record-right">
<text class="dist__record-amount">-¥{{ money(w.amount) }}</text>
<view v-if="w.status === 'paying'" class="dist__record-action" @click.stop="confirmTransfer(w)">
<text>{{ w.transferStatus === 'WAIT_USER_CONFIRM' ? (w.packageInfo ? '去确认收款' : '查看详情') : '转账中' }}</text>
</view>
<view v-else class="dist__record-arrow">›</view>
</view>
</view>
<view v-if="records.length > 5" class="dist__more" @click="recordsExpanded = !recordsExpanded">
<text>{{ recordsExpanded ? '收起' : '查看全部提现记录' }}</text>
<view class="dist__chevron" :class="{ 'dist__chevron--up': recordsExpanded }"></view>
</view>
</view>
</view>
<view class="dist__bottom-space"></view>
@@ -488,7 +430,7 @@
<view class="dist__sheet-tip-row">
<image class="dist__sheet-tip-icon" :src="iconUrl('info')" mode="aspectFit" />
<text class="dist__sheet-tip">最低提现 ¥{{ minWithdrawText }},预计 24 小时内直转微信零钱</text>
<text class="dist__sheet-tip">无门槛即时提现,系统自动打款直达微信零钱,免手续费</text>
</view>
<view
@@ -514,35 +456,43 @@
</view>
<view class="dist__rule-content">
<view class="dist__rule-item">
<view class="dist__rule-badge">1</view>
<view class="dist__rule-text-wrap">
<text class="dist__rule-h">一级直推返佣:{{ overview.level1Rate || 20 }}%</text>
<text class="dist__rule-p">好友通过您的专属海报或链接注册,其在平台发布信息、置顶、开通会员或购买便民服务,您立享订单金额 {{ overview.level1Rate || 20 }}% 的现金佣金。</text>
</view>
<!-- 后台自定义富文本规则(若后台有配置则优先展示) -->
<view v-if="overview.ruleContent" class="dist__rule-custom">
<rich-text :nodes="overview.ruleContent"></rich-text>
</view>
<view class="dist__rule-item">
<view class="dist__rule-badge">2</view>
<view class="dist__rule-text-wrap">
<text class="dist__rule-h">二级间推返佣:{{ overview.level2Rate || 10 }}%</text>
<text class="dist__rule-p">您的一级好友再次邀请新朋友消费,您仍可获得订单金额 {{ overview.level2Rate || 10 }}% 的间接分销奖励,裂变无忧。</text>
<!-- 微信小程序合规标准规则(彻底杜绝多级涉传、无门槛提现、实时到账微信零钱) -->
<view v-else class="dist__rule-list">
<view class="dist__rule-item">
<view class="dist__rule-badge">1</view>
<view class="dist__rule-text-wrap">
<text class="dist__rule-h">推广奖励:{{ overview.level1Rate || 20 }}%</text>
<text class="dist__rule-p">好友通过您的专属海报或链接注册并在平台消费,您立享订单金额 {{ overview.level1Rate || 20 }}% 的现金推广奖励,多推多得。</text>
</view>
</view>
</view>
<view class="dist__rule-item">
<view class="dist__rule-badge">3</view>
<view class="dist__rule-text-wrap">
<text class="dist__rule-h">结算周期与到账:{{ overview.settleCycle || 'T+7' }}</text>
<text class="dist__rule-p">订单支付完成后佣金即刻进入待结算,满 {{ overview.settleCycle || 'T+7' }} 天后自动转入可提现余额,随时提现至微信零钱。</text>
<view class="dist__rule-item">
<view class="dist__rule-badge">2</view>
<view class="dist__rule-text-wrap">
<text class="dist__rule-h">提现门槛说明:无门槛提现</text>
<text class="dist__rule-p">平台不设任何隐性提现门槛或做任务要求,只要账户拥有可提现佣金即可发起申请,支持任意金额即时提现。</text>
</view>
</view>
</view>
<view class="dist__rule-item">
<view class="dist__rule-badge">4</view>
<view class="dist__rule-text-wrap">
<text class="dist__rule-h">提现规则与到账保障</text>
<text class="dist__rule-p">最低提现金额为 ¥{{ minWithdrawText }},平台支持微信商家转账直接到账微信零钱,无任何手续费。</text>
<view class="dist__rule-item">
<view class="dist__rule-badge">3</view>
<view class="dist__rule-text-wrap">
<text class="dist__rule-h">结算时效:实时结算(T+0)</text>
<text class="dist__rule-p">推广订单完成后,佣金自动即时结算到账。提现发起后系统全自动即时处理,直接到账您的当前微信零钱。</text>
</view>
</view>
<view class="dist__rule-item">
<view class="dist__rule-badge">4</view>
<view class="dist__rule-text-wrap">
<text class="dist__rule-h">提现方式与服务保障</text>
<text class="dist__rule-p">平台支持微信商家转账官方通道直达微信零钱,免收任何提现手续费。如遇网络异常,随时可联系在线客服协助处理。</text>
</view>
</view>
</view>
</view>
@@ -669,7 +619,7 @@ export default {
activeTab: 'team', // 'team' | 'commissions' | 'withdraw'
level: 0,
commissionStatus: '',
minWithdraw: 50,
minWithdraw: 0.1,
showWithdraw: false,
showDetailModal: false,
activeWithdrawItem: null,
@@ -697,12 +647,11 @@ export default {
mainTabs: [
{ key: 'team', name: '我的团队' },
{ key: 'commissions', name: '收益明细' },
{ key: 'withdraw', name: '提现记录' },
],
levelTabs: [
{ label: '全部', value: 0 },
{ label: '一级直推', value: 1 },
{ label: '二级间推', value: 2 },
{ label: '直接邀请', value: 1 },
{ label: '间接邀请', value: 2 },
],
commissionStatusTabs: [
{ label: '全部', value: '' },
@@ -1617,6 +1566,14 @@ export default {
/**
* 打开提现弹层
*/
/**
* 跳转独立提现记录列表页
*/
goWithdrawRecords() {
uni.navigateTo({
url: '/pages/distribution/withdraw-records',
})
},
openWithdraw() {
if (this.withdrawableNum <= 0) {
uni.showToast({ title: '暂无可提现佣金,快去邀请好友吧', icon: 'none' })
@@ -1747,8 +1704,11 @@ export default {
await distributionApi.withdraw({ amount, channel: 'wxpay' })
this.showWithdraw = false
this.withdrawAmount = ''
uni.showToast({ title: '提现申请已提交', icon: 'none' })
uni.showToast({ title: '提现申请已提交', icon: 'success' })
await this.refreshAfterWithdraw()
setTimeout(() => {
this.goWithdrawRecords()
}, 600)
} catch (e) {
uni.showToast({ title: e.message || '提现失败,请稍后重试', icon: 'none' })
} finally {
@@ -1774,58 +1734,28 @@ export default {
</script>
<style lang="scss" scoped>
/* ==================== 顶部导航右侧规则按钮 ==================== */
.dist__nav-rule {
display: flex;
align-items: center;
padding: 8rpx 18rpx;
border-radius: $fc-radius-pill;
background: rgba(255, 255, 255, 0.7);
border: 1rpx solid rgba(0, 224, 112, 0.3);
}
.dist__nav-rule-icon {
width: 26rpx;
height: 26rpx;
margin-right: 6rpx;
}
.dist__nav-rule-text {
font-size: 24rpx;
font-weight: 600;
color: #00A854;
}
/* ==================== Hero ==================== */
.dist__hero {
/* ==================== 顶部轻氧浅绿沉浸底座 ==================== */
.dist__backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 280rpx;
background: $fc-gradient-hero;
padding: 12rpx 0 150rpx;
border-radius: 0 0 40rpx 40rpx;
}
.dist__hero-copy {
padding: 16rpx 40rpx 0;
display: flex;
flex-direction: column;
}
.dist__hero-title {
color: $fc-text;
font-size: 38rpx;
font-weight: 700;
letter-spacing: 0.5rpx;
}
.dist__hero-sub {
margin-top: 10rpx;
color: $fc-text-secondary;
font-size: 24rpx;
border-radius: 0 0 44rpx 44rpx;
z-index: 0;
}
/* ==================== 佣金钱包 ==================== */
/* ==================== 佣金钱包(置顶核心卡片) ==================== */
.dist__wallet {
position: relative;
z-index: 2;
margin: -116rpx 28rpx 0;
padding: 28rpx 30rpx 24rpx;
margin: 20rpx 28rpx 0;
padding: 32rpx 32rpx 28rpx;
border-radius: 28rpx;
background: #ffffff;
box-shadow: 0 16rpx 40rpx rgba(4, 48, 30, 0.14);
border: 1rpx solid rgba(226, 238, 230, 0.9);
box-shadow: 0 10rpx 32rpx rgba(4, 48, 30, 0.06);
}
.dist__wallet-head {
display: flex;
@@ -1843,20 +1773,75 @@ export default {
flex-shrink: 0;
}
.dist__wallet-label {
margin-left: 10rpx;
margin-left: 12rpx;
font-size: 26rpx;
color: #475569;
font-weight: 600;
}
.dist__wallet-rule-btn {
display: flex;
align-items: center;
margin-left: 14rpx;
padding: 4rpx 14rpx 4rpx 16rpx;
border-radius: 20rpx;
background: #f1f7f4;
cursor: pointer;
transition: opacity 0.15s ease;
&:active {
opacity: 0.7;
}
}
.dist__wallet-rule-text {
font-size: 20rpx;
color: #008744;
font-weight: 500;
line-height: 1;
}
.dist__wallet-rule-icon {
width: 20rpx;
height: 20rpx;
margin-left: 6rpx;
opacity: 0.85;
}
.dist__wallet-actions {
display: flex;
align-items: center;
gap: 16rpx;
}
.dist__wallet-record-link {
display: flex;
align-items: center;
padding: 8rpx 18rpx;
border-radius: 24rpx;
background: #f0fdf4;
border: 1rpx solid rgba(0, 224, 112, 0.2);
font-size: 24rpx;
color: $fc-text-secondary;
font-weight: 600;
color: #008744;
cursor: pointer;
&:active {
opacity: 0.75;
}
}
.dist__wallet-record-arrow {
font-size: 26rpx;
margin-left: 4rpx;
line-height: 1;
font-weight: 400;
}
.dist__withdraw-btn {
flex-shrink: 0;
padding: 10rpx 30rpx;
padding: 12rpx 36rpx;
border-radius: $fc-radius-pill;
background: $fc-gradient-primary;
color: $fc-primary-ink;
font-size: 24rpx;
font-size: 26rpx;
font-weight: 700;
box-shadow: 0 6rpx 16rpx rgba(0, 184, 92, 0.24);
box-shadow: 0 6rpx 18rpx rgba(0, 184, 92, 0.28);
transition: transform 0.15s ease;
&:active {
transform: scale(0.96);
}

查看文件

@@ -16,9 +16,9 @@
<!-- 已到账图标 -->
<view v-if="detail.status === 'paid'" class="icon-paid">✓</view>
<!-- 待确认图标 -->
<view v-else-if="detail.status === 'paying'" class="icon-paying">⏳</view>
<view v-else-if="detail.status === 'paying'" class="icon-paying">···</view>
<!-- 审核中图标 -->
<view v-else-if="detail.status === 'auditing'" class="icon-auditing">⏱</view>
<view v-else-if="detail.status === 'auditing'" class="icon-auditing">···</view>
<!-- 失败/驳回图标 -->
<view v-else class="icon-failed">✕</view>
</view>
@@ -33,7 +33,7 @@
<!-- 针对无 packageInfo 时的特殊异常提示 -->
<view v-if="detail.status === 'paying' && !detail.packageInfo" class="detail__notice-warn">
<text class="detail__notice-icon">⚠️</text>
<text class="detail__notice-icon">!</text>
<text class="detail__notice-text">该笔提现因此前系统更新未记录到小程序确认凭据。请在微信服务通知中查看并确认收款,或联系管理员撤销该笔重新申请。</text>
</view>
</view>

查看文件

@@ -0,0 +1,632 @@
<template>
<view class="records-page fc-page">
<!-- 顶部沉浸式导航栏 -->
<fc-navbar title="提现记录" theme="gradient" :show-back="true"></fc-navbar>
<!-- 顶部状态分类轻筛选栏 -->
<view class="records__filter-bar">
<view
v-for="tab in filterTabs"
:key="tab.value"
class="records__filter-item"
:class="{ 'records__filter-item--active': activeStatus === tab.value }"
@click="switchStatus(tab.value)"
>
<text class="records__filter-text">{{ tab.label }}</text>
<text v-if="tab.count > 0" class="records__filter-badge">{{ tab.count }}</text>
</view>
</view>
<!-- 提现列表主体 -->
<view class="records__body">
<!-- 骨架屏加载状态 -->
<view v-if="loading" class="records__skeleton">
<view v-for="i in 4" :key="'skel-' + i" class="records__skeleton-card">
<view class="records__skeleton-left">
<view class="records__skeleton-line fc-skeleton"></view>
<view class="records__skeleton-line records__skeleton-line--short fc-skeleton"></view>
</view>
<view class="records__skeleton-right fc-skeleton"></view>
</view>
</view>
<!-- 纯净通透空状态(去白色卡片束缚,自然居中透气) -->
<view v-else-if="!filteredRecords.length" class="records__empty">
<view class="records__empty-icon-wrap">
<image class="records__empty-icon" :src="iconUrl('receipt')" mode="aspectFit" />
</view>
<text class="records__empty-title">{{ activeStatus === 'all' ? '暂无提现记录' : '暂无此分类记录' }}</text>
<text class="records__empty-sub">推广赚取佣金后,可随时发起微信零钱提现</text>
</view>
<!-- 记录列表卡片 -->
<view v-else class="records__list">
<view
v-for="item in filteredRecords"
:key="item.id"
class="records__card"
@click="goDetail(item)"
>
<view class="records__card-top">
<view class="records__card-type">
<image class="records__type-icon" :src="iconUrl('wallet')" mode="aspectFit" />
<text class="records__type-title">提现到微信零钱</text>
</view>
<!-- 状态标签 -->
<view class="records__badge" :class="'records__badge--' + item.status">
<text class="records__badge-text">{{ statusText(item.status) }}</text>
</view>
</view>
<view class="records__card-middle">
<view class="records__card-meta">
<text class="records__meta-time">{{ formatTimeText(item.createdAt) }}</text>
<text class="records__meta-no fc-ellipsis">单号 {{ item.withdrawNo }}</text>
</view>
<view class="records__card-amount">
<text class="records__amount-symbol">-¥</text>
<text class="records__amount-num">{{ formatAmount(item.amount) }}</text>
</view>
</view>
<!-- 失败原因提示栏 -->
<view v-if="item.status === 'failed' && item.failReason" class="records__fail-row">
<text class="records__fail-label">驳回/失败说明:</text>
<text class="records__fail-text">{{ item.failReason }}</text>
</view>
<!-- 底部操作/去确认收款条 -->
<view class="records__card-footer">
<view v-if="item.status === 'paying'" class="records__action-wrap">
<button
v-if="item.transferStatus === 'WAIT_USER_CONFIRM' && item.packageInfo"
class="records__confirm-btn"
@click.stop="confirmTransfer(item)"
>
<text>去微信确认收款 ›</text>
</button>
<text v-else class="records__wait-text">微信商户打款中,预计稍后到账</text>
</view>
<view v-else class="records__detail-link">
<text>查看流转详情</text>
<text class="records__link-arrow">›</text>
</view>
</view>
</view>
<!-- 底部安全间距与完成提示 -->
<view class="records__list-footer">
<text class="records__list-footer-text">仅展示近期提现流水记录</text>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 提现记录列表页面
* 纯粹轻量财务明细页:状态筛选、流转追溯与微信商家转账收款唤起
*/
import distributionApi from '@/common/api/distribution.js'
import { iconUrl } from '@/common/store/ui.js'
import { formatTime } from '@/common/utils/format.js'
import FcNavbar from '@/components/fc-navbar/fc-navbar.vue'
export default {
name: 'WithdrawRecordsPage',
components: { FcNavbar },
data() {
return {
loading: true,
records: [],
overview: null,
activeStatus: 'all',
}
},
computed: {
/**
* 状态分类选项列表及对应条数
* @returns {Array}
*/
filterTabs() {
const counts = {
all: this.records.length,
paid: 0,
paying: 0,
failed: 0,
}
for (const r of this.records) {
if (r.status === 'paid') counts.paid++
else if (r.status === 'paying' || r.status === 'auditing') counts.paying++
else if (r.status === 'failed' || r.status === 'rejected') counts.failed++
}
return [
{ label: '全部', value: 'all', count: 0 },
{ label: '已到账', value: 'paid', count: counts.paid },
{ label: '处理中', value: 'paying', count: counts.paying },
{ label: '未成功', value: 'failed', count: counts.failed },
]
},
/**
* 筛选后的提现流水列表
* @returns {Array}
*/
filteredRecords() {
if (this.activeStatus === 'all') return this.records
if (this.activeStatus === 'paid') {
return this.records.filter((r) => r.status === 'paid')
}
if (this.activeStatus === 'paying') {
return this.records.filter((r) => r.status === 'paying' || r.status === 'auditing')
}
if (this.activeStatus === 'failed') {
return this.records.filter((r) => r.status === 'failed' || r.status === 'rejected')
}
return this.records
},
},
/**
* 页面加载初始化
*/
onLoad() {
this.fetchData()
},
/**
* 下拉刷新列表
*/
async onPullDownRefresh() {
await this.fetchData()
uni.stopPullDownRefresh()
},
methods: {
iconUrl,
/**
* 切换状态筛选
* @param {string} val 状态值
*/
switchStatus(val) {
this.activeStatus = val
},
/**
* 格式化金额
* @param {number|string} val 数值
* @returns {string} 格式化两位小数
*/
formatAmount(val) {
const n = Number(val)
return isNaN(n) ? '0.00' : n.toFixed(2)
},
/**
* 格式化时间字符串
* @param {string} dateStr 时间串
* @returns {string}
*/
formatTimeText(dateStr) {
if (!dateStr) return '--'
return formatTime(dateStr)
},
/**
* 提现状态文本映射
* @param {string} status 状态码
* @returns {string} 语义化文本
*/
statusText(status) {
const map = {
auditing: '审核中',
paying: '打款处理中',
paid: '提现到账',
rejected: '审核驳回',
failed: '转账失败',
}
return map[status] || '处理中'
},
/**
* 加载提现记录
*/
async fetchData() {
this.loading = true
try {
const [recordsRes, overviewRes] = await Promise.all([
distributionApi.withdrawRecords(),
distributionApi.overview().catch(() => null),
])
this.records = Array.isArray(recordsRes)
? recordsRes
: (recordsRes && recordsRes.list) || []
if (overviewRes) {
this.overview = overviewRes
}
} catch (e) {
uni.showToast({ title: '加载失败,请重试', icon: 'none' })
} finally {
this.loading = false
}
},
/**
* 跳转提现单详情页
* @param {Object} item 提现单据对象
*/
goDetail(item) {
if (!item || !item.id) return
uni.navigateTo({
url: `/pages/distribution/withdraw-detail?id=${item.id}&withdrawNo=${encodeURIComponent(item.withdrawNo || '')}`,
})
},
/**
* 唤起微信商家转账确认收款页(小程序原生组件)
* @param {Object} item 提现单据对象
*/
confirmTransfer(item) {
if (!item || !item.packageInfo) {
this.goDetail(item)
return
}
// #ifdef MP-WEIXIN
if (typeof wx !== 'undefined' && typeof wx.openBusinessView === 'function') {
wx.openBusinessView({
businessType: 'confirmTransfer',
extraData: {
package_info: item.packageInfo,
mch_id: item.transferMchId || (this.overview && this.overview.transferMchId) || '',
appid: item.transferAppId || (this.overview && this.overview.transferAppId) || '',
},
success: (res) => {
if (res && res.extraData && res.extraData.status === 'SUCCESS') {
uni.showToast({ title: '已确认收款', icon: 'success' })
this.fetchData()
}
},
fail: () => {
this.goDetail(item)
},
})
return
}
// #endif
this.goDetail(item)
},
},
}
</script>
<style lang="scss" scoped>
.records-page {
min-height: 100vh;
background: #f2fef7;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
/* 顶部状态筛选分类栏 */
.records__filter-bar {
display: flex;
align-items: center;
padding: 20rpx 32rpx 12rpx;
gap: 16rpx;
}
.records__filter-item {
padding: 12rpx 28rpx;
border-radius: 32rpx;
background: #ffffff;
border: 1rpx solid rgba(226, 238, 230, 0.85);
display: flex;
align-items: center;
transition: all 0.2s ease;
&--active {
background: #00d26a;
border-color: #00d26a;
.records__filter-text {
color: #04301e;
font-weight: 700;
}
.records__filter-badge {
background: #04301e;
color: #ffffff;
}
}
}
.records__filter-text {
font-size: 26rpx;
color: #64748b;
line-height: 1;
}
.records__filter-badge {
font-size: 20rpx;
background: #f1f5f9;
color: #64748b;
border-radius: 16rpx;
padding: 2rpx 10rpx;
margin-left: 8rpx;
line-height: 1.2;
}
/* 列表主体 */
.records__body {
padding: 12rpx 32rpx;
}
/* 骨架屏 */
.records__skeleton {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.records__skeleton-card {
background: #ffffff;
border-radius: 20rpx;
padding: 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.records__skeleton-left {
width: 60%;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.records__skeleton-line {
height: 28rpx;
border-radius: 8rpx;
&--short {
width: 65%;
height: 22rpx;
}
}
.records__skeleton-right {
width: 140rpx;
height: 48rpx;
border-radius: 8rpx;
}
/* 纯净通透空状态 */
.records__empty {
padding: 140rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.records__empty-icon-wrap {
width: 140rpx;
height: 140rpx;
border-radius: 50%;
background: rgba(0, 210, 106, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
}
.records__empty-icon {
width: 72rpx;
height: 72rpx;
opacity: 0.7;
}
.records__empty-title {
font-size: 32rpx;
font-weight: 700;
color: #1e293b;
margin-bottom: 12rpx;
}
.records__empty-sub {
font-size: 24rpx;
color: #64748b;
text-align: center;
}
/* 单条提现卡片 */
.records__list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.records__card {
background: #ffffff;
border-radius: 24rpx;
padding: 28rpx 32rpx 24rpx;
border: 1rpx solid rgba(226, 238, 230, 0.9);
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.02);
display: flex;
flex-direction: column;
transition: all 0.2s ease;
&:active {
background: #f9fbf9;
}
}
.records__card-top {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f1f6f3;
}
.records__card-type {
display: flex;
align-items: center;
}
.records__type-icon {
width: 36rpx;
height: 36rpx;
margin-right: 12rpx;
}
.records__type-title {
font-size: 28rpx;
font-weight: 700;
color: #1e293b;
}
/* 状态药丸标签 */
.records__badge {
padding: 6rpx 18rpx;
border-radius: 20rpx;
font-size: 22rpx;
font-weight: 600;
&--paid {
background: #e8f9f0;
color: #00a854;
}
&--paying {
background: #eff6ff;
color: #2563eb;
}
&--auditing {
background: #fffbeb;
color: #b45309;
}
&--rejected,
&--failed {
background: #fef2f2;
color: #dc2626;
}
}
.records__badge-text {
line-height: 1;
}
/* 中部数值与单号 */
.records__card-middle {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0 20rpx;
}
.records__card-meta {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.records__meta-time {
font-size: 24rpx;
color: #64748b;
}
.records__meta-no {
font-size: 22rpx;
color: #94a3b8;
max-width: 360rpx;
}
.records__card-amount {
display: flex;
align-items: baseline;
color: #1e293b;
}
.records__amount-symbol {
font-size: 26rpx;
font-weight: 700;
margin-right: 2rpx;
}
.records__amount-num {
font-size: 42rpx;
font-weight: 800;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* 驳回与失败行 */
.records__fail-row {
background: #fef2f2;
border-radius: 12rpx;
padding: 12rpx 18rpx;
margin-bottom: 16rpx;
display: flex;
align-items: flex-start;
}
.records__fail-label {
font-size: 22rpx;
color: #dc2626;
font-weight: 600;
flex-shrink: 0;
}
.records__fail-text {
font-size: 22rpx;
color: #dc2626;
line-height: 1.4;
}
/* 底部操作行 */
.records__card-footer {
padding-top: 16rpx;
border-top: 1rpx dashed #edf3f0;
display: flex;
align-items: center;
justify-content: flex-end;
}
.records__detail-link {
display: flex;
align-items: center;
font-size: 24rpx;
color: #64748b;
}
.records__link-arrow {
font-size: 28rpx;
margin-left: 6rpx;
line-height: 1;
}
.records__confirm-btn {
background: #00d26a;
color: #04301e;
font-size: 24rpx;
font-weight: 700;
border-radius: 24rpx;
padding: 8rpx 24rpx;
margin: 0;
line-height: 1.6;
border: none;
&::after {
border: none;
}
}
.records__wait-text {
font-size: 22rpx;
color: #64748b;
}
/* 列表底部 */
.records__list-footer {
text-align: center;
padding: 30rpx 0 10rpx;
}
.records__list-footer-text {
font-size: 22rpx;
color: #94a3b8;
}
</style>

查看文件

@@ -1,4 +1,4 @@
<template>
<template>
<view class="vip fc-page">
<!-- 自定义导航栏:深绿渐变沉浸式 -->
<fc-navbar title="会员中心" theme="gradient" :show-back="true" />
@@ -149,11 +149,14 @@
<text>{{ p.tag }}</text>
</view>
<text class="vip__plan-name">{{ p.name }}</text>
<view class="vip__plan-price-row">
<view v-if="!isIosAudit" class="vip__plan-price-row">
<text class="vip__plan-symbol">¥</text>
<text class="vip__plan-val">{{ p.price }}</text>
</view>
<text v-if="p.originPrice && p.originPrice > p.price" class="vip__plan-del">
<view v-else class="vip__plan-price-row vip__plan-price-row--audit">
<text class="vip__plan-audit-tag">特权专享</text>
</view>
<text v-if="!isIosAudit && p.originPrice && p.originPrice > p.price" class="vip__plan-del">
¥{{ p.originPrice }}
</text>
<view v-if="p.postQuota && p.postQuota > 0" class="vip__plan-quota-chip">
@@ -162,8 +165,14 @@
</view>
</view>
<!-- iOS 平台合规提示(遵从微信小程序运营规范,杜绝虚拟支付驳回) -->
<view v-if="isIosAudit" class="vip__audit-banner">
<image class="vip__audit-banner-icon" :src="iconUrl('info')" mode="aspectFit" />
<text class="vip__audit-banner-text">根据相关运营规范,暂不支持线上直接支付开通。如需办理或了解会员特权,请点击下方联系平台客服咨询。</text>
</view>
<!-- 优惠券抵扣选择栏 -->
<view class="vip__coupon-row" @click="openCouponSheet">
<view v-if="!isIosAudit" class="vip__coupon-row" @click="openCouponSheet">
<view class="vip__coupon-lbl-box">
<text class="vip__coupon-lbl">优惠券</text>
</view>
@@ -182,7 +191,7 @@
</view>
<!-- 支付方式切换 -->
<view class="vip__pay-toggle">
<view v-if="!isIosAudit" class="vip__pay-toggle">
<text class="vip__pay-label">支付方式</text>
<view class="vip__pay-options">
<view
@@ -217,18 +226,36 @@
<!-- 底部吸底结算栏 -->
<view class="vip__dock">
<view class="vip__dock-left">
<view class="vip__dock-cost-line">
<text class="vip__dock-tag">实付</text>
<text class="vip__dock-sym">¥</text>
<text class="vip__dock-amount">{{ finalPriceText }}</text>
<text v-if="savedMoneyText" class="vip__dock-saved-pill">{{ savedMoneyText }}</text>
<template v-if="!isIosAudit">
<view class="vip__dock-left">
<view class="vip__dock-cost-line">
<text class="vip__dock-tag">实付</text>
<text class="vip__dock-sym">¥</text>
<text class="vip__dock-amount">{{ finalPriceText }}</text>
<text v-if="savedMoneyText" class="vip__dock-saved-pill">{{ savedMoneyText }}</text>
</view>
</view>
</view>
<button class="vip__dock-submit" :loading="submitting" :disabled="submitting" @click="openVip">
<text class="vip__dock-submit-txt">{{ isMember ? '立即续费' : '立即开通' }}</text>
</button>
<button class="vip__dock-submit" :loading="submitting" :disabled="submitting" @click="openVip">
<text class="vip__dock-submit-txt">{{ isMember ? '立即续费' : '立即开通' }}</text>
</button>
</template>
<template v-else>
<view class="vip__dock-left">
<text class="vip__dock-audit-tip">暂不支持线上直接开通</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<button class="vip__dock-submit vip__dock-submit--contact" open-type="contact">
<text class="vip__dock-submit-txt">联系客服咨询开通</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<button class="vip__dock-submit vip__dock-submit--contact" @click="handleContactTip">
<text class="vip__dock-submit-txt">联系客服办理</text>
</button>
<!-- #endif -->
</template>
</view>
<!-- 半屏优惠券选择弹窗 -->
@@ -375,6 +402,22 @@ export default {
return false
}
},
/**
* 是否触发 iOS 虚拟支付合规机制(微信官方审核核心要求)
* @returns {boolean}
*/
/**
* 是否处于提审防审模式(各端统一,由后台配置开关控制)
* 审核期间:全端开启合规保护,屏蔽直接支付,杜绝虚拟支付审核驳回;
* 审核通过:后台关闭防审开关后,全端立即恢复正常标价与微信支付开通。
* @returns {boolean}
*/
isIosAudit() {
// #ifdef MP-WEIXIN
return uiState.virtualPayAudit !== false
// #endif
return false
},
/**
* 到期时间文字
* @returns {string}
@@ -724,6 +767,15 @@ export default {
* @returns {Promise<void>}
*/
async openVip() {
if (this.isIosAudit) {
uni.showModal({
title: '提示',
content: '根据相关规范,暂不支持线上直接支付开通。请点击页面下方联系客服咨询办理。',
showCancel: false,
confirmText: '我知道了',
})
return
}
if (!this.checkLogin()) return
if (!this.planId) {
uni.showToast({ title: '请选择套餐', icon: 'none' })
@@ -842,6 +894,17 @@ export default {
if (!dateStr) return ''
return String(dateStr).slice(0, 10)
},
/**
* 非微信环境下的客服咨询提示
*/
handleContactTip() {
uni.showModal({
title: '联系客服',
content: '如需办理或了解会员权益,请联系平台官方客服。',
showCancel: false,
confirmText: '我知道了',
})
},
},
}
</script>
@@ -1133,6 +1196,45 @@ export default {
align-items: baseline;
color: #f5a623;
margin-top: 10rpx;
&--audit {
margin-top: 16rpx;
margin-bottom: 8rpx;
}
}
.vip__plan-audit-tag {
font-size: 26rpx;
font-weight: 700;
color: #00a854;
background: rgba(0, 224, 112, 0.12);
padding: 4rpx 14rpx;
border-radius: 8rpx;
}
.vip__audit-banner {
display: flex;
align-items: flex-start;
background: #f0fdf4;
border: 1rpx solid #bbf7d0;
border-radius: 16rpx;
padding: 20rpx 24rpx;
margin: 24rpx 0 10rpx;
}
.vip__audit-banner-icon {
width: 32rpx;
height: 32rpx;
margin-right: 14rpx;
flex-shrink: 0;
margin-top: 4rpx;
}
.vip__audit-banner-text {
font-size: 24rpx;
color: #166534;
line-height: 1.5;
}
.vip__dock-audit-tip {
font-size: 24rpx;
color: #64748b;
line-height: 1.3;
}
.vip__plan-symbol {
font-size: 24rpx;

查看文件

@@ -1,4 +1,4 @@
<template>
<template>
<view class="recharge fc-page">
<!-- 规范化顶部导航 -->
<fc-navbar title="余额中心" theme="gradient" :show-back="true" />
@@ -14,7 +14,7 @@
</view>
<!-- 充值金额 -->
<view class="fc-card">
<view v-if="!isIosAudit" class="fc-card">
<text class="recharge__title">选择充值金额</text>
<view class="recharge__tiers">
<view v-for="t in tiers" :key="t.value" class="recharge__tier"
@@ -35,6 +35,17 @@
<text class="recharge__note">微信支付安全付款,支付成功后余额自动到账(通常几秒内)。</text>
</view>
<!-- iOS 设备合规提示卡片 -->
<view v-else class="fc-card recharge__audit-card">
<text class="recharge__title">温馨提示</text>
<text class="recharge__audit-text">根据相关运营规范,暂不支持线上直接充值服务。如有疑问或需要协助,请联系客服咨询。</text>
<!-- #ifdef MP-WEIXIN -->
<button class="recharge__btn recharge__btn--contact" open-type="contact">
<text>联系在线客服</text>
</button>
<!-- #endif -->
</view>
<!-- 支付密码(后台开启「余额支付密码」后显示) -->
<view v-if="payPasswordEnabled" class="fc-card">
<view class="recharge__row" @click="openPayPassword">
@@ -87,6 +98,17 @@ export default {
}
},
computed: {
/** 是否触发 iOS 虚拟支付合规机制 */
/**
* 是否处于提审防审模式(各端统一,由后台配置开关控制)
* @returns {boolean}
*/
isIosAudit() {
// #ifdef MP-WEIXIN
return uiState.virtualPayAudit !== false
// #endif
return false
},
payPasswordEnabled() {
return !!uiState.payPasswordEnabled
},
@@ -122,6 +144,15 @@ export default {
uni.navigateTo({ url: '/pages/member/flows' })
},
async submit() {
if (this.isIosAudit) {
uni.showModal({
title: '提示',
content: '根据相关规范,暂不支持线上直接充值服务。',
showCancel: false,
confirmText: '我知道了',
})
return
}
if (this.submitting) return
const num = Number(this.amount)
if (!num || num <= 0) {
@@ -365,5 +396,24 @@ export default {
line-height: 1;
}
.recharge__audit-card {
padding: 40rpx 30rpx;
margin-top: 24rpx;
}
.recharge__audit-text {
display: block;
font-size: 26rpx;
color: #64748b;
line-height: 1.6;
margin: 20rpx 0 30rpx;
}
.recharge__btn--contact {
background: #00d668;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
}
/* 行标题(支付密码行) */
</style>