aslan-h5/src/views/Wallet/Transfer/TransferView.vue

670 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="transfer gradient-background-circles">
<!-- 使用 GeneralHeader 替换 MobileHeader -->
<GeneralHeader
:title="t('transfer')"
:showBack="true"
:showLanguageList="true"
color="black"
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 999"
/>
<div class="content">
<!-- 信息部分 -->
<div>
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
"
>
<div style="font-weight: 600">{{ t('information') }}</div>
<div style="display: flex; align-items: center" @click="showDetails">
<div style="color: rgba(0, 0, 0, 0.4)">{{ t('details') }}</div>
<img
src="../../../assets/icon/arrow.png"
alt=""
width="16px"
style="margin-left: 4px"
class="rtl-flip"
/>
</div>
</div>
<div
style="
background-color: white;
padding: 16px;
border-radius: 12px;
margin-bottom: 12px;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
"
>
<span style="font-weight: 510; font-size: 1em">{{ t('available_salaries') }} </span>
<span v-if="loading" class="loading-text">{{ t('loading') }}...</span>
<span v-else style="font-weight: 700; color: #333; font-size: 1.1em">{{
availableSalary
}}</span>
</div>
</div>
<!-- 转账对象 -->
<div>
<div style="font-weight: 600; margin-bottom: 10px">{{ t('transfer_to_others') }}</div>
<div
style="
background-color: white;
padding: 16px;
border-radius: 12px;
margin-bottom: 12px;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
"
>
<!-- 用户信息 -->
<div v-if="selectedPayee.id" style="width: 100%">
<!-- 头像信息 -->
<div style="display: flex; align-items: center; margin-bottom: 12px">
<!-- 头像 -->
<div
style="
position: relative;
width: 50px;
height: 50px;
border-radius: 25px;
background-color: #8b5cf6;
color: white;
margin-right: 12px;
overflow: hidden;
font-size: 20px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
"
>
<img
v-if="selectedPayee.avatar"
:src="selectedPayee.avatar"
:alt="selectedPayee.name"
style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%"
/>
<span v-else>{{ selectedPayee.name.charAt(0) }}</span>
</div>
<!-- 个人信息 -->
<div style="flex: 1">
<div style="margin: 0 0 4px 0; font-size: 16px; font-weight: 600; color: #333">
{{ selectedPayee.name }}
</div>
<div style="margin: 0; display: flex; align-items: center">
<p style="font-size: 14px; color: #666">ID</p>
<p style="font-size: 14px; color: #666">:</p>
<p style="font-size: 14px; color: #666">
{{ selectedPayee.account || selectedPayee.id }}
</p>
</div>
</div>
<!-- 换人按钮 -->
<button
style="
border-radius: 12px;
border: 1px solid #e6e6e6;
color: rgba(0, 0, 0, 0.4);
font-weight: 500;
padding: 2px 8px;
background: none;
"
@click="searchPayee"
>
{{ t('change') }}
</button>
</div>
<!-- 身份 -->
<div style="margin-left: 62px; height: 20px; display: flex; gap: 5px">
<img
src="../../../assets/icon/identity/coinSeller.png"
alt=""
height="100%"
style="display: block"
/>
</div>
</div>
<!-- 空状态 - 默认显示 -->
<div v-else style="display: flex; justify-content: center; align-items: center">
<div
style="
font-weight: 590;
color: rgba(0, 0, 0, 0.4);
padding: 16px;
border-radius: 12px;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
"
@click="searchPayee"
>
{{ t('search') }}
</div>
</div>
</div>
</div>
<!-- 转账部分 -->
<div>
<div
style="
background-color: white;
padding: 16px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
"
>
<!-- 转账金额选择 -->
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px">
<div
v-for="coin in coinOptions"
:key="coin.id"
@click="selectCoin(coin)"
:class="['coin-item', { selected: selectedCoin === coin.id }]"
>
<img src="../../../assets/icon/dollar.png" alt="" style="width: 40%" />
<div style="font-weight: 500; color: #131111">${{ coin.price }}</div>
</div>
</div>
</div>
<!-- 转账按钮 -->
<div style="display: flex; justify-content: center; margin-top: 10px">
<button
style="border-radius: 32px"
class="transfer-btn"
@click="transfer"
:disabled="!selectedCoin || !selectedPayee.id"
>
{{ t('transfer') }}
</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import GeneralHeader from '@/components/GeneralHeader.vue'
import { getBankBalance, userBankTransfer } from '@/api/wallet.js'
import { getSelectedPayee, clearSelectedPayee } from '@/utils/payeeStore.js'
import { showError, showSuccess } from '@/utils/toast.js'
import { setDocumentDirection } from '@/locales/i18n'
const { t, locale } = useI18n()
const router = useRouter()
// 监听语言变化并设置文档方向
locale.value && setDocumentDirection(locale.value)
const availableSalary = ref('0')
const selectedCoin = ref(null)
const loading = ref(false)
// 选中的收款人
const selectedPayee = reactive({
id: null,
account: '',
name: '',
avatar: '',
type: '',
isHost: false,
isAgency: false,
hasSalary: false,
})
// 初始化收款人信息
const initializePayee = () => {
const savedPayee = getSelectedPayee()
if (savedPayee) {
Object.assign(selectedPayee, savedPayee)
}
}
// 转账金币选项
// 对其他用户
const coinOptions = reactive([
{ id: 1, amount: 10500, price: 1 },
{ id: 2, amount: 52500, price: 5 },
{ id: 3, amount: 105000, price: 10 },
{ id: 4, amount: 525000, price: 50 },
{ id: 5, amount: 1050000, price: 100 },
{ id: 6, amount: 2100000, price: 200 },
])
// 对金币代理
const coinOptionsToSeller = reactive([
{ id: 1, amount: 11500, price: 1 },
{ id: 2, amount: 57500, price: 5 },
{ id: 3, amount: 115000, price: 10 },
{ id: 4, amount: 575000, price: 50 },
{ id: 5, amount: 1150000, price: 100 },
{ id: 6, amount: 2300000, price: 200 },
])
// 获取银行余额
const fetchBankBalance = async () => {
loading.value = true
try {
const response = await getBankBalance()
if (response.status && typeof response.body === 'number') {
// 格式化余额显示保留2位小数
availableSalary.value = response.body.toFixed(2)
} else {
availableSalary.value = '0.00'
}
} catch (error) {
console.error('Failed to fetch bank balance:', error)
availableSalary.value = '0.00'
// 可以显示错误提示
showError(t('failed_to_load_balance'))
} finally {
loading.value = false
}
}
// 方法
const showDetails = () => {
router.push('/information-details')
}
const searchPayee = () => {
router.push('/search-payee')
}
// 清除当前选中的收款人
const clearPayee = () => {
Object.assign(selectedPayee, {
id: null,
account: '',
name: '',
avatar: '',
type: '',
isHost: false,
isAgency: false,
hasSalary: false,
})
clearSelectedPayee()
}
const selectCoin = (coin) => {
// 单选逻辑:如果已经选中相同的金币,则取消选择;否则选中新的金币
if (selectedCoin.value === coin.id) {
selectedCoin.value = null
} else {
selectedCoin.value = coin.id
}
}
const transfer = async () => {
if (!selectedCoin.value) {
showError(t('please_select_amount'))
return
}
if (!selectedPayee.id) {
showError(t('please_select_payee'))
return
}
const selectedCoinData = coinOptions.find((coin) => coin.id === selectedCoin.value)
// 获取支付密码
// const password = prompt(t('enter_payment_password'))
// if (!password) {
// showError(t('payment_password_required'))
// return
// }
// 构造请求参数
const transferData = {
amount: selectedCoinData.price,
acceptUserId: selectedPayee.id,
acceptMethod: 'BANK_TRANSFER',
// password: password
}
try {
// 调用转账接口
const response = await userBankTransfer(transferData)
if (response.status) {
showSuccess(
t('transfer_success', {
price: selectedCoinData.price,
name: selectedPayee.name,
})
)
} else {
showError(t('transfer_failed', { price: selectedCoinData.price }))
}
// 重置选择
selectedCoin.value = null
// 刷新银行余额
await fetchBankBalance()
} catch (error) {
// 错误处理
console.error('Transfer failed:', error)
if (error.errorCode === 5000) {
showError(t('insufficient_balance'))
} else if (error.errorCode === 5010 || error.errorCode === 5009) {
showError(t('payment_password_error'))
} else {
showError(t('transfer_error'))
}
}
}
// 页面加载时获取银行余额和初始化收款人
onMounted(() => {
fetchBankBalance()
initializePayee()
})
onUnmounted(() => {
clearPayee() //清空收款人信息
})
</script>
<style scoped>
* {
color: rgba(0, 0, 0, 0.8);
font-family: 'SF Pro Text';
}
.transfer {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.content {
padding: 16px;
position: relative;
z-index: 2;
}
/* 信息部分 */
.info-section {
background-color: white;
padding: 16px;
border-radius: 12px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.info-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.info-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.details-btn {
background: none;
border: none;
color: #666;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
}
.available-salary {
font-size: 14px;
color: #666;
}
.available-salary .amount {
font-weight: 600;
color: #333;
}
.available-salary .loading-text {
font-weight: 500;
color: #8b5cf6;
font-style: italic;
}
/* 收款人信息 */
.payee-info {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
border: 1px solid #e0e0e0;
border-radius: 12px;
margin-bottom: 20px;
cursor: pointer;
transition: background-color 0.2s;
}
.payee-info:active {
background-color: #f9f9f9;
}
.payee-details {
display: flex;
align-items: center;
flex: 1;
}
.payee-avatar {
width: 40px;
height: 40px;
border-radius: 20px;
background-color: #8b5cf6;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
font-weight: 600;
margin-right: 12px;
overflow: hidden;
}
.payee-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.payee-text h4 {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 600;
color: #333;
}
.payee-text p {
margin: 0 0 8px 0;
font-size: 12px;
color: #666;
}
.payee-tags {
display: flex;
gap: 4px;
}
.tag {
padding: 2px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: 600;
}
.host-tag {
background-color: #e0e7ff;
color: #5b21b6;
}
.agency-tag {
background-color: #dbeafe;
color: #1e40af;
}
.salary-tag {
background-color: #fef3c7;
color: #d97706;
}
.change-btn {
background-color: #8b5cf6;
color: white;
border: none;
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
}
.change-btn:active {
background-color: #7c3aed;
}
.search-placeholder {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
color: #666;
}
.search-icon {
font-size: 24px;
}
.search-placeholder span {
font-size: 14px;
font-weight: 500;
}
/* 金币网格 */
.coin-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 2px solid #e5e7eb;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
background-color: white;
aspect-ratio: 1/1;
}
.coin-item.selected {
border-color: #8b5cf6;
background-color: #f3f4f6;
}
.coin-item:active {
transform: scale(0.98);
}
.coin-icon {
font-size: 24px;
margin-bottom: 8px;
}
.coin-amount {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.coin-price {
font-size: 12px;
color: #666;
}
/* 转账按钮 */
.transfer-btn {
width: 70%;
padding: 12px;
background: linear-gradient(135deg, #bb92ff 2.82%, #8b45ff 99.15%);
color: white;
border: none;
border-radius: 12px;
font-weight: 600;
cursor: pointer;
}
.transfer-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.transfer-btn:not(:disabled):active {
background: linear-gradient(135deg, #bb92ff 2.82%, #8b45ff 99.15%);
}
.rtl-flip {
width: 16px;
height: 16px;
}
[dir='rtl'] .rtl-flip {
transform: scaleX(-1);
}
@media screen and (max-width: 360px) {
* {
font-size: 10px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
}
}
@media screen and (min-width: 768px) {
* {
font-size: 24px;
}
}
/* RTL支持 */
[dir='rtl'] .payee-avatar {
margin-right: 0;
margin-left: 12px;
}
[dir='rtl'] .change-btn {
margin-left: 0;
margin-right: 12px;
}
</style>