Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30dcf6460a | ||
|
|
f98a1cca4f | ||
|
|
adfd2426b4 | ||
|
|
f62bc444ce | ||
|
|
b9ef0e4e3f | ||
|
|
d2890a25b6 | ||
|
|
39c2e05121 | ||
|
|
9c2d91ba96 | ||
|
|
ace15a79c6 | ||
|
|
6c3499b470 | ||
|
|
7c77b10c8c | ||
|
|
191ecd4c83 | ||
|
|
4d4727b17e | ||
|
|
b72bd2121a | ||
|
|
ac4e201f98 | ||
|
|
3cc72b946d | ||
|
|
621932f514 | ||
|
|
3cbaebb5d3 | ||
|
|
092864e661 | ||
|
|
d4c901d1c6 |
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
VITE_BASE_URL=/
|
VITE_BASE_URL=/
|
||||||
|
|
||||||
VITE_APP_TITLE="WANFi Platform"
|
VITE_APP_TITLE="Platform"
|
||||||
|
|
||||||
# the prefix of the icon name
|
# the prefix of the icon name
|
||||||
VITE_ICON_PREFIX=icon
|
VITE_ICON_PREFIX=icon
|
||||||
@@ -35,3 +35,4 @@ VITE_SERVICE_EXPIRED_TOKEN_CODES=403
|
|||||||
|
|
||||||
VITE_SERVICE_SERVER_ERROR_CODE=500
|
VITE_SERVICE_SERVER_ERROR_CODE=500
|
||||||
|
|
||||||
|
VITE_PAYPAL_CLIENT_ID=AfPgwFAmo9K7KCqiiGpNRCyQMSxI6V33eH-nEMnVndJNVEYOEOEn5wSPkHUybfzcjDLnBejt-RKnIfqX
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.svg" />
|
<link rel="icon" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script src="/langs/zh-cn.js"></script>
|
||||||
|
<script src="/langs/en-us.js"></script>
|
||||||
<title>%VITE_APP_TITLE%</title>
|
<title>%VITE_APP_TITLE%</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@better-scroll/core": "2.5.1",
|
"@better-scroll/core": "2.5.1",
|
||||||
"@iconify/vue": "4.1.2",
|
"@iconify/vue": "4.1.2",
|
||||||
|
"@paypal/paypal-js": "^8.2.0",
|
||||||
"@sa/axios": "workspace:*",
|
"@sa/axios": "workspace:*",
|
||||||
"@sa/color-palette": "workspace:*",
|
"@sa/color-palette": "workspace:*",
|
||||||
"@sa/hooks": "workspace:*",
|
"@sa/hooks": "workspace:*",
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ function createCommonRequest<ResponseData = any>(
|
|||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.isBackendSuccess(response)) {
|
if (opts.isBackendSuccess(response) || response.config?.responseType === "blob") {
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+15
@@ -14,6 +14,9 @@ importers:
|
|||||||
'@iconify/vue':
|
'@iconify/vue':
|
||||||
specifier: 4.1.2
|
specifier: 4.1.2
|
||||||
version: 4.1.2(vue@3.4.27(typescript@5.4.5))
|
version: 4.1.2(vue@3.4.27(typescript@5.4.5))
|
||||||
|
'@paypal/paypal-js':
|
||||||
|
specifier: ^8.2.0
|
||||||
|
version: 8.2.0
|
||||||
'@sa/axios':
|
'@sa/axios':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:packages/axios
|
version: link:packages/axios
|
||||||
@@ -730,6 +733,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
'@paypal/paypal-js@8.2.0':
|
||||||
|
resolution: {integrity: sha512-hLg5wNORW3WiyMiRNJOm6cN2IqjPlClpxd971bEdm0LNpbbejQZYtesb0/0arTnySSbGcxg7MxjkZ/N5Z5qBNQ==}
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -3263,6 +3269,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
promise-polyfill@8.3.0:
|
||||||
|
resolution: {integrity: sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==}
|
||||||
|
|
||||||
prompts@2.4.2:
|
prompts@2.4.2:
|
||||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -4633,6 +4642,10 @@ snapshots:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.17.1
|
fastq: 1.17.1
|
||||||
|
|
||||||
|
'@paypal/paypal-js@8.2.0':
|
||||||
|
dependencies:
|
||||||
|
promise-polyfill: 8.3.0
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -7553,6 +7566,8 @@ snapshots:
|
|||||||
|
|
||||||
progress@2.0.3: {}
|
progress@2.0.3: {}
|
||||||
|
|
||||||
|
promise-polyfill@8.3.0: {}
|
||||||
|
|
||||||
prompts@2.4.2:
|
prompts@2.4.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
kleur: 3.0.3
|
kleur: 3.0.3
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const enUS = {
|
||||||
|
system: {
|
||||||
|
title: 'MacroHub'
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const zhCN = {
|
||||||
|
system: {
|
||||||
|
title: 'MacroHub'
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, defineEmits, computed } from 'vue';
|
import { defineProps, defineEmits, computed, ref, onMounted, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { AlipayOutlined, WechatOutlined, WalletOutlined } from '@ant-design/icons-vue';
|
import { AlipayOutlined, WechatOutlined, WalletOutlined,MoneyCollectOutlined } from '@ant-design/icons-vue';
|
||||||
import { Modal } from 'ant-design-vue';
|
import { Modal } from 'ant-design-vue';
|
||||||
|
import PaypalButton from '@/components/payment/paypal-button.vue';
|
||||||
|
import { stripePay, getPaymentConfig } from '@/service/api/payment';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -21,6 +23,27 @@ interface Props {
|
|||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const emit = defineEmits(['update:visible', 'confirm', 'cancel']);
|
const emit = defineEmits(['update:visible', 'confirm', 'cancel']);
|
||||||
|
|
||||||
|
const paymentMethods = ref<string[]>([]);
|
||||||
|
const currencySymbol = ref('¥');
|
||||||
|
|
||||||
|
// 获取支付配置
|
||||||
|
const fetchPaymentConfig = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getPaymentConfig();
|
||||||
|
paymentMethods.value = response.data.paymentMethods || [];
|
||||||
|
currencySymbol.value = response.data.currencySymbol || '¥';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch payment config:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听visible属性变化
|
||||||
|
watch(() => props.visible, (newVisible) => {
|
||||||
|
if (newVisible) {
|
||||||
|
fetchPaymentConfig();
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
const orderTypeMap = {
|
const orderTypeMap = {
|
||||||
0: t('page.order.packagePurchase'),
|
0: t('page.order.packagePurchase'),
|
||||||
1: t('page.order.balanceRecharge')
|
1: t('page.order.balanceRecharge')
|
||||||
@@ -37,7 +60,7 @@ const isBalancePayDisabled = computed(() => {
|
|||||||
return props.loading || false;
|
return props.loading || false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleConfirm = (paymentMethod: 'alipay' | 'wxpay' | 'balance') => {
|
const handleConfirm = async (paymentMethod: 'alipay' | 'wxpay' | 'balance') => {
|
||||||
if (paymentMethod === 'balance') {
|
if (paymentMethod === 'balance') {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: t('page.order.confirmPayment'),
|
title: t('page.order.confirmPayment'),
|
||||||
@@ -48,6 +71,9 @@ const handleConfirm = (paymentMethod: 'alipay' | 'wxpay' | 'balance') => {
|
|||||||
emit('confirm', paymentMethod);
|
emit('confirm', paymentMethod);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if(paymentMethod === 'stripe') {
|
||||||
|
const response = await stripePay(props.orderInfo.orderId);
|
||||||
|
window.location.href = response.data
|
||||||
} else {
|
} else {
|
||||||
emit('confirm', paymentMethod);
|
emit('confirm', paymentMethod);
|
||||||
}
|
}
|
||||||
@@ -80,7 +106,7 @@ const handleCancel = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="label">{{ t('page.order.orderAmount') }}:</span>
|
<span class="label">{{ t('page.order.orderAmount') }}:</span>
|
||||||
<span class="value highlight">¥{{ orderInfo.orderAmount.toFixed(2) }}</span>
|
<span class="value highlight">{{ currencySymbol }}{{ orderInfo.orderAmount.toFixed(2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="label">{{ t('page.order.orderId') }}:</span>
|
<span class="label">{{ t('page.order.orderId') }}:</span>
|
||||||
@@ -99,20 +125,19 @@ const handleCancel = () => {
|
|||||||
}"
|
}"
|
||||||
@click="!isBalancePayDisabled && handleConfirm('balance')"
|
@click="!isBalancePayDisabled && handleConfirm('balance')"
|
||||||
>
|
>
|
||||||
<div class="debug-info" style="display: none;">
|
|
||||||
Enable balance pay: {{ enableBalancePay }}
|
|
||||||
</div>
|
|
||||||
<WalletOutlined class="payment-icon balance-icon" />
|
<WalletOutlined class="payment-icon balance-icon" />
|
||||||
<span>{{ t('page.order.balancePay') }}</span>
|
<span>{{ t('page.order.balancePay') }}</span>
|
||||||
<div class="balance-info" v-if="userBalance !== undefined">
|
<div class="balance-info" v-if="userBalance !== undefined">
|
||||||
{{ t('page.order.availableBalance') }}: ¥{{ userBalance.toFixed(2) }}
|
{{ t('page.order.availableBalance') }}: {{ currencySymbol }}{{ userBalance.toFixed(2) }}
|
||||||
<template v-if="userBalance < orderInfo.orderAmount">
|
<template v-if="userBalance < orderInfo.orderAmount">
|
||||||
<br>
|
<br>
|
||||||
<span class="balance-insufficient">{{ t('page.order.insufficientBalance') }}</span>
|
<span class="balance-insufficient">{{ t('page.order.insufficientBalance') }}</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
v-if="paymentMethods.includes('alipay')"
|
||||||
class="method-item"
|
class="method-item"
|
||||||
:class="{ disabled: loading }"
|
:class="{ disabled: loading }"
|
||||||
@click="!loading && handleConfirm('alipay')"
|
@click="!loading && handleConfirm('alipay')"
|
||||||
@@ -120,7 +145,9 @@ const handleCancel = () => {
|
|||||||
<AlipayOutlined class="payment-icon alipay-icon" />
|
<AlipayOutlined class="payment-icon alipay-icon" />
|
||||||
<span>{{ t('page.order.alipay') }}</span>
|
<span>{{ t('page.order.alipay') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
v-if="paymentMethods.includes('wxpay')"
|
||||||
class="method-item"
|
class="method-item"
|
||||||
:class="{ disabled: loading }"
|
:class="{ disabled: loading }"
|
||||||
@click="!loading && handleConfirm('wxpay')"
|
@click="!loading && handleConfirm('wxpay')"
|
||||||
@@ -128,6 +155,21 @@ const handleCancel = () => {
|
|||||||
<WechatOutlined class="payment-icon wxpay-icon" />
|
<WechatOutlined class="payment-icon wxpay-icon" />
|
||||||
<span>{{ t('page.order.wxpay') }}</span>
|
<span>{{ t('page.order.wxpay') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="paymentMethods.includes('stripe')"
|
||||||
|
class="method-item"
|
||||||
|
:class="{ disabled: loading }"
|
||||||
|
@click="!loading && handleConfirm('stripe')"
|
||||||
|
>
|
||||||
|
<MoneyCollectOutlined class="payment-icon wxpay-icon" />
|
||||||
|
<span>{{ t('page.order.stripe') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PaypalButton
|
||||||
|
v-if="visible && paymentMethods.includes('paypal')"
|
||||||
|
:order-info="props.orderInfo"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
@@ -183,10 +225,13 @@ const handleCancel = () => {
|
|||||||
.methods-container {
|
.methods-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.method-item {
|
.method-item {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
max-width: calc(50% - 6px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import { constructPaypalBtn } from '@/utils/paypal';
|
||||||
|
import { getPaymentConfig, getPayPalConfig } from '@/service/api/payment';
|
||||||
|
import CryptoJS from 'crypto-js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES解密处理(CBC模式)
|
||||||
|
*/
|
||||||
|
function decryptCBC(word: string) {
|
||||||
|
word = (word + '').replace(/\n*$/g, '').replace(/\n/g, ''); // 这一行,将换行符替换为空
|
||||||
|
|
||||||
|
// 密钥
|
||||||
|
const keyStr = "EolSdjfd89v2PubN";
|
||||||
|
|
||||||
|
// 向量
|
||||||
|
const ivStr = "EjlnujOBvlv2PubN";
|
||||||
|
const key = CryptoJS.enc.Utf8.parse(keyStr);
|
||||||
|
let iv = CryptoJS.enc.Utf8.parse(ivStr);
|
||||||
|
const decrypt = CryptoJS.AES.decrypt(word, key,
|
||||||
|
{
|
||||||
|
iv: iv,
|
||||||
|
mode: CryptoJS.mode.CBC,
|
||||||
|
padding: CryptoJS.pad.Pkcs7
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return decrypt.toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
orderInfo: {
|
||||||
|
orderId: string;
|
||||||
|
orderType: number; // 0: 购买套餐, 1: 余额充值
|
||||||
|
orderAmount: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
onMounted(async()=>{
|
||||||
|
const response = await getPaymentConfig();
|
||||||
|
let currency = 'USD';
|
||||||
|
if (response && response.data) {
|
||||||
|
currency = response.data.currency || 'USD';
|
||||||
|
}
|
||||||
|
const configRes = await getPayPalConfig();
|
||||||
|
const clientId = decryptCBC(configRes.data);
|
||||||
|
await constructPaypalBtn(clientId, props.orderInfo.orderId, currency);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="paypal-buttons"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {UserOutlined,HomeOutlined,MoneyCollectOutlined,ClusterOutlined} from "@ant-design/icons-vue";
|
import {UserOutlined,HomeOutlined,WalletOutlined,ClusterOutlined} from "@ant-design/icons-vue";
|
||||||
import {useRouterPush} from "@/hooks/common";
|
import {useRouterPush} from "@/hooks/common";
|
||||||
|
|
||||||
const { routerPushByKey } = useRouterPush();
|
const { routerPushByKey } = useRouterPush();
|
||||||
@@ -22,7 +22,7 @@ defineOptions({
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-item">
|
<div class="flex-item">
|
||||||
<ButtonIcon>
|
<ButtonIcon>
|
||||||
<MoneyCollectOutlined class="text-icon-large" @click="routerPushByKey('billing_billservice')"/>
|
<WalletOutlined class="text-icon-large" @click="routerPushByKey('billing_billservice')"/>
|
||||||
</ButtonIcon>
|
</ButtonIcon>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item">
|
<div class="flex-item">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const viewEn: any = {
|
|||||||
"view.endpoint_access": "Current Connection",
|
"view.endpoint_access": "Current Connection",
|
||||||
"view.endpoint_records": "Past Connection",
|
"view.endpoint_records": "Past Connection",
|
||||||
"view.endpoint_cdrlrecords":"Internet Records",
|
"view.endpoint_cdrlrecords":"Internet Records",
|
||||||
|
"view.endpoint_bill":"Bill Records",
|
||||||
"view.billing": "Billing",
|
"view.billing": "Billing",
|
||||||
"view.billing_histories": "Historical",
|
"view.billing_histories": "Historical",
|
||||||
"view.billing_Rechargehistory":"Recharge History",
|
"view.billing_Rechargehistory":"Recharge History",
|
||||||
@@ -196,7 +197,7 @@ const local: any = {
|
|||||||
login: {
|
login: {
|
||||||
common: {
|
common: {
|
||||||
loginOrRegister: 'Login / Register',
|
loginOrRegister: 'Login / Register',
|
||||||
userNamePlaceholder: 'Please enter user name',
|
userNamePlaceholder: 'Please enter user name or email',
|
||||||
phonePlaceholder: 'Please enter phone number',
|
phonePlaceholder: 'Please enter phone number',
|
||||||
codePlaceholder: 'Please enter verification code',
|
codePlaceholder: 'Please enter verification code',
|
||||||
passwordPlaceholder: 'Please enter password',
|
passwordPlaceholder: 'Please enter password',
|
||||||
@@ -213,7 +214,7 @@ const local: any = {
|
|||||||
getCodeFailed: 'Failed to get verification code, please try again'
|
getCodeFailed: 'Failed to get verification code, please try again'
|
||||||
},
|
},
|
||||||
pwdLogin: {
|
pwdLogin: {
|
||||||
title: 'Password Login',
|
title: 'Login',
|
||||||
rememberMe: 'Remember me',
|
rememberMe: 'Remember me',
|
||||||
forgetPassword: 'Forget password?',
|
forgetPassword: 'Forget password?',
|
||||||
register: 'Register',
|
register: 'Register',
|
||||||
@@ -647,7 +648,8 @@ const local: any = {
|
|||||||
endpoint:{
|
endpoint:{
|
||||||
access:"current client",
|
access:"current client",
|
||||||
records:"Historical client",
|
records:"Historical client",
|
||||||
cdrlrecords:"Internet records"
|
cdrlrecords:"Internet records",
|
||||||
|
bill:'Bill records',
|
||||||
},
|
},
|
||||||
cdrlrecords:{
|
cdrlrecords:{
|
||||||
total:'Total',
|
total:'Total',
|
||||||
@@ -728,6 +730,8 @@ const local: any = {
|
|||||||
selectPayment:'Payment',
|
selectPayment:'Payment',
|
||||||
alipay:'Alipay',
|
alipay:'Alipay',
|
||||||
wxpay:'WeChat Pay',
|
wxpay:'WeChat Pay',
|
||||||
|
wxpayScan:'Scan QR code on WeChat',
|
||||||
|
stripe:'Stripe',
|
||||||
balancePay:'Balance Pay',
|
balancePay:'Balance Pay',
|
||||||
availableBalance:'Balance',
|
availableBalance:'Balance',
|
||||||
paymentSuccess:'Payment successful',
|
paymentSuccess:'Payment successful',
|
||||||
@@ -738,6 +742,30 @@ const local: any = {
|
|||||||
confirm:'Confirm',
|
confirm:'Confirm',
|
||||||
insufficientBalance:'Insufficient balance'
|
insufficientBalance:'Insufficient balance'
|
||||||
},
|
},
|
||||||
|
bill:{
|
||||||
|
loading:'Loading',
|
||||||
|
tipTitle:'Bill DownLoad',
|
||||||
|
exportTip:'Sure to download?',
|
||||||
|
total:'Total',
|
||||||
|
invoiceNumber:'Bill Number',
|
||||||
|
title: 'Bill List',
|
||||||
|
billDate: 'Bill Date',
|
||||||
|
amount: 'Amount',
|
||||||
|
billType: 'Bill Type',
|
||||||
|
statu: 'Status',
|
||||||
|
actions: 'Action',
|
||||||
|
download: 'Download',
|
||||||
|
preview: 'Preview',
|
||||||
|
exportOk: 'Download Success',
|
||||||
|
exportFail: 'Download Fail',
|
||||||
|
previewTitle: 'Bill Preview',
|
||||||
|
previewFailed: 'Preview fail',
|
||||||
|
noFile: 'No billing documents available',
|
||||||
|
types: {
|
||||||
|
recharge:'Recharge',
|
||||||
|
package: 'Package',
|
||||||
|
},
|
||||||
|
},
|
||||||
kyc:{
|
kyc:{
|
||||||
rejectReason:'Reject Reason:',
|
rejectReason:'Reject Reason:',
|
||||||
drive:'Driving license',
|
drive:'Driving license',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const viewZh: any = {
|
|||||||
"view.endpoint_access": "当前设备",
|
"view.endpoint_access": "当前设备",
|
||||||
"view.endpoint_records": "历史设备",
|
"view.endpoint_records": "历史设备",
|
||||||
"view.endpoint_cdrlrecords":"上网记录",
|
"view.endpoint_cdrlrecords":"上网记录",
|
||||||
|
"view.endpoint_bill":"账单记录",
|
||||||
"view.billing": "账单",
|
"view.billing": "账单",
|
||||||
"view.billing_billservice":"账单服务",
|
"view.billing_billservice":"账单服务",
|
||||||
"view.billing_histories": "历史查询",
|
"view.billing_histories": "历史查询",
|
||||||
@@ -196,7 +197,7 @@ const local:any = {
|
|||||||
login: {
|
login: {
|
||||||
common: {
|
common: {
|
||||||
loginOrRegister: '登录 / 注册',
|
loginOrRegister: '登录 / 注册',
|
||||||
userNamePlaceholder: '请输入用户名',
|
userNamePlaceholder: '请输入用户名或邮箱',
|
||||||
phonePlaceholder: '请输入手机号',
|
phonePlaceholder: '请输入手机号',
|
||||||
codePlaceholder: '请输入验证码',
|
codePlaceholder: '请输入验证码',
|
||||||
passwordPlaceholder: '请输入密码',
|
passwordPlaceholder: '请输入密码',
|
||||||
@@ -213,7 +214,7 @@ const local:any = {
|
|||||||
getCodeFailed: '获取验证码失败,请重试'
|
getCodeFailed: '获取验证码失败,请重试'
|
||||||
},
|
},
|
||||||
pwdLogin: {
|
pwdLogin: {
|
||||||
title: '密码登录',
|
title: '登录',
|
||||||
rememberMe: '记住我',
|
rememberMe: '记住我',
|
||||||
forgetPassword: '忘记密码?',
|
forgetPassword: '忘记密码?',
|
||||||
register: '注册账号',
|
register: '注册账号',
|
||||||
@@ -602,6 +603,36 @@ const local:any = {
|
|||||||
Paid:'已支付',
|
Paid:'已支付',
|
||||||
Unpaid:'未支付',
|
Unpaid:'未支付',
|
||||||
},
|
},
|
||||||
|
bill:{
|
||||||
|
loading:'加载中',
|
||||||
|
tipTitle:'账单下载',
|
||||||
|
exportTip:'确定下载吗?',
|
||||||
|
total:'共',
|
||||||
|
invoiceNumber:'账单编号',
|
||||||
|
title: '账单列表',
|
||||||
|
billDate: '账单日期',
|
||||||
|
amount: '金额',
|
||||||
|
billType: '账单类型',
|
||||||
|
statu: '状态',
|
||||||
|
actions: '操作',
|
||||||
|
download: '下载',
|
||||||
|
preview: '预览',
|
||||||
|
status: {
|
||||||
|
pending: '待支付',
|
||||||
|
paid: '已支付',
|
||||||
|
failed: '支付失败',
|
||||||
|
unknown: '未知状态'
|
||||||
|
},
|
||||||
|
exportOk: '下载成功',
|
||||||
|
exportFail: '下载失败',
|
||||||
|
previewTitle: '账单预览',
|
||||||
|
previewFailed: '预览失败',
|
||||||
|
noFile: '暂无账单文件',
|
||||||
|
types: {
|
||||||
|
recharge:'余额充值',
|
||||||
|
package: '套餐费用',
|
||||||
|
},
|
||||||
|
},
|
||||||
Internetdetails:{
|
Internetdetails:{
|
||||||
title:"上网详单",
|
title:"上网详单",
|
||||||
month:"月",
|
month:"月",
|
||||||
@@ -647,7 +678,8 @@ const local:any = {
|
|||||||
endpoint:{
|
endpoint:{
|
||||||
access:"当前设备",
|
access:"当前设备",
|
||||||
records:"历史设备",
|
records:"历史设备",
|
||||||
cdrlrecords:"上网记录"
|
cdrlrecords:"上网记录",
|
||||||
|
bill:'账单记录',
|
||||||
},
|
},
|
||||||
cdrlrecords:{
|
cdrlrecords:{
|
||||||
total:'共',
|
total:'共',
|
||||||
@@ -728,6 +760,8 @@ const local:any = {
|
|||||||
selectPayment:'支付方式',
|
selectPayment:'支付方式',
|
||||||
alipay:'支付宝支付',
|
alipay:'支付宝支付',
|
||||||
wxpay:'微信支付',
|
wxpay:'微信支付',
|
||||||
|
wxpayScan:'使用微信扫码支付',
|
||||||
|
stripe:'Stripe',
|
||||||
balancePay:'余额支付',
|
balancePay:'余额支付',
|
||||||
availableBalance:'可用余额',
|
availableBalance:'可用余额',
|
||||||
paymentSuccess:'支付成功',
|
paymentSuccess:'支付成功',
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import zhCN from './langs/zh-cn';
|
import localeZhCN from './langs/zh-cn';
|
||||||
import enUS from './langs/en-us';
|
import localeEnUS from './langs/en-us';
|
||||||
|
|
||||||
|
const fullZhCN = { ...localeZhCN, ...zhCN };
|
||||||
|
const fullEnUS = { ...localeEnUS, ...enUS };
|
||||||
|
|
||||||
const locales: Record<App.I18n.LangType, App.I18n.Schema> = {
|
const locales: Record<App.I18n.LangType, App.I18n.Schema> = {
|
||||||
'zh-CN': zhCN,
|
'zh-CN': fullZhCN,
|
||||||
'en-US': enUS
|
'en-US': fullEnUS
|
||||||
};
|
};
|
||||||
|
|
||||||
export default locales;
|
export default locales;
|
||||||
@@ -330,7 +330,7 @@ export const customRoutes: GeneratedRoute[] = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: '账单',
|
title: '账单',
|
||||||
i18nKey: 'view.billing',
|
i18nKey: 'view.billing',
|
||||||
icon: 'ant-design:account-book-outlined',
|
icon: 'ant-design:container-outlined',
|
||||||
order: 13,
|
order: 13,
|
||||||
//hideInMenu: true
|
//hideInMenu: true
|
||||||
},
|
},
|
||||||
@@ -354,7 +354,7 @@ export const customRoutes: GeneratedRoute[] = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: '充值服务',
|
title: '充值服务',
|
||||||
i18nKey: 'view.recharge_rechargeservice',
|
i18nKey: 'view.recharge_rechargeservice',
|
||||||
icon: 'ant-design:money-collect-outlined',
|
icon: 'ant-design:wallet-outlined',
|
||||||
order: 2,
|
order: 2,
|
||||||
// hideInMenu: true
|
// hideInMenu: true
|
||||||
},
|
},
|
||||||
@@ -378,7 +378,7 @@ export const customRoutes: GeneratedRoute[] = [
|
|||||||
title: '充值记录',
|
title: '充值记录',
|
||||||
i18nKey: 'view.billing_Rechargehistory',
|
i18nKey: 'view.billing_Rechargehistory',
|
||||||
icon: 'ant-design:calendar-outlined',
|
icon: 'ant-design:calendar-outlined',
|
||||||
order: 4
|
order: 3
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -389,21 +389,21 @@ export const customRoutes: GeneratedRoute[] = [
|
|||||||
title: '套餐记录',
|
title: '套餐记录',
|
||||||
i18nKey: 'view.billing_packagehistories',
|
i18nKey: 'view.billing_packagehistories',
|
||||||
icon: 'ant-design:shopping-outlined',
|
icon: 'ant-design:shopping-outlined',
|
||||||
order: 8
|
order: 4
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'billing_Internetdetails',
|
|
||||||
path: '/billing/Internetdetails',
|
|
||||||
component: 'view.billing_Internetdetails',
|
|
||||||
meta: {
|
|
||||||
title: '上网详单',
|
|
||||||
i18nKey: 'view.billing_Internetdetails',
|
|
||||||
icon: 'material-symbols:filter-list-off',
|
|
||||||
order: 9,
|
|
||||||
hideInMenu: true
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// name: 'billing_Internetdetails',
|
||||||
|
// path: '/billing/Internetdetails',
|
||||||
|
// component: 'view.billing_Internetdetails',
|
||||||
|
// meta: {
|
||||||
|
// title: '上网详单',
|
||||||
|
// i18nKey: 'view.billing_Internetdetails',
|
||||||
|
// icon: 'material-symbols:filter-list-off',
|
||||||
|
// order: 5,
|
||||||
|
// hideInMenu: true
|
||||||
|
// },
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
name: 'billing_cdrlrecords',
|
name: 'billing_cdrlrecords',
|
||||||
path: '/billing/cdrlrecords',
|
path: '/billing/cdrlrecords',
|
||||||
@@ -412,7 +412,18 @@ export const customRoutes: GeneratedRoute[] = [
|
|||||||
title: '上网记录',
|
title: '上网记录',
|
||||||
i18nKey: 'view.endpoint_cdrlrecords',
|
i18nKey: 'view.endpoint_cdrlrecords',
|
||||||
icon: 'ant-design:book-outlined',
|
icon: 'ant-design:book-outlined',
|
||||||
order: 4
|
order: 6
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'billing_bill',
|
||||||
|
path: '/billing/bill',
|
||||||
|
component: 'view.billing_bill',
|
||||||
|
meta: {
|
||||||
|
title: '账单记录',
|
||||||
|
i18nKey: 'view.endpoint_bill',
|
||||||
|
icon: 'ant-design:book-outlined',
|
||||||
|
order: 8
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -425,7 +436,7 @@ export const customRoutes: GeneratedRoute[] = [
|
|||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
order: 10,
|
order: 10,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+11
-2
@@ -1,5 +1,5 @@
|
|||||||
import { request } from '../request';
|
|
||||||
|
|
||||||
|
import { request, rawRequest } from '../request';
|
||||||
/**
|
/**
|
||||||
* Login
|
* Login
|
||||||
*
|
*
|
||||||
@@ -206,6 +206,15 @@ export function resetPasswordByEmail(data: { email: string; code: string; passwo
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/** 下载账单 PDF 文件 */
|
||||||
|
/** 下载账单 PDF 文件(使用 axios 直连) */
|
||||||
|
/** 下载账单 PDF 文件(使用 rawRequest,避免 transformBackendResponse 破坏 blob) */
|
||||||
|
export function downloadBill(id: string) {
|
||||||
|
return request({
|
||||||
|
url: `/u/bill/download/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -37,3 +37,43 @@ export function payBalance(params: { orderId: string }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Paypal createOrder */
|
||||||
|
export function payPalOrders(params: {orderId: number}) {
|
||||||
|
return request({
|
||||||
|
url: '/payment/paypal/orders',
|
||||||
|
method: 'post',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Paypal captureOrder */
|
||||||
|
export function payPalCapture(paypalOrderId: string, orderId: number) {
|
||||||
|
return request({
|
||||||
|
url: `/payment/paypal/orders/${paypalOrderId}/capture/${orderId}`,
|
||||||
|
method: 'post'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get paypal configuration */
|
||||||
|
export function getPayPalConfig() {
|
||||||
|
return request({
|
||||||
|
url: '/payment/paypal/result/one',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stripe pay with orderId */
|
||||||
|
export function stripePay(orderId: number) {
|
||||||
|
return request({
|
||||||
|
url: `/payment/stripe/pay/${orderId}`,
|
||||||
|
method: 'post'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/** Get payment configuration */
|
||||||
|
export function getPaymentConfig() {
|
||||||
|
return request({
|
||||||
|
url: '/u/config/pay',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
Vendored
+14
-7
@@ -676,13 +676,20 @@ declare namespace Api {
|
|||||||
namespace Bill {
|
namespace Bill {
|
||||||
/** Bill record information */
|
/** Bill record information */
|
||||||
interface BillRecord {
|
interface BillRecord {
|
||||||
id: string;
|
/** 账单编号 */
|
||||||
startTime: string | null;
|
invoiceNumber: string;
|
||||||
endTime: string | null;
|
/** 账单金额 */
|
||||||
traffic: string | null;
|
amount: number;
|
||||||
amount: string;
|
/** 账单类型:0-购买套餐,1-余额充值 */
|
||||||
status: number;
|
type: 0 | 1;
|
||||||
createTime: string;
|
/** 账单时间 */
|
||||||
|
invoiceTime: string;
|
||||||
|
/** 账单状态:0-待支付,1-已支付,2-支付失败 */
|
||||||
|
status: 0 | 1 | 2;
|
||||||
|
/** 账单文件URL */
|
||||||
|
fileUrl?: string;
|
||||||
|
/** 发票文件URL */
|
||||||
|
invoiceFile?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bill list response */
|
/** Bill list response */
|
||||||
|
|||||||
Vendored
+7
@@ -30,6 +30,7 @@ declare global {
|
|||||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||||
|
const constructPaypalBtn: typeof import('../utils/paypal')['constructPaypalBtn']
|
||||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||||
const createApp: typeof import('vue')['createApp']
|
const createApp: typeof import('vue')['createApp']
|
||||||
@@ -89,6 +90,7 @@ declare global {
|
|||||||
const doPostUser: typeof import('../service/api/user')['doPostUser']
|
const doPostUser: typeof import('../service/api/user')['doPostUser']
|
||||||
const doPutRole: typeof import('../service/api/role')['doPutRole']
|
const doPutRole: typeof import('../service/api/role')['doPutRole']
|
||||||
const doPutUser: typeof import('../service/api/user')['doPutUser']
|
const doPutUser: typeof import('../service/api/user')['doPutUser']
|
||||||
|
const downloadBill: typeof import('../service/api/auth')['downloadBill']
|
||||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
const emptyInfo: typeof import('../store/modules/auth/shared')['emptyInfo']
|
const emptyInfo: typeof import('../store/modules/auth/shared')['emptyInfo']
|
||||||
@@ -127,6 +129,8 @@ declare global {
|
|||||||
const getFixedTabIds: typeof import('../store/modules/tab/shared')['getFixedTabIds']
|
const getFixedTabIds: typeof import('../store/modules/tab/shared')['getFixedTabIds']
|
||||||
const getFixedTabs: typeof import('../store/modules/tab/shared')['getFixedTabs']
|
const getFixedTabs: typeof import('../store/modules/tab/shared')['getFixedTabs']
|
||||||
const getGlobalMenusByAuthRoutes: typeof import('../store/modules/route/shared')['getGlobalMenusByAuthRoutes']
|
const getGlobalMenusByAuthRoutes: typeof import('../store/modules/route/shared')['getGlobalMenusByAuthRoutes']
|
||||||
|
const getPayPalConfig: typeof import('../service/api/payment')['getPayPalConfig']
|
||||||
|
const getPaymentConfig: typeof import('../service/api/payment')['getPaymentConfig']
|
||||||
const getQueryParams: typeof import('../utils/common')['getQueryParams']
|
const getQueryParams: typeof import('../utils/common')['getQueryParams']
|
||||||
const getRouteIcons: typeof import('../store/modules/tab/shared')['getRouteIcons']
|
const getRouteIcons: typeof import('../store/modules/tab/shared')['getRouteIcons']
|
||||||
const getSelectedMenuKeyPathByKey: typeof import('../store/modules/route/shared')['getSelectedMenuKeyPathByKey']
|
const getSelectedMenuKeyPathByKey: typeof import('../store/modules/route/shared')['getSelectedMenuKeyPathByKey']
|
||||||
@@ -180,6 +184,8 @@ declare global {
|
|||||||
const onUpdated: typeof import('vue')['onUpdated']
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||||
const payBalance: typeof import('../service/api/payment')['payBalance']
|
const payBalance: typeof import('../service/api/payment')['payBalance']
|
||||||
|
const payPalCapture: typeof import('../service/api/payment')['payPalCapture']
|
||||||
|
const payPalOrders: typeof import('../service/api/payment')['payPalOrders']
|
||||||
const pick: typeof import('lodash-es')['pick']
|
const pick: typeof import('lodash-es')['pick']
|
||||||
const provide: typeof import('vue')['provide']
|
const provide: typeof import('vue')['provide']
|
||||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||||
@@ -211,6 +217,7 @@ declare global {
|
|||||||
const shallowRef: typeof import('vue')['shallowRef']
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
const sortRoutesByOrder: typeof import('../store/modules/route/shared')['sortRoutesByOrder']
|
const sortRoutesByOrder: typeof import('../store/modules/route/shared')['sortRoutesByOrder']
|
||||||
const storeToRefs: typeof import('pinia')['storeToRefs']
|
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||||
|
const stripePay: typeof import('../service/api/payment')['stripePay']
|
||||||
const submitKYCVerification: typeof import('../service/api/auth')['submitKYCVerification']
|
const submitKYCVerification: typeof import('../service/api/auth')['submitKYCVerification']
|
||||||
const submitOrder: typeof import('../service/api/auth')['submitOrder']
|
const submitOrder: typeof import('../service/api/auth')['submitOrder']
|
||||||
const submitPackageOrder: typeof import('../service/api/auth')['submitPackageOrder']
|
const submitPackageOrder: typeof import('../service/api/auth')['submitPackageOrder']
|
||||||
|
|||||||
Vendored
+1
-2
@@ -34,14 +34,12 @@ declare module 'vue' {
|
|||||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||||
APagination: typeof import('ant-design-vue/es')['Pagination']
|
|
||||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||||
APopover: typeof import('ant-design-vue/es')['Popover']
|
APopover: typeof import('ant-design-vue/es')['Popover']
|
||||||
AppLoading: typeof import('./../components/common/app-loading.vue')['default']
|
AppLoading: typeof import('./../components/common/app-loading.vue')['default']
|
||||||
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
|
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
|
||||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
|
||||||
ARow: typeof import('ant-design-vue/es')['Row']
|
ARow: typeof import('ant-design-vue/es')['Row']
|
||||||
ASegmented: typeof import('ant-design-vue/es')['Segmented']
|
ASegmented: typeof import('ant-design-vue/es')['Segmented']
|
||||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||||
@@ -80,6 +78,7 @@ declare module 'vue' {
|
|||||||
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
|
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
|
||||||
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
|
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
|
||||||
OrderConfirmModal: typeof import('./../components/order-confirm/orderConfirmModal.vue')['default']
|
OrderConfirmModal: typeof import('./../components/order-confirm/orderConfirmModal.vue')['default']
|
||||||
|
PaypalButton: typeof import('./../components/payment/paypal-button.vue')['default']
|
||||||
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
|
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
|
||||||
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
|
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { loadScript } from "@paypal/paypal-js";
|
||||||
|
import { payPalOrders, payPalCapture } from '@/service/api/payment';
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const locale = appStore.locale.includes("zh") ? "zh_CN" : "en_US"
|
||||||
|
|
||||||
|
export const constructPaypalBtn = async(clientId: string, orderId: number, currency: string) =>{
|
||||||
|
loadScript({ clientId, currency, locale: locale })
|
||||||
|
.then((paypal: any) => {
|
||||||
|
paypal
|
||||||
|
.Buttons({
|
||||||
|
style: {
|
||||||
|
shape: "rect",
|
||||||
|
layout: "vertical",
|
||||||
|
color: "white",
|
||||||
|
label: "paypal",
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async createOrder() {
|
||||||
|
try {
|
||||||
|
const response = await payPalOrders({orderId: orderId});
|
||||||
|
|
||||||
|
const orderData = response.data;
|
||||||
|
if (orderData.id) {
|
||||||
|
return orderData.id;
|
||||||
|
}
|
||||||
|
const errorDetail = orderData?.details?.[0];
|
||||||
|
const errorMessage = errorDetail
|
||||||
|
? `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})`
|
||||||
|
: JSON.stringify(orderData);
|
||||||
|
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
// resultMessage(`Could not initiate PayPal Checkout...<br><br>${error}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async onApprove(data, actions) {
|
||||||
|
try {
|
||||||
|
const response = await payPalCapture(data.orderID, orderId)
|
||||||
|
|
||||||
|
const orderData = response.data
|
||||||
|
// Three cases to handle:
|
||||||
|
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
|
||||||
|
// (2) Other non-recoverable errors -> Show a failure message
|
||||||
|
// (3) Successful transaction -> Show confirmation or thank you message
|
||||||
|
|
||||||
|
const errorDetail = orderData?.details?.[0];
|
||||||
|
|
||||||
|
if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
|
||||||
|
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
|
||||||
|
// recoverable state, per
|
||||||
|
// https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
|
||||||
|
return actions.restart();
|
||||||
|
} else if (errorDetail) {
|
||||||
|
// (2) Other non-recoverable errors -> Show a failure message
|
||||||
|
throw new Error(`${errorDetail.description} (${orderData.debug_id})`);
|
||||||
|
} else if (!orderData.purchase_units) {
|
||||||
|
throw new Error(JSON.stringify(orderData));
|
||||||
|
} else {
|
||||||
|
// (3) Successful transaction -> Show confirmation or thank you message
|
||||||
|
// Or go to another URL: actions.redirect('thank_you.html');
|
||||||
|
const transaction =
|
||||||
|
orderData?.purchase_units?.[0]?.payments?.captures?.[0] ||
|
||||||
|
orderData?.purchase_units?.[0]?.payments?.authorizations?.[0];
|
||||||
|
// resultMessage(
|
||||||
|
// `Transaction ${transaction.status}: ${transaction.id}<br>
|
||||||
|
//<br>See console for all available details`
|
||||||
|
//);
|
||||||
|
console.log(
|
||||||
|
"Capture result",
|
||||||
|
orderData,
|
||||||
|
JSON.stringify(orderData, null, 2)
|
||||||
|
);
|
||||||
|
location.reload();
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
// resultMessage(
|
||||||
|
// `Sorry, your transaction could not be processed...<br><br>${error}`
|
||||||
|
//);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.render("#paypal-buttons")
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("failed to render the PayPal Buttons", error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("failed to load the PayPal JS SDK script", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example function to show a result to the user. Your site's UI library can be used instead.
|
||||||
|
function resultMessage(message: any) {
|
||||||
|
const container: any = document.querySelector("#result-message");
|
||||||
|
container.innerHTML = message;
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ import ResetPwd from './modules/reset-pwd.vue';
|
|||||||
import BindWechat from './modules/bind-wechat.vue';
|
import BindWechat from './modules/bind-wechat.vue';
|
||||||
import { WifiOutlined } from '@ant-design/icons-vue';
|
import { WifiOutlined } from '@ant-design/icons-vue';
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
const appStore = useAppStore();
|
||||||
interface Props {
|
interface Props {
|
||||||
/** The login module */
|
/** The login module */
|
||||||
module?: UnionKey.LoginModule;
|
module?: UnionKey.LoginModule;
|
||||||
@@ -53,12 +55,18 @@ const bgColor = computed(() => {
|
|||||||
<WaveBg :theme-color="bgThemeColor" />
|
<WaveBg :theme-color="bgThemeColor" />
|
||||||
<ACard class="relative z-4">
|
<ACard class="relative z-4">
|
||||||
<div class="w-400px lt-sm:w-300px">
|
<div class="w-400px lt-sm:w-300px">
|
||||||
<header class="flex justify-center items-center">
|
<header class="flex items-center justify-center relative w-full">
|
||||||
|
<div class="flex items-center">
|
||||||
<WifiOutlined class="text-64px text-primary lt-sm:text-48px" style="margin-left: 9px"/>
|
<WifiOutlined class="text-64px text-primary lt-sm:text-48px" style="margin-left: 9px"/>
|
||||||
<h3 class="text-10px text-primary font-medium self-end">{{ "WANFi" }}</h3>
|
<h3 class="text-10px text-primary font-medium self-end">{{ $t('system.title') }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="absolute right-0">
|
||||||
|
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main >
|
<main >
|
||||||
<h3 class="text-28px text-primary font-500 lt-sm:text-22px ">{{ t(activeModule.label) }}</h3>
|
<h3 class="text-28px text-primary font-500 lt-sm:text-22px ">{{ t(activeModule.label) }}</h3>
|
||||||
|
|
||||||
<div class="animation-slide-in-left ">
|
<div class="animation-slide-in-left ">
|
||||||
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
||||||
<component :is="activeModule.component" />
|
<component :is="activeModule.component" />
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ const model = reactive<LoginForm>({
|
|||||||
wanfiRedirectParams: {}
|
wanfiRedirectParams: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules = {
|
// const rules = {
|
||||||
username: patternRules.username,
|
// username: patternRules.username,
|
||||||
};
|
// };
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
await validate();
|
await validate();
|
||||||
@@ -95,7 +95,7 @@ async function getCheckCode() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AForm ref="formRef" :model="model" :rules="rules">
|
<AForm ref="formRef" :model="model">
|
||||||
<AFormItem name="username">
|
<AFormItem name="username">
|
||||||
<AInput v-model:value="model.username" size="large" :placeholder="$t('page.login.common.userNamePlaceholder')" autocomplete="off"/>
|
<AInput v-model:value="model.username" size="large" :placeholder="$t('page.login.common.userNamePlaceholder')" autocomplete="off"/>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
|
|||||||
@@ -0,0 +1,410 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, h } from 'vue'
|
||||||
|
import type { TableColumnsType } from 'ant-design-vue'
|
||||||
|
import { Table as ATable, Button as AButton, Modal, message } from 'ant-design-vue'
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
import { useTable } from '@/hooks/common/table'
|
||||||
|
import { fetchBillHistory, downloadBill } from '@/service/api/auth'
|
||||||
|
import { getPaymentConfig } from '@/service/api/payment'
|
||||||
|
import type { Api } from '@/typings/api'
|
||||||
|
import { SimpleScrollbar } from '~/packages/materials/src'
|
||||||
|
import { Card as ACard, Tag as ATag } from 'ant-design-vue'
|
||||||
|
import { useElementSize } from '@vueuse/core'
|
||||||
|
import {
|
||||||
|
EyeOutlined,
|
||||||
|
DownloadOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const { t } = useI18n()
|
||||||
|
const currencySymbol = ref('¥')
|
||||||
|
|
||||||
|
// 获取货币符号
|
||||||
|
const fetchCurrencySymbol = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getPaymentConfig()
|
||||||
|
if (response && response.data) {
|
||||||
|
currencySymbol.value = response.data.currencySymbol
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('Failed to fetch currency symbol:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期时间
|
||||||
|
const formatDateTime = (dateTimeString: string) => {
|
||||||
|
if (!dateTimeString) return ''
|
||||||
|
const date = new Date(dateTimeString)
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式布局
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
|
// 处理文件URL
|
||||||
|
const parseFile = (path: string) => {
|
||||||
|
let baseUrl = import.meta.env.VITE_SERVICE_BASE_URL;
|
||||||
|
// console.log(baseUrl)
|
||||||
|
// debugger
|
||||||
|
|
||||||
|
// 移除末尾的斜杠
|
||||||
|
baseUrl = baseUrl.replace(/\/+$/, '');
|
||||||
|
|
||||||
|
// 从完整 URL 中提取域名部分
|
||||||
|
if (baseUrl.includes('://')) {
|
||||||
|
try {
|
||||||
|
const url = new URL(baseUrl);
|
||||||
|
baseUrl = url.host; // 这将获取域名和端口(如果有的话)
|
||||||
|
} catch (error) {
|
||||||
|
// 如果解析失败,使用原始值的域名部分
|
||||||
|
baseUrl = baseUrl.replace(/^https?:\/\//, '').split('/')[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造最终的 URL
|
||||||
|
return `${path.startsWith('/') ? path : '/' + path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapperEl = ref<HTMLElement | null>(null)
|
||||||
|
const { height: wrapperElHeight } = useElementSize(wrapperEl)
|
||||||
|
|
||||||
|
const scrollConfig = computed(() => ({
|
||||||
|
y: wrapperElHeight.value - 72,
|
||||||
|
x: 800
|
||||||
|
}))
|
||||||
|
|
||||||
|
const getStatusColor = (status: number) => {
|
||||||
|
switch (status) {
|
||||||
|
case 0:
|
||||||
|
return 'warning'
|
||||||
|
case 1:
|
||||||
|
return 'success'
|
||||||
|
case 2:
|
||||||
|
return 'error'
|
||||||
|
default:
|
||||||
|
return 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusText = (status: number) => {
|
||||||
|
switch (status) {
|
||||||
|
case 0:
|
||||||
|
return t('page.bill.status.pending')
|
||||||
|
case 1:
|
||||||
|
return t('page.bill.status.paid')
|
||||||
|
case 2:
|
||||||
|
return t('page.bill.status.failed')
|
||||||
|
default:
|
||||||
|
return t('page.bill.status.unknown')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格相关配置
|
||||||
|
const { columns, data, loading, getData, mobilePagination, searchParams } = useTable<Api.Bill.BillRecord>({
|
||||||
|
apiFn: fetchBillHistory,
|
||||||
|
apiParams: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10
|
||||||
|
},
|
||||||
|
rowKey: 'id',
|
||||||
|
pagination: true,
|
||||||
|
columns: (): AntDesign.TableColumn<Api.Bill.BillRecord>[] => [
|
||||||
|
{
|
||||||
|
key: 'invoiceNumber',
|
||||||
|
dataIndex: 'invoiceNumber',
|
||||||
|
title: t('page.bill.invoiceNumber'),
|
||||||
|
align: 'left',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'invoiceTime',
|
||||||
|
dataIndex: 'invoiceTime',
|
||||||
|
title: t('page.bill.billDate'),
|
||||||
|
align: 'center',
|
||||||
|
width: 150,
|
||||||
|
customRender: ({ text }: { text: string }) => text ? formatDateTime(text) : ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'amount',
|
||||||
|
dataIndex: 'amount',
|
||||||
|
title: t('page.bill.amount'),
|
||||||
|
align: 'center',
|
||||||
|
width: 120,
|
||||||
|
responsive: ['md'],
|
||||||
|
customRender: ({ text }: { text: number }) => {
|
||||||
|
const formattedAmount = Number(text).toFixed(2)
|
||||||
|
return `${currencySymbol.value}${formattedAmount}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
dataIndex: 'type',
|
||||||
|
title: t('page.bill.billType'),
|
||||||
|
align: 'center',
|
||||||
|
width: 120,
|
||||||
|
responsive: ['md'],
|
||||||
|
customRender: ({ text }: { text: Api.Bill.BillRecord['type'] }) => {
|
||||||
|
const typeMap: Record<Api.Bill.BillRecord['type'],{ color: string; label: string }> = {
|
||||||
|
0: { color: 'blue', label: t('page.bill.types.package') },
|
||||||
|
1: { color: 'gold', label: t('page.bill.types.recharge') }
|
||||||
|
}
|
||||||
|
const typeInfo = typeMap[text] || { color: 'default', label: t('page.bill.types.unknown') }
|
||||||
|
return h(ATag, { color: typeInfo.color }, () => typeInfo.label)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'invoiceFile',
|
||||||
|
title: t('page.bill.actions'),
|
||||||
|
align: 'center',
|
||||||
|
width: 60,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 下载账单
|
||||||
|
const handleExport = (record: Api.Bill.BillRecord) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('page.bill.tipTitle'),
|
||||||
|
content: t('page.bill.exportTip'),
|
||||||
|
async onOk() {
|
||||||
|
const key = 'exportJob';
|
||||||
|
message.loading({ content: t('page.bill.loading'), key });
|
||||||
|
try {
|
||||||
|
const res = await downloadBill(record.id);
|
||||||
|
// console.log('downloadBill 返回:', res);
|
||||||
|
let blob;
|
||||||
|
if (res instanceof Blob) {
|
||||||
|
blob = res;
|
||||||
|
} else if (res.data instanceof Blob) {
|
||||||
|
blob = res.data;
|
||||||
|
} else {
|
||||||
|
blob = new Blob([res.data]);
|
||||||
|
}
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = `invoice-${record.invoiceNumber || record.id}.pdf`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
message.success({ content: t('page.bill.exportOk'), key, duration: 2 });
|
||||||
|
} catch (e) {
|
||||||
|
// console.error('导出异常:', e);
|
||||||
|
message.error({ content: t('page.bill.exportFail'), key, duration: 2 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览账单
|
||||||
|
const handlePreview = async (record: Api.Bill.BillRecord) => {
|
||||||
|
// console.log('handlePreview被调用', record);
|
||||||
|
if (!record.invoiceFile) {
|
||||||
|
message.error(t('page.bill.noFile'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const url = parseFile(record.invoiceFile)
|
||||||
|
// console.log('预览PDF地址:', url, record.invoiceFile)
|
||||||
|
Modal.info({
|
||||||
|
title: t('page.bill.previewTitle'),
|
||||||
|
width: '80%',
|
||||||
|
content: h('iframe', {
|
||||||
|
src: url,
|
||||||
|
style: {
|
||||||
|
width: '100%',
|
||||||
|
height: '80vh',
|
||||||
|
border: 'none'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
message.error(t('page.bill.previewFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
router.push('/billing/billservice');
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchCurrencySymbol()
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SimpleScrollbar>
|
||||||
|
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||||
|
<ACard
|
||||||
|
:title="
|
||||||
|
isMobile
|
||||||
|
? h('div', { class: 'flex justify-between items-center w-full' }, [
|
||||||
|
h('span', t('page.bill.title')),
|
||||||
|
h(
|
||||||
|
AButton,
|
||||||
|
{
|
||||||
|
onClick: handleBack,
|
||||||
|
size: 'small',
|
||||||
|
style: { marginLeft: '8px' }
|
||||||
|
},
|
||||||
|
{ default: () => t('page.login.common.back') }
|
||||||
|
)
|
||||||
|
])
|
||||||
|
: t('page.bill.title')
|
||||||
|
"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ flex: 1, overflow: 'hidden' }"
|
||||||
|
class="flex-col-stretch sm:flex-1-hidden card-wrapper"
|
||||||
|
>
|
||||||
|
<ATable
|
||||||
|
ref="wrapperEl"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="data"
|
||||||
|
:loading="loading"
|
||||||
|
row-key="id"
|
||||||
|
size="small"
|
||||||
|
|
||||||
|
:pagination="{
|
||||||
|
...mobilePagination,
|
||||||
|
total: mobilePagination.total,
|
||||||
|
current: searchParams.pageNum,
|
||||||
|
pageSize: searchParams.pageSize,
|
||||||
|
showTotal: (total: number) => `${t('page.bill.total')} ${total} `
|
||||||
|
}"
|
||||||
|
:scroll="scrollConfig"
|
||||||
|
class="h-full"
|
||||||
|
@change="(pagination) => {
|
||||||
|
searchParams.pageNum = pagination.current;
|
||||||
|
searchParams.pageSize = pagination.pageSize;
|
||||||
|
getData();
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #expandedRowRender="{ record }">
|
||||||
|
<div v-if="isMobile" class="pl-4">
|
||||||
|
<div class="mb-2">
|
||||||
|
<strong>{{ t('page.bill.billDate') }}:</strong>
|
||||||
|
{{ formatDateTime(record.invoiceTime) }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<strong>{{ t('page.bill.amount') }}:</strong>
|
||||||
|
{{ currencySymbol + Number(record.amount).toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<strong>{{ t('page.bill.billType') }}:</strong>
|
||||||
|
<ATag :color="record.type === 0 ? 'blue' : 'gold'">
|
||||||
|
{{ record.type === 0 ? t('page.bill.types.package') : t('page.bill.types.recharge') }}
|
||||||
|
</ATag>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<AButton
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
:disabled="!record.invoiceFile"
|
||||||
|
@click="handlePreview(record)"
|
||||||
|
>
|
||||||
|
<EyeOutlined />
|
||||||
|
</AButton>
|
||||||
|
<AButton
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
:disabled="!record.invoiceFile"
|
||||||
|
@click="handleExport(record)"
|
||||||
|
>
|
||||||
|
<DownloadOutlined />
|
||||||
|
</AButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'status'">
|
||||||
|
<ATag :color="getStatusColor(record.status)">
|
||||||
|
{{ getStatusText(record.status) }}
|
||||||
|
</ATag>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'invoiceFile'">
|
||||||
|
<div class="table-file-wrapper">
|
||||||
|
<AButton
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
:disabled="!record.invoiceFile"
|
||||||
|
@click="handlePreview(record)"
|
||||||
|
>
|
||||||
|
<EyeOutlined />
|
||||||
|
</AButton>
|
||||||
|
<AButton
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
:disabled="!record.invoiceFile"
|
||||||
|
@click="handleExport(record)"
|
||||||
|
>
|
||||||
|
<DownloadOutlined />
|
||||||
|
</AButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</ATable>
|
||||||
|
</ACard>
|
||||||
|
</div>
|
||||||
|
</SimpleScrollbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.h-full {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-wrapper {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-file-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-btn-link) {
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-btn-link:hover) {
|
||||||
|
color: #1890ff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-modal-body) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-modal-confirm-content) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-table-row-expand-icon-cell) {
|
||||||
|
width: 24px !important;
|
||||||
|
min-width: 24px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
/* 在PC端隐藏展开按钮图标 */
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
:deep(.ant-table-row-expand-icon) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,7 +7,8 @@ import {
|
|||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
CreditCardOutlined,
|
CreditCardOutlined,
|
||||||
ContainerOutlined,
|
ContainerOutlined,
|
||||||
MoneyCollectOutlined,
|
WalletOutlined,
|
||||||
|
AuditOutlined
|
||||||
|
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ const router = useRouter();
|
|||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
icon: MoneyCollectOutlined,
|
icon: WalletOutlined,
|
||||||
title: t('page.recharge.recharge'),
|
title: t('page.recharge.recharge'),
|
||||||
path: '/billing/recharge'
|
path: '/billing/recharge'
|
||||||
|
|
||||||
@@ -45,6 +46,11 @@ const menuItems = [
|
|||||||
icon: FileTextOutlined,
|
icon: FileTextOutlined,
|
||||||
title: t('page.endpoint.cdrlrecords'),
|
title: t('page.endpoint.cdrlrecords'),
|
||||||
path: '/billing/cdrlrecords'
|
path: '/billing/cdrlrecords'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: AuditOutlined,
|
||||||
|
title:t('page.endpoint.bill'),
|
||||||
|
path: '/billing/bill'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch } from 'vue'
|
import {ref, onMounted, watch, computed} from 'vue'
|
||||||
import type { TableColumnsType } from 'ant-design-vue'
|
import type { TableColumnsType } from 'ant-design-vue'
|
||||||
import { HistoryOutlined } from '@ant-design/icons-vue'
|
import { HistoryOutlined } from '@ant-design/icons-vue'
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { fetchCDRHistory } from '@/service/api/auth';
|
import { fetchCDRHistory } from '@/service/api/auth';
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import {useWindowSize} from "@vueuse/core";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'cdrlrecords'
|
name: 'cdrlrecords'
|
||||||
});
|
});
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
const isMobile = computed(() => width.value < 768);
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
router.push('/billing/billservice');
|
router.push('/billing/billservice');
|
||||||
};
|
};
|
||||||
@@ -317,7 +320,7 @@ watch(tableData, (newData) => {
|
|||||||
|
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-space size="middle">
|
<a-space size="middle">
|
||||||
<a-button @click="handleBack">
|
<a-button v-if="isMobile" @click="handleBack">
|
||||||
{{ t('page.login.common.back') }}
|
{{ t('page.login.common.back') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" :loading="tableLoading" @click="handleSearch">
|
<a-button type="primary" :loading="tableLoading" @click="handleSearch">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useRouter } from 'vue-router';
|
|||||||
import { fetchPackageHistory } from '@/service/api/auth';
|
import { fetchPackageHistory } from '@/service/api/auth';
|
||||||
import { useWindowSize } from '@vueuse/core';
|
import { useWindowSize } from '@vueuse/core';
|
||||||
import { useTable, useTableOperate } from '@/hooks/common/table';
|
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||||
|
import { getPaymentConfig } from '@/service/api/payment';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
@@ -14,7 +15,18 @@ const handleBack = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const currencySymbol = ref('¥');
|
||||||
|
const tableLoading = ref(false);
|
||||||
|
const fetchCurrencySymbol = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getPaymentConfig();
|
||||||
|
if (response && response.data) {
|
||||||
|
currencySymbol.value = response.data.currencySymbol || '¥';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch currency symbol:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
const packageData = ref<Api.Package.PackageHistoryRecord[]>([]);
|
const packageData = ref<Api.Package.PackageHistoryRecord[]>([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
|
|
||||||
@@ -36,6 +48,7 @@ const formatDateTime = (dateTimeString: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPackageData = async () => {
|
const getPackageData = async () => {
|
||||||
|
tableLoading.value=true;
|
||||||
try {
|
try {
|
||||||
const response = await fetchPackageHistory({
|
const response = await fetchPackageHistory({
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
@@ -48,9 +61,11 @@ const getPackageData = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('Failed to fetch package history:', error);
|
// console.error('Failed to fetch package history:', error);
|
||||||
}
|
}
|
||||||
|
tableLoading.value=false;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
fetchCurrencySymbol();
|
||||||
getPackageData();
|
getPackageData();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,7 +90,7 @@ const showPackageDetail = (record: Api.Package.PackageHistoryRecord) => {
|
|||||||
]),
|
]),
|
||||||
h('div', { class: 'detail-item' }, [
|
h('div', { class: 'detail-item' }, [
|
||||||
h('span', { class: 'detail-label' }, t('page.packagehistories.price') + ':'),
|
h('span', { class: 'detail-label' }, t('page.packagehistories.price') + ':'),
|
||||||
h('span', { class: 'detail-value' }, `¥${Number(record.upackage?.price || 0).toFixed(2)}`)
|
h('span', { class: 'detail-value' }, `${currencySymbol.value}${Number(record.upackage?.price || 0).toFixed(2)}`)
|
||||||
]),
|
]),
|
||||||
h('div', { class: 'detail-item' }, [
|
h('div', { class: 'detail-item' }, [
|
||||||
h('span', { class: 'detail-label' }, t('page.packagehistories.traffic') + ':'),
|
h('span', { class: 'detail-label' }, t('page.packagehistories.traffic') + ':'),
|
||||||
@@ -168,7 +183,7 @@ const { columns, data, loading, getData, mobilePagination, searchParams } = useT
|
|||||||
key: 'orderAmount',
|
key: 'orderAmount',
|
||||||
customRender: ({ text }: { text: string }) => {
|
customRender: ({ text }: { text: string }) => {
|
||||||
const formattedAmount = Number(text).toFixed(2);
|
const formattedAmount = Number(text).toFixed(2);
|
||||||
return `¥${formattedAmount}`;
|
return `${currencySymbol.value}${formattedAmount}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -223,9 +238,15 @@ const formatTraffic = (bytes: number): string => {
|
|||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="text-lg font-bold mb-4 flex justify-between items-center">
|
<div class="text-lg font-bold mb-4 flex justify-between items-center">
|
||||||
<span>{{ t('page.packagehistories.title') }}</span>
|
<span>{{ t('page.packagehistories.title') }}</span>
|
||||||
<a-button @click="handleBack">
|
<a-button v-if="isMobile" @click="handleBack" >
|
||||||
{{ t('page.login.common.back') }}
|
{{ t('page.login.common.back') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<a-button type="primary" :loading="tableLoading" @click="getPackageData">
|
||||||
|
<template #icon>
|
||||||
|
<span class="i-carbon:refresh"></span>
|
||||||
|
</template>
|
||||||
|
{{ t('page.cdrlrecords.refresh') }}
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ATable :columns="columns" :data-source="data" row-key="id"
|
<ATable :columns="columns" :data-source="data" row-key="id"
|
||||||
@@ -304,4 +325,10 @@ const formatTraffic = (bytes: number): string => {
|
|||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
/* 在PC端隐藏展开按钮图标 */
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
:deep(.ant-table-row-expand-icon) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed, watchEffect } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { AlipayOutlined, WechatOutlined, WalletOutlined, ArrowLeftOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { Modal } from 'ant-design-vue';
|
||||||
|
import PaypalButton from '@/components/payment/paypal-button.vue';
|
||||||
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const emit = defineEmits(['update:visible', 'confirm', 'cancel']);
|
||||||
|
const loading = ref(false);
|
||||||
|
const orderInfo = ref({
|
||||||
|
orderId: '',
|
||||||
|
orderType: 0,
|
||||||
|
orderAmount: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const userBalance = ref(0);
|
||||||
|
|
||||||
|
// 监听余额变化
|
||||||
|
watchEffect(async () => {
|
||||||
|
try {
|
||||||
|
const response = await authStore.getDashboardData();
|
||||||
|
if (response && typeof response === 'object' && !response.error) {
|
||||||
|
const numBalance = Number(response.balance);
|
||||||
|
userBalance.value = isNaN(numBalance) ? 0 : numBalance;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch user balance:', error);
|
||||||
|
userBalance.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Debug: 添加 authStore 状态日志
|
||||||
|
console.log('Debug - Complete Auth Store:', {
|
||||||
|
userInfo: JSON.parse(JSON.stringify(authStore.userInfo)),
|
||||||
|
user: authStore.userInfo?.user ? JSON.parse(JSON.stringify(authStore.userInfo.user)) : null,
|
||||||
|
allKeys: authStore.userInfo ? Object.keys(authStore.userInfo) : [],
|
||||||
|
userKeys: authStore.userInfo?.user ? Object.keys(authStore.userInfo.user) : []
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判断是否显示余额支付按钮
|
||||||
|
const showBalancePayment = computed(() => {
|
||||||
|
// 如果是余额充值,不显示余额支付
|
||||||
|
return orderInfo.value.orderType !== 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判断余额支付是否禁用
|
||||||
|
const isBalancePayDisabled = computed(() => {
|
||||||
|
const balanceNum = userBalance.value; // 已经确保是数字类型
|
||||||
|
const amountNum = Number(orderInfo.value.orderAmount);
|
||||||
|
|
||||||
|
console.log('Debug - Balance Check:', {
|
||||||
|
balance: balanceNum,
|
||||||
|
amount: amountNum,
|
||||||
|
isBalanceNaN: isNaN(balanceNum),
|
||||||
|
isAmountNaN: isNaN(amountNum),
|
||||||
|
loading: loading.value
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果转换后不是有效数字,禁用按钮
|
||||||
|
if (isNaN(balanceNum) || isNaN(amountNum)) {
|
||||||
|
console.log('Debug - Invalid number conversion');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果余额为0或小于订单金额,禁用按钮
|
||||||
|
const isInsufficient = balanceNum === 0 || balanceNum < amountNum;
|
||||||
|
console.log('Debug - Balance sufficient check:', {
|
||||||
|
balance: balanceNum,
|
||||||
|
amount: amountNum,
|
||||||
|
isInsufficient
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isInsufficient) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在加载,禁用按钮
|
||||||
|
return loading.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 从路由参数中获取订单信息
|
||||||
|
onMounted(async () => {
|
||||||
|
const { orderId, orderType, orderAmount } = route.query;
|
||||||
|
|
||||||
|
// 添加参数验证和错误处理
|
||||||
|
if (!orderId) {
|
||||||
|
Modal.error({
|
||||||
|
title: t('page.common.error'),
|
||||||
|
content: t('page.pay.missingOrderId'),
|
||||||
|
onOk: () => router.back()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
orderInfo.value = {
|
||||||
|
orderId: orderId as string,
|
||||||
|
orderType: orderType ? parseInt(orderType as string) : 0,
|
||||||
|
orderAmount: orderAmount ? parseFloat(orderAmount as string) : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// 验证转换后的值是否有效
|
||||||
|
if (isNaN(orderInfo.value.orderType) || isNaN(orderInfo.value.orderAmount)) {
|
||||||
|
throw new Error('Invalid parameter values');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: 打印订单信息
|
||||||
|
console.log('Debug - Order Info:', orderInfo.value);
|
||||||
|
} catch (error) {
|
||||||
|
Modal.error({
|
||||||
|
title: t('page.common.error'),
|
||||||
|
content: t('page.pay.invalidParameters'),
|
||||||
|
onOk: () => router.back()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const orderTypeMap = {
|
||||||
|
0: t('page.order.packagePurchase'),
|
||||||
|
1: t('page.order.balanceRecharge')
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const handleConfirm = (paymentMethod: 'alipay' | 'wxpay' | 'balance') => {
|
||||||
|
|
||||||
|
if (paymentMethod === 'balance') {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('page.order.confirmPayment'),
|
||||||
|
content: t('page.order.balancePayConfirm'),
|
||||||
|
okText: t('page.order.confirm'),
|
||||||
|
cancelText: t('page.order.cancel'),
|
||||||
|
onOk: () => {
|
||||||
|
// 处理余额支付
|
||||||
|
emit('confirm', paymentMethod);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 处理其他支付方式
|
||||||
|
// emit('confirm', paymentMethod);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
router.back();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="pay-container">
|
||||||
|
<div class="header">
|
||||||
|
<a-button type="link" @click="handleBack" class="back-button">
|
||||||
|
<ArrowLeftOutlined />
|
||||||
|
{{ t('page.pay.back') }}
|
||||||
|
</a-button>
|
||||||
|
<h2>{{ t('page.pay.title') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="order-info">
|
||||||
|
<h3>{{ t('page.pay.orderDetails') }}</h3>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('page.order.orderType') }}:</span>
|
||||||
|
<span class="value">{{ orderTypeMap[orderInfo.orderType] }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('page.order.orderAmount') }}:</span>
|
||||||
|
<span class="value highlight">¥{{ orderInfo.orderAmount.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('page.order.orderId') }}:</span>
|
||||||
|
<span class="value">{{ orderInfo.orderId }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">{{ t('page.order.balance') }}:</span>
|
||||||
|
<span class="value">¥{{ userBalance.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="payment-methods">
|
||||||
|
<h3>{{ t('page.order.selectPayment') }}</h3>
|
||||||
|
<div class="methods-container">
|
||||||
|
<!-- 余额支付按钮 -->
|
||||||
|
<div
|
||||||
|
v-if="showBalancePayment"
|
||||||
|
class="method-item"
|
||||||
|
:class="{ disabled: loading || isBalancePayDisabled }"
|
||||||
|
@click="!loading && !isBalancePayDisabled && handleConfirm('balance')"
|
||||||
|
>
|
||||||
|
<WalletOutlined class="payment-icon balance-icon" />
|
||||||
|
<span>{{ t('page.order.balancePay') }}</span>
|
||||||
|
<span v-if="isBalancePayDisabled" class="balance-insufficient">
|
||||||
|
{{ t('page.order.insufficientBalance') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="method-item"
|
||||||
|
:class="{ disabled: loading }"
|
||||||
|
@click="!loading && handleConfirm('alipay')"
|
||||||
|
>
|
||||||
|
<AlipayOutlined class="payment-icon alipay-icon" />
|
||||||
|
<span>{{ t('page.order.alipay') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="method-item"
|
||||||
|
:class="{ disabled: loading }"
|
||||||
|
@click="!loading && handleConfirm('wxpay')"
|
||||||
|
>
|
||||||
|
<WechatOutlined class="payment-icon wxpay-icon" />
|
||||||
|
<span>{{ t('page.order.wxpay') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="method-item">
|
||||||
|
<PaypalButton
|
||||||
|
:amount="orderInfo.orderAmount"
|
||||||
|
:loading="loading"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pay-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h2 {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-info {
|
||||||
|
padding: 24px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-info h3,
|
||||||
|
.payment-methods h3 {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
width: 84px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
flex: 1;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value.highlight {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.methods-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-item:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
background: #f0f5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alipay-icon {
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wxpay-icon {
|
||||||
|
color: #07c160;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-icon {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-item.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-item.disabled:hover {
|
||||||
|
border-color: #e8e8e8;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-insufficient {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ff4d4f;
|
||||||
|
margin-top: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,14 +6,26 @@ import { useI18n } from "vue-i18n";
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { fetchRechargeHistory } from '@/service/api/auth';
|
import { fetchRechargeHistory } from '@/service/api/auth';
|
||||||
import { useWindowSize } from '@vueuse/core';
|
import { useWindowSize } from '@vueuse/core';
|
||||||
|
import { getPaymentConfig } from '@/service/api/payment';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
router.push('/billing/billservice');
|
router.push('/billing/billservice');
|
||||||
};
|
};
|
||||||
|
const tableLoading = ref(false);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const currencySymbol = ref('¥');
|
||||||
|
|
||||||
|
const fetchCurrencySymbol = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getPaymentConfig();
|
||||||
|
if (response && response.data) {
|
||||||
|
currencySymbol.value = response.data.currencySymbol || '¥';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch currency symbol:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
const rechargeData = ref<Api.Recharge.RechargeRecord[]>([]);
|
const rechargeData = ref<Api.Recharge.RechargeRecord[]>([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
|
|
||||||
@@ -34,7 +46,6 @@ const formatDateTime = (dateTimeString: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const {columns, data, loading, getData, mobilePagination, searchParams} = useTable({
|
const {columns, data, loading, getData, mobilePagination, searchParams} = useTable({
|
||||||
apiFn: fetchRechargeHistory,
|
apiFn: fetchRechargeHistory,
|
||||||
apiParams: {
|
apiParams: {
|
||||||
@@ -56,7 +67,7 @@ const { columns, data, loading, getData, mobilePagination, searchParams } = useT
|
|||||||
key: 'orderAmount',
|
key: 'orderAmount',
|
||||||
customRender: ({text}: { text: string }) => {
|
customRender: ({text}: { text: string }) => {
|
||||||
const formattedAmount = Number(text).toFixed(2);
|
const formattedAmount = Number(text).toFixed(2);
|
||||||
return `¥${formattedAmount}`;
|
return `${currencySymbol.value}${formattedAmount}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -86,15 +97,25 @@ const { columns, data, loading, getData, mobilePagination, searchParams } = useT
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
fetchCurrencySymbol();
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="text-lg font-bold mb-4 flex justify-between items-center">
|
<div class="text-lg font-bold mb-4 flex justify-between items-center">
|
||||||
<span>{{ t('page.Rechargehistory.recharge') }}</span>
|
<span>{{ t('page.Rechargehistory.recharge') }}</span>
|
||||||
<a-button @click="handleBack">
|
<a-button v-if="isMobile" @click="handleBack">
|
||||||
{{ t('page.login.common.back') }}
|
{{ t('page.login.common.back') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<!-- <a-button type="primary" :loading="tableLoading" @click="getPackageData">-->
|
||||||
|
<!-- <template #icon>-->
|
||||||
|
<!-- <span class="i-carbon:refresh"></span>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- {{ t('page.cdrlrecords.refresh') }}-->
|
||||||
|
<!-- </a-button>-->
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ATable
|
<ATable
|
||||||
@@ -132,4 +153,10 @@ const { columns, data, loading, getData, mobilePagination, searchParams } = useT
|
|||||||
.pl-4 {
|
.pl-4 {
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
|
/* 在PC端隐藏展开按钮图标 */
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
:deep(.ant-table-row-expand-icon) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -3,9 +3,11 @@
|
|||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { doGetOrderInfo } from '@/service/api/order';
|
import { doGetOrderInfo } from '@/service/api/order';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { routerPushByKey } = useRouterPush();
|
const { routerPushByKey } = useRouterPush();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const qrCodeImg = ref<string>(window.location.origin + route.query.url);
|
const qrCodeImg = ref<string>(window.location.origin + route.query.url);
|
||||||
|
|
||||||
@@ -34,15 +36,15 @@
|
|||||||
<div class="weChat__card--div">
|
<div class="weChat__card--div">
|
||||||
<a-card class="weChat__card" :bordered="false">
|
<a-card class="weChat__card" :bordered="false">
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="title__span">微信支付</span>
|
<span class="title__span">{{ t('page.order.wxpay') }}</span>
|
||||||
</template>
|
</template>
|
||||||
<div class="weChat__card__content">
|
<div class="weChat__card__content">
|
||||||
|
<div class="bottomContent">
|
||||||
|
<span class="weChat__card_span"> {{ t('page.order.wxpayScan') }}</span>
|
||||||
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<img :src="qrCodeImg" class="weChat__card_qrCodeImg" />
|
<img :src="qrCodeImg" class="weChat__card_qrCodeImg" />
|
||||||
</p>
|
</p>
|
||||||
<div class="bottomContent">
|
|
||||||
<span class="weChat__card_span">使用微信扫码支付</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
// 响应式布局
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
router.push('/endpoint/clientservice');
|
router.push('/endpoint/clientservice');
|
||||||
};
|
};
|
||||||
@@ -108,7 +110,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-space size="middle">
|
<a-space size="middle">
|
||||||
<a-button @click="handleBack">
|
<a-button v-if="isMobile" @click="handleBack">
|
||||||
{{ t('page.login.common.back') }}
|
{{ t('page.login.common.back') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" :loading="loading" @click="handleRefresh">
|
<a-button type="primary" :loading="loading" @click="handleRefresh">
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
// 响应式布局
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
router.push('/endpoint/clientservice');
|
router.push('/endpoint/clientservice');
|
||||||
};
|
};
|
||||||
@@ -134,7 +137,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-space size="middle">
|
<a-space size="middle">
|
||||||
<a-button @click="handleBack">
|
<a-button v-if="isMobile" @click="handleBack">
|
||||||
{{ t('page.login.common.back') }}
|
{{ t('page.login.common.back') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" :loading="loading" @click="handleRefresh">
|
<a-button type="primary" :loading="loading" @click="handleRefresh">
|
||||||
|
|||||||
@@ -6,9 +6,26 @@ import type { ECOption } from '@/hooks/common/echarts';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
import { clientAuth } from '@/service/ue/client';
|
import { clientAuth } from '@/service/ue/client';
|
||||||
|
import { getPaymentConfig } from '@/service/api/payment';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
// 添加货币符号的响应式引用
|
||||||
|
const currencySymbol = ref('¥');
|
||||||
|
|
||||||
|
// 获取货币符号
|
||||||
|
const fetchCurrencySymbol = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getPaymentConfig();
|
||||||
|
if (response && response.data) {
|
||||||
|
currencySymbol.value = response.data.currencySymbol || '¥';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch currency symbol:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'HeaderBanner'
|
name: 'HeaderBanner'
|
||||||
});
|
});
|
||||||
@@ -307,7 +324,7 @@ async function mockDataUpdate() {
|
|||||||
value: numBalance,
|
value: numBalance,
|
||||||
max: Math.max(numBalance, 100),
|
max: Math.max(numBalance, 100),
|
||||||
displayValue:formatBalance(response.balance),
|
displayValue:formatBalance(response.balance),
|
||||||
unit: t('page.headerbanner.money'),
|
unit: currencySymbol.value,
|
||||||
subTitle: t('page.headerbanner.deviceCount') + `: ${
|
subTitle: t('page.headerbanner.deviceCount') + `: ${
|
||||||
response.packageName ? (
|
response.packageName ? (
|
||||||
!response.clientNumEnable
|
!response.clientNumEnable
|
||||||
@@ -316,6 +333,10 @@ async function mockDataUpdate() {
|
|||||||
):'-'
|
):'-'
|
||||||
}`
|
}`
|
||||||
};
|
};
|
||||||
|
// 初始化流量变量,在外层定义
|
||||||
|
let totalTraffic = 0;
|
||||||
|
let usedTraffic = 0;
|
||||||
|
let remainingTraffic = 0;
|
||||||
|
|
||||||
// 更新流量数据
|
// 更新流量数据
|
||||||
if (!response.packageName) {
|
if (!response.packageName) {
|
||||||
@@ -334,9 +355,13 @@ async function mockDataUpdate() {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 有套餐时的正常显示逻辑
|
// 有套餐时的正常显示逻辑
|
||||||
const totalTraffic = response.trafficEnable ? (response.traffic || 0) : 0;
|
// const totalTraffic = response.trafficEnable ? (response.traffic || 0) : 0;
|
||||||
const usedTraffic = response.trafficEnable ? (response.trafficUsed || 0) : 0;
|
// const usedTraffic = response.trafficEnable ? (response.trafficUsed || 0) : 0;
|
||||||
const remainingTraffic = Math.max(0, totalTraffic - usedTraffic);
|
// const remainingTraffic = Math.max(0, totalTraffic - usedTraffic);
|
||||||
|
//内层调用,则不会存在控制台报错
|
||||||
|
totalTraffic = response.trafficEnable ? (response.traffic || 0) : 0;
|
||||||
|
usedTraffic = response.trafficEnable ? (response.trafficUsed || 0) : 0;
|
||||||
|
remainingTraffic = Math.max(0, totalTraffic - usedTraffic);
|
||||||
|
|
||||||
// 格式化流量显示
|
// 格式化流量显示
|
||||||
const formattedTotal = formatTraffic(totalTraffic);
|
const formattedTotal = formatTraffic(totalTraffic);
|
||||||
@@ -373,6 +398,7 @@ async function mockDataUpdate() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 更新速率限制显示
|
// 更新速率限制显示
|
||||||
if (!response.packageName) {
|
if (!response.packageName) {
|
||||||
speedLimits.value = {
|
speedLimits.value = {
|
||||||
@@ -422,6 +448,8 @@ let timer: ReturnType<typeof setInterval> | null = null;
|
|||||||
// 初始化
|
// 初始化
|
||||||
async function init() {
|
async function init() {
|
||||||
// 立即执行一次数据更新
|
// 立即执行一次数据更新
|
||||||
|
// 先获取货币符号
|
||||||
|
await fetchCurrencySymbol();
|
||||||
await mockDataUpdate();
|
await mockDataUpdate();
|
||||||
// 设置定期执行的定时器
|
// 设置定期执行的定时器
|
||||||
timer = setInterval(mockDataUpdate, 30000);
|
timer = setInterval(mockDataUpdate, 30000);
|
||||||
@@ -500,7 +528,7 @@ const getDeviceCount = (subTitle?: string, clientNumEnable?: boolean): string =>
|
|||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">{{ t('page.headerbanner.price') }}</span>
|
<span class="info-label">{{ t('page.headerbanner.price') }}</span>
|
||||||
<span class="info-value">{{ packageInfo.price === '-' ? '-' : '¥' + packageInfo.price }}</span>
|
<span class="info-value">{{ packageInfo.price === '-' ? '-' : currencySymbol + packageInfo.price }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">{{ t('page.headerbanner.monthflowr') }}</span>
|
<span class="info-label">{{ t('page.headerbanner.monthflowr') }}</span>
|
||||||
@@ -531,7 +559,7 @@ const getDeviceCount = (subTitle?: string, clientNumEnable?: boolean): string =>
|
|||||||
<div class="info-group">
|
<div class="info-group">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">{{ t('page.headerbanner.Remainingcredit') }}</span>
|
<span class="info-label">{{ t('page.headerbanner.Remainingcredit') }}</span>
|
||||||
<span class="info-value">{{ baseData[0].displayValue === '-' ? '-' : '¥' + baseData[0].displayValue }}</span>
|
<span class="info-value">{{ baseData[0].displayValue === '-' ? '-' : currencySymbol + baseData[0].displayValue }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">{{ t('page.headerbanner.client') }}</span>
|
<span class="info-label">{{ t('page.headerbanner.client') }}</span>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useRouterPush } from '@/hooks/common/router';
|
|||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import OrderConfirmModal from '@/components/order-confirm/orderConfirmModal.vue';
|
import OrderConfirmModal from '@/components/order-confirm/orderConfirmModal.vue';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
import { getPaymentConfig } from '@/service/api/payment';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'BalanceRecharge'
|
name: 'BalanceRecharge'
|
||||||
@@ -16,7 +17,18 @@ defineOptions({
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { routerPushByKey } = useRouterPush();
|
const { routerPushByKey } = useRouterPush();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const currencySymbol = ref('¥');
|
||||||
|
|
||||||
|
const fetchCurrencySymbol = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getPaymentConfig();
|
||||||
|
if (response && response.data) {
|
||||||
|
currencySymbol.value = response.data.currencySymbol || '¥';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch currency symbol:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
interface RechargeOption {
|
interface RechargeOption {
|
||||||
amount: number;
|
amount: number;
|
||||||
displayAmount: string;
|
displayAmount: string;
|
||||||
@@ -27,13 +39,13 @@ const customAmount = ref<string | number | undefined>(undefined);
|
|||||||
const selectedAmount = ref<number | null>(null);
|
const selectedAmount = ref<number | null>(null);
|
||||||
const isCustomMode = ref<boolean>(false);
|
const isCustomMode = ref<boolean>(false);
|
||||||
|
|
||||||
const rechargeOptions: Ref<RechargeOption[]> = ref([
|
const rechargeOptions = computed(() => [
|
||||||
{ amount: 10, displayAmount: `${t('page.carddata.money')}10`, price: 10.00 },
|
{ amount: 10, displayAmount: `${currencySymbol.value}10`, price: 10.00 },
|
||||||
{ amount: 20, displayAmount: `${t('page.carddata.money')}20`, price: 20.00 },
|
{ amount: 20, displayAmount: `${currencySymbol.value}20`, price: 20.00 },
|
||||||
{ amount: 30, displayAmount: `${t('page.carddata.money')}30`, price: 30.00 },
|
{ amount: 30, displayAmount: `${currencySymbol.value}30`, price: 30.00 },
|
||||||
{ amount: 50, displayAmount: `${t('page.carddata.money')}50`, price: 50.00 },
|
{ amount: 50, displayAmount: `${currencySymbol.value}50`, price: 50.00 },
|
||||||
{ amount: 100, displayAmount: `${t('page.carddata.money')}100`, price: 100.00 },
|
{ amount: 100, displayAmount: `${currencySymbol.value}100`, price: 100.00 },
|
||||||
{ amount: 200, displayAmount: `${t('page.carddata.money')}200`, price: 200.00 },
|
{ amount: 200, displayAmount: `${currencySymbol.value}200`, price: 200.00 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const paymentAmount = computed(() => {
|
const paymentAmount = computed(() => {
|
||||||
@@ -76,10 +88,12 @@ const handleBlur = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
fetchCurrencySymbol();
|
||||||
document.addEventListener('click', handleClickOutside);
|
document.addEventListener('click', handleClickOutside);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
fetchCurrencySymbol();
|
||||||
document.removeEventListener('click', handleClickOutside);
|
document.removeEventListener('click', handleClickOutside);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -202,7 +216,7 @@ const handlePaymentConfirm = async (paymentMethod: 'alipay' | 'wxpay') => {
|
|||||||
:disabled="!paymentAmount || paymentAmount <= 0"
|
:disabled="!paymentAmount || paymentAmount <= 0"
|
||||||
@click="handleRecharge"
|
@click="handleRecharge"
|
||||||
>
|
>
|
||||||
<span style="color: var(--text-color, var(--ant-text-color))"> ¥{{ paymentAmount.toFixed(2) }} {{ t('page.carddata.pay') }} </span>
|
<span style="color: var(--text-color, var(--ant-text-color))"> {{ currencySymbol }}{{ paymentAmount.toFixed(2) }} {{ t('page.carddata.pay') }} </span>
|
||||||
</AButton>
|
</AButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,23 @@ import { aliPayPcPay,aliPayWapPay, wxPayScanCode, payBalance } from '@/service/a
|
|||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useAuthStore} from '@/store/modules/auth';
|
import { useAuthStore} from '@/store/modules/auth';
|
||||||
|
import { getPaymentConfig } from '@/service/api/payment';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'PackageSubscription'
|
name: 'PackageSubscription'
|
||||||
});
|
});
|
||||||
|
const currencySymbol = ref('¥');
|
||||||
|
|
||||||
|
const fetchCurrencySymbol = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getPaymentConfig();
|
||||||
|
if (response && response.data) {
|
||||||
|
currencySymbol.value = response.data.currencySymbol || '¥';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch currency symbol:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
interface RateLimit {
|
interface RateLimit {
|
||||||
@@ -343,6 +356,7 @@ const isPackageActive = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
await fetchCurrencySymbol();
|
||||||
fetchDashboardData();
|
fetchDashboardData();
|
||||||
await fetchPackages();
|
await fetchPackages();
|
||||||
await fetchUserBalance();
|
await fetchUserBalance();
|
||||||
@@ -354,7 +368,7 @@ onMounted(async () => {
|
|||||||
<!-- 顶部价格展示 -->
|
<!-- 顶部价格展示 -->
|
||||||
<div class="price-header">
|
<div class="price-header">
|
||||||
<div class="price">
|
<div class="price">
|
||||||
<span class="currency">¥</span>
|
<span class="currency">{{ currencySymbol }}</span>
|
||||||
<span class="amount">{{ selectedPackage.price }}</span>
|
<span class="amount">{{ selectedPackage.price }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle">{{ selectedPackage.packageName }}</div>
|
<div class="subtitle">{{ selectedPackage.packageName }}</div>
|
||||||
@@ -380,7 +394,7 @@ onMounted(async () => {
|
|||||||
{{ t('page.setmeal.highlyrecommended') }}
|
{{ t('page.setmeal.highlyrecommended') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="package-name">{{ option.packageName }}</div>
|
<div class="package-name">{{ option.packageName }}</div>
|
||||||
<div class="price">¥{{ option.price }}</div>
|
<div class="price">{{ currencySymbol }}{{ option.price }}</div>
|
||||||
<div class="traffic">{{ option.trafficEnable ? option.trafficDisplay : t('page.setmeal.unlimit') }}</div>
|
<div class="traffic">{{ option.trafficEnable ? option.trafficDisplay : t('page.setmeal.unlimit') }}</div>
|
||||||
<div class="device-count">
|
<div class="device-count">
|
||||||
{{ option.clientNumEnable ? `${option.clientNum} ${t('page.setmeal.device')}` : t('page.setmeal.unlimit') }}
|
{{ option.clientNumEnable ? `${option.clientNum} ${t('page.setmeal.device')}` : t('page.setmeal.unlimit') }}
|
||||||
|
|||||||
Reference in new issue
Block a user