up add sms

This commit is contained in:
Mrx
2026-02-25 16:37:42 +08:00
parent 3e2d828f4d
commit 576ab0c362
12 changed files with 470 additions and 87 deletions

6
.env
View File

@@ -7,6 +7,12 @@ VITE_INQUIRE_AES_KEY=ff83609b2b24fc73196aac3d3dfb874f
VITE_WECHAT_APP_ID=wx442ee1ac1ee75917
# 阿里云滑块验证码配置
# 从阿里云验证码控制台获取 SceneId
VITE_ALIYUN_CAPTCHA_SCENE_ID=wynt39to
# 是否启用加密模式true/false需要在阿里云控制台开启加密模式
# 注意:根据代码逻辑,设置为 true 表示禁用加密,设置为 false 表示启用加密
VITE_ALIYUN_CAPTCHA_ENCRYPTED=true
VITE_CHAT_AES_KEY=qw5w6SFE2D1jmxyd
VITE_CHAT_AES_IV=345GDFED433223DF

View File

@@ -109,6 +109,12 @@
delete window.wx;
</script>
<!-- 阿里云滑块验证码 -->
<script>
window.AliyunCaptchaConfig = { region: "cn", prefix: "12zxnj" };
</script>
<script type="text/javascript" src="https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js"></script>
<!-- 预加载关键资源 -->
<link rel="preconnect" href="https://www.zhinengcha.cn" />
<link rel="preconnect" href="https://res.wx.qq.com" />
@@ -207,6 +213,8 @@
<div class="loading-text">加载中</div>
</div>
<div id="app"></div>
<!-- 阿里云验证码容器 -->
<div id="captcha-element"></div>
<script type="module" src="/src/main.js"></script>
</body>

View File

