feat: 新增微信小程序虚拟支付提审防审模式及相关功能
本次提交主要实现微信小程序虚拟支付提审防审机制,包括: 1. 后台配置虚拟支付防审开关,支持开启/关闭模式 2. 前端根据开关状态及设备类型,iOS设备自动触发合规提示 3. 分销模块调整提现规则,无门槛即时提现 4. 登录页面优化,增加返回和暂不登录入口,符合微信审核规范 5. 新增提现记录页面,优化分销中心界面布局 该功能确保微信提审期间能顺利通过审核,审核通过后可一键切换为正常运营模式,无需重新发布小程序代码。
这个提交包含在:
@@ -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=正常运营)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
33
scratch/add_miniapp_config.php
普通文件
33
scratch/add_miniapp_config.php
普通文件
@@ -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";
|
||||
22
scratch/check_member_tags.js
普通文件
22
scratch/check_member_tags.js
普通文件
@@ -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
普通文件
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
普通文件
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";
|
||||
11
scratch/clean_withdraw_detail.js
普通文件
11
scratch/clean_withdraw_detail.js
普通文件
@@ -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
普通文件
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);
|
||||
20
scratch/replace_api_setting.js
普通文件
20
scratch/replace_api_setting.js
普通文件
@@ -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
普通文件
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
普通文件
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
普通文件
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
普通文件
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
普通文件
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,13 +667,13 @@ 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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
在新工单中引用
屏蔽一个用户