@@ -119,6 +119,7 @@ declare global {
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAgent: typeof import('./composables/useAgent.js')['useAgent']
const useAgentStore: typeof import('./stores/agentStore.js')['useAgentStore']
const useAliyunCaptcha: typeof import('./composables/useAliyunCaptcha.js')['useAliyunCaptcha']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
const useApiFetch: typeof import('./composables/useApiFetch.js')['default']
const useAppStore: typeof import('./stores/appStore.js')['useAppStore']

View File

@@ -703,6 +703,7 @@ const featureMap = {
name: "谛听多维报告",
component: defineAsyncComponent(() => import("@/ui/CDWBG8B4D/index.vue")),
},
// 谛听多维报告拆分模块
DWBG8B4D_Overview: {
name: "报告概览",

View File

@@ -133,6 +133,7 @@ import { useUserStore } from "@/stores/userStore";
import { useDialogStore } from "@/stores/dialogStore";
import { useEnv } from "@/composables/useEnv";
import { showConfirmDialog } from "vant";
import { useAliyunCaptcha } from "@/composables/useAliyunCaptcha";
import Payment from "@/components/Payment.vue";
import BindPhoneOnlyDialog from "@/components/BindPhoneOnlyDialog.vue";
@@ -200,6 +201,7 @@ const dialogStore = useDialogStore();
const userStore = useUserStore();
const { isWeChat } = useEnv();
const appStore = useAppStore();
const { runWithCaptcha } = useAliyunCaptcha();
// 响应式数据
const showPayment = ref(false);
@@ -440,22 +442,27 @@ async function sendVerificationCode() {
return;
}
const { data, error } = await useApiFetch("/auth/sendSms")
.post({ mobile: formData.mobile, actionType: "query" })
.json();
if (!error.value && data.value.code === 200) {
showToast({ message: "验证码发送成功", type: "success" });
startCountdown();
nextTick(() => {
const verificationCodeInput = document.getElementById('verificationCode');
if (verificationCodeInput) {
verificationCodeInput.focus();
// 使用阿里云滑块验证码保护发送短信接口
runWithCaptcha(
(captchaVerifyParam) => useApiFetch("/auth/sendSms")
.post({ mobile: formData.mobile, actionType: "query", captchaVerifyParam })
.json(),
(result) => {
const { data, error } = result
if (!error.value && data.value?.code === 200) {
showToast({ message: "验证码发送成功", type: "success" });
startCountdown();
nextTick(() => {
const verificationCodeInput = document.getElementById('verificationCode');
if (verificationCodeInput) {
verificationCodeInput.focus();
}
});
} else {
showToast({ message: data.value?.msg || "验证码发送失败,请重试" });
}
});
} else {
showToast({ message: "验证码发送失败,请重试" });
}
}
);
}
let timer = null;

View File

@@ -0,0 +1,221 @@
import { showToast, showLoadingToast, closeToast } from 'vant'
import useApiFetch from '@/composables/useApiFetch'
// 阿里云验证码场景ID从环境变量读取
const ALIYUN_CAPTCHA_SCENE_ID = import.meta.env.VITE_ALIYUN_CAPTCHA_SCENE_ID || 'wynt39to'
// 是否启用加密模式(通过环境变量控制,非加密模式时前端不调用后端获取 EncryptedSceneId
const ENABLE_ENCRYPTED = import.meta.env.VITE_ALIYUN_CAPTCHA_ENCRYPTED === 'false'
let captchaInitialized = false
/** 首次初始化后SDK 会异步调用 getInstance用此 Promise 在实例就绪后再 show */
let captchaReadyPromise = null
let captchaReadyResolve = null
async function ensureCaptchaInit() {
console.log('[AliyunCaptcha] ====== ensureCaptchaInit 开始执行 ======');
// 1. 检查是否已经初始化或非浏览器环境
if (captchaInitialized) {
console.log('[AliyunCaptcha] 已初始化,直接返回');
return;
}
if (typeof window === 'undefined') {
console.log('[AliyunCaptcha] 非浏览器环境,直接返回');
return;
}
// 2. 检查 SDK 是否存在
if (typeof window.initAliyunCaptcha !== 'function') {
console.error('[AliyunCaptcha] ❌ initAliyunCaptcha 不存在SDK 可能未加载!请检查是否引入 captcha.js');
return;
}
console.log('[AliyunCaptcha] ✅ initAliyunCaptcha 函数存在SDK 已加载');
// 3. 设置初始化状态
captchaInitialized = true;
window.captcha = null;
window.__lastBizResponse = null;
window.__onCaptchaBizSuccess = null;
captchaReadyPromise = new Promise((resolve) => {
captchaReadyResolve = resolve;
});
console.log('[AliyunCaptcha] 初始化状态已设置,创建 captchaReadyPromise');
// 4. 构建配置对象
const initConfig = {
SceneId: ALIYUN_CAPTCHA_SCENE_ID,
mode: 'popup',
element: '#captcha-element',
getInstance(instance) {
console.log('[AliyunCaptcha] 🎯 getInstance 被调用,实例已创建:', instance);
window.captcha = instance;
if (typeof captchaReadyResolve === 'function') {
console.log('[AliyunCaptcha] ✅ 调用 captchaReadyResolve(),通知实例就绪');
captchaReadyResolve();
captchaReadyResolve = null;
} else {
console.warn('[AliyunCaptcha] ⚠️ captchaReadyResolve 不是函数,可能已被调用');
}
},
captchaVerifyCallback(param) {
console.log('[AliyunCaptcha] 📞 captchaVerifyCallback 被调用,参数:', JSON.stringify(param, null, 2));
const result = typeof window.__captchaVerifyCallback === 'function'
? window.__captchaVerifyCallback(param)
: Promise.resolve({ captchaResult: false, bizResult: false });
console.log('[AliyunCaptcha] 📤 captchaVerifyCallback 返回值:', result);
return result;
},
onBizResultCallback(bizResult) {
console.log('[AliyunCaptcha] 📬 onBizResultCallback 被调用bizResult:', bizResult);
if (typeof window.__onBizResultCallback === 'function') {
window.__onBizResultCallback(bizResult);
} else {
console.warn('[AliyunCaptcha] ⚠️ __onBizResultCallback 不是函数');
}
window.__lastBizResponse = null;
window.__onCaptchaBizSuccess = null;
},
slideStyle: { width: 360, height: 40 },
language: 'cn',
};
console.log('[AliyunCaptcha] 📋 构建的 initConfig:', JSON.stringify(initConfig, null, 2));
// 5. 判断加密模式
if (!ENABLE_ENCRYPTED) {
console.log('[AliyunCaptcha] 🔓 使用非加密模式ENABLE_ENCRYPTED = false');
console.log('[AliyunCaptcha] 📤 调用 window.initAliyunCaptcha(initConfig)');
try {
window.initAliyunCaptcha(initConfig);
console.log('[AliyunCaptcha] ✅ initAliyunCaptcha 调用完成(非加密模式)');
} catch (err) {
console.error('[AliyunCaptcha] ❌ initAliyunCaptcha 调用失败(非加密模式):', err);
}
return;
}
// 6. 加密模式:获取 encryptedSceneId
console.log('[AliyunCaptcha] 🔐 使用加密模式ENABLE_ENCRYPTED = true');
console.log('[AliyunCaptcha] 📡 请求后端接口获取 encryptedSceneId: /captcha/encryptedSceneId');
let resp, encryptedSceneId;
try {
const { data, error } = await useApiFetch('/captcha/encryptedSceneId')
.post()
.json();
console.log('[AliyunCaptcha] 📥 后端接口响应:', { data: data?.value, error: error?.value });
resp = data?.value;
encryptedSceneId = resp?.data?.encryptedSceneId;
if (error?.value) {
console.error('[AliyunCaptcha] ❌ 后端接口请求失败:', error.value);
}
if (!encryptedSceneId) {
console.error('[AliyunCaptcha] ❌ encryptedSceneId 为空,响应数据:', resp);
}
} catch (err) {
console.error('[AliyunCaptcha] ❌ 请求 encryptedSceneId 时发生异常:', err);
resp = null;
encryptedSceneId = null;
}
if (error?.value || !encryptedSceneId) {
console.error('[AliyunCaptcha] ❌ 获取 encryptedSceneId 失败error:', error?.value, 'encryptedSceneId:', encryptedSceneId);
showToast({ message: '获取验证码参数失败,请稍后重试' });
captchaInitialized = false;
captchaReadyPromise = null;
captchaReadyResolve = null;
console.log('[AliyunCaptcha] 🔄 重置初始化状态,等待下次重试');
return;
}
console.log('[AliyunCaptcha] ✅ 成功获取 encryptedSceneId:', encryptedSceneId);
initConfig.EncryptedSceneId = encryptedSceneId;
console.log('[AliyunCaptcha] 📤 调用 window.initAliyunCaptcha(initConfig)(加密模式)');
try {
console.log('[AliyunCaptcha] 开始调用 window.initAliyunCaptcha(initConfig)(加密模式)')
window.initAliyunCaptcha(initConfig)
console.log('[AliyunCaptcha] ✅ window.initAliyunCaptcha 调用完成(加密模式)')
} catch (err) {
console.error('[AliyunCaptcha] ❌ window.initAliyunCaptcha 调用失败(加密模式):', err)
}
}
/**
* 阿里云滑块验证码通用封装。
* 依赖 index.html 中已加载的 AliyunCaptcha.js初始化在首次调起时执行。
*
* @param { (captchaVerifyParam: string) => Promise<{ data: Ref, error: Ref }> } bizVerify - 业务请求函数,接收滑块参数,返回 useApiFetch 的 { data, error }
* @param { (res: any) => void } [onSuccess] - 业务成功回调code===200 时调用,传入接口返回的 data.value
*/
export function useAliyunCaptcha() {
/**
* 先弹出滑块,通过后执行 bizVerify(captchaVerifyParam),再根据结果调用 onSuccess。
*/
async function runWithCaptcha(bizVerify, onSuccess) {
if (typeof window === 'undefined') {
showToast({ message: '验证码仅支持浏览器环境' })
return
}
showLoadingToast({
message: '安全验证加载中...',
forbidClick: true,
duration: 0,
loadingType: 'spinner',
})
try {
window.__captchaVerifyCallback = async (captchaVerifyParam) => {
window.__lastBizResponse = null
const { data, error } = await bizVerify(captchaVerifyParam)
const result = data?.value ?? data
if (error?.value || !result) {
return { captchaResult: false, bizResult: false }
}
window.__lastBizResponse = result
const captchaOk = result.captchaVerifyResult !== false
const bizOk = result.code === 200
return { captchaResult: captchaOk, bizResult: bizOk }
}
window.__onBizResultCallback = (bizResult) => {
if (
bizResult === true &&
window.__lastBizResponse &&
typeof window.__onCaptchaBizSuccess === 'function'
) {
window.__onCaptchaBizSuccess(window.__lastBizResponse)
}
}
await ensureCaptchaInit()
// 首次初始化时 SDK 会异步调用 getInstance需等待实例就绪后再 show
if (captchaReadyPromise) {
await captchaReadyPromise
captchaReadyPromise = null
}
if (!window.captcha) {
showToast({ message: '验证码未加载,请刷新页面重试' })
return
}
window.__onCaptchaBizSuccess = onSuccess
window.captcha.show()
} finally {
closeToast()
}
}
return { runWithCaptcha }
}

View File

@@ -18,6 +18,10 @@
</span>
</div>
</div>
<!-- 税后收入 -->
<div v-if="item.status !== 3" class="text-xs text-red-500 mt-1">
税后收入{{ (item.amount * 0.94).toFixed(2) }}
</div>
<div class="flex items-center justify-between mb-2">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
:class="getReportTypeStyle(item.product_name)">

View File

@@ -24,6 +24,10 @@
</span>
</div>
</div>
<!-- 税后收入 -->
<div v-if="item.status !== 3" class="text-xs text-red-500 mt-1">
税后收入{{ (item.amount * 0.94).toFixed(2) }}
</div>
<!-- 推广返佣显示返佣类型 tag -->
<!-- <div class="flex items-center mb-2" v-if="activeTab === 'promote' && item.rebate_type">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"

View File

@@ -6,11 +6,13 @@ import { useUserStore } from '@/stores/userStore'
import { useRouter, useRoute } from 'vue-router'
import { mobileCodeLogin } from '@/api/user'
import useApiFetch from '@/composables/useApiFetch'
import { useAliyunCaptcha } from '@/composables/useAliyunCaptcha'
const router = useRouter()
const route = useRoute()
const agentStore = useAgentStore()
const userStore = useUserStore()
const { runWithCaptcha } = useAliyunCaptcha()
const phoneNumber = ref('')
const verificationCode = ref('')
@@ -34,25 +36,28 @@ async function sendVerificationCode() {
return
}
const { data, error } = await useApiFetch('auth/sendSms')
.post({ mobile: phoneNumber.value, actionType: 'login' })
.json()
if (data.value && !error.value) {
if (data.value.code === 200) {
showToast({ message: "获取成功" });
startCountdown()
// 聚焦到验证码输入框
nextTick(() => {
const verificationCodeInput = document.getElementById('verificationCode');
if (verificationCodeInput) {
verificationCodeInput.focus();
}
});
} else {
showToast(data.value.msg)
// 使用阿里云滑块验证码保护发送短信接口
runWithCaptcha(
(captchaVerifyParam) => useApiFetch('auth/sendSms')
.post({ mobile: phoneNumber.value, actionType: 'login', captchaVerifyParam })
.json(),
(result) => {
// result 已经是解包后的响应数据data.value
if (result && result.code === 200) {
showToast({ message: "获取成功" });
startCountdown()
// 聚焦到验证码输入框
nextTick(() => {
const verificationCodeInput = document.getElementById('verificationCode');
if (verificationCodeInput) {
verificationCodeInput.focus();
}
});
} else if (result) {
showToast(result.msg || "发送失败")
}
}
}
)
}
function startCountdown() {

View File

@@ -55,13 +55,13 @@
</div>
<!-- 右侧资产信息仅代理显示 -->
<div v-if="isAgent" class="text-right">
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">余额</div>
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">税后收入</div>
<div class="text-2xl font-bold"
:style="(revenueData?.balance || 0) < 0 ? 'color: #ef4444;' : 'color: var(--van-theme-primary);'">
¥ {{ (revenueData?.balance || 0).toFixed(2) }}
:style="afterTax(revenueData?.balance) < 0 ? 'color: #ef4444;' : 'color: var(--van-theme-primary);'">
¥ {{ afterTax(revenueData?.balance).toFixed(2) }}
</div>
<!-- 负数余额提示 -->
<div v-if="(revenueData?.balance || 0) < 0" class="text-xs mt-1" style="color: #ef4444;">
<div v-if="afterTax(revenueData?.balance) < 0" class="text-xs mt-1" style="color: #ef4444;">
账户存在欠款需补足后才能提现
</div>
</div>
@@ -71,21 +71,21 @@
<div class="grid grid-cols-3 gap-3 mb-4 pt-4 border-t"
style="border-color: var(--van-border-color);">
<div class="text-center">
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">累计收益</div>
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">累计收益税后</div>
<div class="text-base font-semibold" style="color: var(--van-text-color);">
¥ {{ (revenueData?.total_earnings || 0).toFixed(2) }}
¥ {{ afterTax(revenueData?.total_earnings).toFixed(2) }}
</div>
</div>
<div class="text-center">
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">风险保障金</div>
<div class="text-base font-semibold" style="color: var(--van-text-color);">
¥ {{ (revenueData?.frozen_balance || 0).toFixed(2) }}
¥ {{ afterTax(revenueData?.frozen_balance || 0).toFixed(2) }}
</div>
</div>
<div class="text-center">
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">累计提现</div>
<div class="text-base font-semibold" style="color: var(--van-text-color);">
¥ {{ (revenueData?.withdrawn_amount || 0).toFixed(2) }}
¥ {{ afterTax(revenueData?.withdrawn_amount).toFixed(2) }}
</div>
</div>
</div>
@@ -116,12 +116,12 @@
<div class="flex items-center">
<van-icon name="balance-list" class="text-lg mr-2"
style="color: var(--color-warning);" />
<span class="text-base font-bold" style="color: var(--van-text-color);">我的推广收益</span>
<span class="text-base font-bold" style="color: var(--van-text-color);">我的推广收益税后</span>
</div>
</div>
<div class="text-center mb-3">
<div class="text-xl font-bold" style="color: var(--color-warning);">
¥ {{ (revenueData?.commission_total || 0).toFixed(2) }}
¥ {{ afterTax(revenueData?.commission_total).toFixed(2) }}
</div>
<div class="text-sm mt-0.5" style="color: var(--van-text-color-2);">累计总收益</div>
</div>
@@ -129,13 +129,13 @@
<div class="text-center">
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">今日</div>
<div class="text-base font-semibold" style="color: var(--color-warning);">
¥ {{ (revenueData?.commission_today || 0).toFixed(2) }}
¥ {{ afterTax(revenueData?.commission_today).toFixed(2) }}
</div>
</div>
<div class="text-center">
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">本月</div>
<div class="text-base font-semibold" style="color: var(--color-warning);">
¥ {{ (revenueData?.commission_month || 0).toFixed(2) }}
¥ {{ afterTax(revenueData?.commission_month).toFixed(2) }}
</div>
</div>
</div>
@@ -151,12 +151,12 @@
<div class="flex items-center justify-between mb-3">
<div class="flex items-center">
<van-icon name="gift-o" class="text-lg mr-2" style="color: var(--color-success);" />
<span class="text-base font-bold" style="color: var(--van-text-color);">下级推广收益</span>
<span class="text-base font-bold" style="color: var(--van-text-color);">下级推广收益税后</span>
</div>
</div>
<div class="text-center mb-3">
<div class="text-xl font-bold" style="color: var(--color-success);">
¥ {{ (revenueData?.rebate_total || 0).toFixed(2) }}
¥ {{ afterTax(revenueData?.rebate_total).toFixed(2) }}
</div>
<div class="text-sm mt-0.5" style="color: var(--van-text-color-2);">累计总收益</div>
</div>
@@ -164,13 +164,13 @@
<div class="text-center">
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">今日</div>
<div class="text-base font-semibold" style="color: var(--color-success);">
¥ {{ (revenueData?.rebate_today || 0).toFixed(2) }}
¥ {{ afterTax(revenueData?.rebate_today).toFixed(2) }}
</div>
</div>
<div class="text-center">
<div class="text-sm mb-1" style="color: var(--van-text-color-2);">本月</div>
<div class="text-base font-semibold" style="color: var(--color-success);">
¥ {{ (revenueData?.rebate_month || 0).toFixed(2) }}
¥ {{ afterTax(revenueData?.rebate_month).toFixed(2) }}
</div>
</div>
</div>
@@ -289,6 +289,10 @@ const { userName, userAvatar, isLoggedIn, mobile } = storeToRefs(userStore);
const { isWeChat } = useEnv();
const revenueData = ref(null);
// 税率常量6%
const TAX_RATE = 0.06;
const afterTax = (amount) => (amount || 0) * (1 - TAX_RATE);
// 等级名称映射(数字等级)
const levelNamesMap = {
1: "普通代理",

View File

@@ -105,34 +105,34 @@
<!-- 金额提示 -->
<div class="text-sm mb-2 space-y-1" style="color: var(--van-text-color);">
<div>
可提现金额<span class="font-semibold"
:style="(availableAmount || 0) < 0 ? 'color: #ef4444;' : 'color: var(--van-theme-primary);'">
¥{{ (availableAmount || 0).toFixed(2) }}
可提现金额税后<span class="font-semibold"
:style="afterTax(availableAmount) < 0 ? 'color: #ef4444;' : 'color: var(--van-theme-primary);'">
¥{{ afterTax(availableAmount).toFixed(2) }}
</span>
</div>
<div v-if="alipayMonthQuota > 0" class="text-sm" style="color: var(--van-text-color-2);">
本月支付宝提现额度
<span class="font-semibold">¥{{ alipayMonthQuota.toFixed(2) }}</span>
<span class="font-semibold">¥{{ afterTax(alipayMonthQuota).toFixed(2) }}</span>
已用
<span class="font-semibold">¥{{ alipayMonthUsed.toFixed(2) }}</span>
<span class="font-semibold">¥{{ afterTax(alipayMonthUsed).toFixed(2) }}</span>
剩余
<span class="font-semibold"
:style="alipayMonthRemain <= 0 ? 'color: #ef4444;' : 'color: var(--van-theme-primary);'">
¥{{ alipayMonthRemain.toFixed(2) }}
:style="afterTax(alipayMonthRemain) <= 0 ? 'color: #ef4444;' : 'color: var(--van-theme-primary);'">
¥{{ afterTax(alipayMonthRemain).toFixed(2) }}
</span>
</div>
<div v-if="alipayMonthRemain <= 0 && alipayMonthQuota > 0" class="text-sm text-red-500">
<div v-if="afterTax(alipayMonthRemain) <= 0 && alipayMonthQuota > 0" class="text-sm text-red-500">
本月支付宝提现额度已用完请使用银行卡提现
</div>
</div>
<!-- 负数余额提示 -->
<div v-if="(availableAmount || 0) < 0" class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200">
<div v-if="afterTax(availableAmount) < 0" class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200">
<div class="flex items-start gap-2">
<van-icon name="warning-o" class="text-red-500 mt-0.5" />
<div class="flex-1">
<div class="text-sm font-medium text-red-700 mb-1">账户存在欠款</div>
<div class="text-sm text-red-600">
您的账户余额为负数存在欠款金额 ¥{{ Math.abs(availableAmount || 0).toFixed(2) }}
您的账户余额为负数存在欠款金额 ¥{{ Math.abs(afterTax(availableAmount)).toFixed(2) }}
请先通过后续订单收益补足欠款后才能申请提现
</div>
</div>
@@ -256,19 +256,19 @@
<!-- 金额提示 -->
<div class="text-sm mb-2" style="color: var(--van-text-color);">
可提现金额<span class="font-semibold"
:style="(availableAmount || 0) < 0 ? 'color: #ef4444;' : 'color: var(--van-theme-primary);'">
¥{{ (availableAmount || 0).toFixed(2) }}
可提现金额税后<span class="font-semibold"
:style="afterTax(availableAmount) < 0 ? 'color: #ef4444;' : 'color: var(--van-theme-primary);'">
¥{{ afterTax(availableAmount).toFixed(2) }}
</span>
</div>
<!-- 负数余额提示 -->
<div v-if="(availableAmount || 0) < 0" class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200">
<div v-if="afterTax(availableAmount) < 0" class="mb-4 p-3 rounded-lg bg-red-50 border border-red-200">
<div class="flex items-start gap-2">
<van-icon name="warning-o" class="text-red-500 mt-0.5" />
<div class="flex-1">
<div class="text-sm font-medium text-red-700 mb-1">账户存在欠款</div>
<div class="text-sm text-red-600">
您的账户余额为负数存在欠款金额 ¥{{ Math.abs(availableAmount || 0).toFixed(2) }}
您的账户余额为负数存在欠款金额 ¥{{ Math.abs(afterTax(availableAmount)).toFixed(2) }}
请先通过后续订单收益补足欠款后才能申请提现
</div>
</div>
@@ -304,11 +304,11 @@
</div>
<!-- 提交按钮 -->
<van-button type="primary" block :loading="isSubmitting" :disabled="(availableAmount || 0) < 0"
<van-button type="primary" block :loading="isSubmitting" :disabled="afterTax(availableAmount) < 0"
class="text-white rounded-xl shadow-lg h-12 font-bold text-base"
style="background: linear-gradient(135deg, var(--van-theme-primary), var(--van-theme-primary-dark));"
@click="handleSubmit">
{{ (availableAmount || 0) < 0 ? '账户存在欠款无法提现' : '立即提现' }} </van-button>
{{ afterTax(availableAmount) < 0 ? '账户存在欠款无法提现' : '立即提现' }} </van-button>
</div>
<!-- 税务确认弹窗 -->
@@ -323,12 +323,12 @@
</div>
<!-- 金额详情 -->
<div class="rounded-xl p-4 space-y-3" style="background-color: var(--van-background-color-light);">
<div class="rounded-xl p-4" style="background-color: var(--van-background-color-light);">
<div class="flex justify-between items-center">
<span style="color: var(--van-text-color-2);">提现金额</span>
<span class="font-semibold" style="color: var(--van-text-color);">¥{{ amount }}</span>
<span style="color: var(--van-text-color-2);">提现到账金额</span>
<span class="font-bold text-lg" style="color: var(--van-theme-primary);">¥{{ amount }}</span>
</div>
<div class="flex justify-between items-center">
<!-- <div class="flex justify-between items-center">
<span style="color: var(--van-text-color-2);">预估税费</span>
<span class="font-semibold text-red-500">
-¥{{ estimatedTaxAmount.toFixed(2) }}
@@ -340,7 +340,7 @@
<span class="font-bold text-lg" style="color: var(--van-theme-primary);">¥{{
estimatedActualAmount.toFixed(2) }}</span>
</div>
</div>
</div> -->
</div>
<!-- 提示信息 -->
@@ -348,10 +348,11 @@
<p class="text-blue-600 font-medium mb-2">
税收说明
</p>
<div class="text-gray-600 space-y-1">
<p> 提现金额¥{{ amount }}</p>
<div class="text-gray-600">
<p>当前税率6%</p>
<!-- <p> 提现金额¥{{ amount }}</p>
<p> 税率6%</p>
<p class="text-blue-600"> 税收计算¥{{ amount }} × 6% = ¥{{ taxAmount.toFixed(2) }}</p>
<p class="text-blue-600"> 税收计算¥{{ amount }} × 6% = ¥{{ taxAmount.toFixed(2) }}</p> -->
</div>
</div>
@@ -457,6 +458,14 @@
const agentStore = useAgentStore();
const dialogStore = useDialogStore();
// 税率常量6%
const TAX_RATE = 0.06;
// 税后金额 = 税前金额 * (1 - TAX_RATE)
const afterTax = (amount) => (amount || 0) * (1 - TAX_RATE);
// 税前金额 = 税后金额 / (1 - TAX_RATE)
const beforeTax = (afterTaxAmount) => (afterTaxAmount || 0) / (1 - TAX_RATE);
// Tab 切换
const activeTab = ref("alipay");
// 状态管理
@@ -602,6 +611,9 @@
// 表单验证
const validateAmount = (val) => {
const num = Number(val);
// 税前金额(用于与余额比较)
const preTaxAmount = beforeTax(num);
// 如果余额为负数,不允许提现
if (availableAmount.value < 0) {
return false;
@@ -609,14 +621,18 @@
if (activeTab.value === 'alipay') {
// 支付宝金额需同时满足余额与额度限制
if (alipayMonthRemain.value <= 0) {
// 税后额度剩余
const afterTaxRemain = afterTax(alipayMonthRemain.value);
if (afterTaxRemain <= 0) {
return false;
}
return num >= 50 && num <= availableAmount.value && num <= alipayMonthRemain.value;
// 税后金额 >= 50税前金额 <= 余额,税后金额 <= 税后额度剩余
return num >= 50 && preTaxAmount <= availableAmount.value && num <= afterTaxRemain;
}
// 银行卡提现只受余额限制
return num >= 50 && num <= availableAmount.value;
// 税后金额 >= 50税前金额 <= 余额
return num >= 50 && preTaxAmount <= availableAmount.value;
};
const validateForm = () => {
@@ -664,19 +680,23 @@
return false;
}
if (amountNum > availableAmount.value) {
// 税前金额(用于与余额比较)
const preTaxAmount = beforeTax(amountNum);
if (preTaxAmount > availableAmount.value) {
showToast("超过可提现金额");
return false;
}
// 支付宝额度检查
if (activeTab.value === 'alipay') {
if (alipayMonthRemain.value <= 0) {
const afterTaxRemain = afterTax(alipayMonthRemain.value);
if (afterTaxRemain <= 0) {
showToast("本月支付宝提现额度已用完,请使用银行卡提现");
return false;
}
if (amountNum > alipayMonthRemain.value) {
showToast(`本月支付宝最高可提现 ${alipayMonthRemain.value.toFixed(2)} 元,请调整提现金额或使用银行卡提现`);
if (amountNum > afterTaxRemain) {
showToast(`本月支付宝最高可提现 ${afterTaxRemain.toFixed(2)} 元,请调整提现金额或使用银行卡提现`);
return false;
}
}
@@ -711,9 +731,12 @@
isSubmitting.value = true;
try {
const withdrawalType = activeTab.value === 'alipay' ? 1 : 2;
// 用户输入的是税后金额,传给后端需要逆推为税前金额
const preTaxWithdrawAmount = beforeTax(Number(amount.value));
const params = {
withdrawal_type: withdrawalType,
amount: Number(amount.value),
amount: preTaxWithdrawAmount, // 传税前金额给后端
payee_name: realName.value,
};
@@ -756,14 +779,14 @@
if (status.value === 2) resetPage();
}
};
// 填充最大金额
// 填充最大金额(税后金额)
const fillMaxAmount = () => {
if (activeTab.value === 'alipay') {
const maxByQuota = alipayMonthRemain.value || 0;
const maxByBalance = availableAmount.value || 0;
amount.value = Math.min(maxByQuota, maxByBalance);
const maxByQuotaAfterTax = afterTax(alipayMonthRemain.value) || 0;
const maxByBalanceAfterTax = afterTax(availableAmount.value) || 0;
amount.value = Math.min(maxByQuotaAfterTax, maxByBalanceAfterTax);
} else {
amount.value = availableAmount.value;
amount.value = afterTax(availableAmount.value);
}
};

File diff suppressed because one or more lines are too long