881 lines
20 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="coin-seller gradient-background-circles">
<!-- 使用 GeneralHeader 替换 MobileHeader -->
<GeneralHeader
:isHomePage="true"
:showLanguageList="true"
:title="t('recharge_agency')"
color="black"
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); z-index: 999"
/>
<!-- 权限校验加载状态 -->
<div v-if="checkingAccess" class="loading-container">
<div class="loading-spinner"></div>
<p>{{ t('checking_permissions') }}...</p>
</div>
<!-- 主要内容 -->
<div v-else-if="dealerAccess" class="content">
<!-- 身份 -->
<div style="display: flex; justify-content: space-between">
<div
style="
margin: 0 1%;
margin-bottom: 12px;
font-weight: 600;
display: flex;
align-items: center;
font-size: 1.3em;
"
>
{{ t('my_account') }}
<img
src="../../assets/icon/identity/coinSeller.png"
alt=""
height="20px"
style="display: block; margin-left: 10px"
/>
</div>
<img
@click="goToRecords"
src="../../assets/icon/records.png"
height="20px"
alt=""
style="display: block; object-fit: cover"
/>
</div>
<!-- 用户信息卡片 -->
<div class="user-card">
<!-- 个人信息 -->
<div class="user-info">
<div class="avatar">
<img
:src="userInfo.userAvatar || ''"
alt=""
style="
display: block;
object-fit: cover;
width: 100%;
aspect-ratio: 1/1;
border-radius: 50%;
"
@error="handleAvatarImageError"
/>
</div>
<div class="user-details">
<h3>{{ userInfo.userNickname || userInfo.name }}</h3>
<p>
<span style="display: flex; align-items: center">
<span>ID</span>
<span style="margin: 0 2px">:</span>
<span>{{ userInfo.id }}</span>
</span>
</p>
</div>
</div>
<!-- 我的金币 -->
<div>
<div style="font-weight: 600">{{ t('my_coins') }}</div>
<div style="margin-top: 12px; width: 100%; display: flex; align-items: center">
<img src="../../assets/icon/coin.png" alt="" height="20px" style="display: block" />
<div style="color: #ffb627; font-weight: 590; margin-left: 5px">
{{ coinsAmount }}
</div>
</div>
</div>
</div>
<!-- 设置最小接收金额 -->
<!-- <div
style="
background-color: white;
padding: 16px;
border-radius: 12px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 10px;
"
>
<div style="display: flex; justify-content: space-between; align-items: center; gap: 4px">
<div style="font-weight: 600">Minimum amount of transfers accepted:</div>
<img
src="../../assets/icon/help.png"
height="20px"
alt=""
style="display: block; object-fit: cover"
@click="showHelpTip = true"
/>
</div>
<div style="display: flex; justify-content: space-around; align-items: center; gap: 10px">
<div
v-for="amountItem in AmountList"
style="
flex: 1;
min-width: 0;
align-self: stretch;
border-radius: 12px;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
"
:style="amountStyle(amountItem)"
@click="selectAmount(amountItem)"
>
<div style="font-weight: 700">{{ amountItem.type }}</div>
</div>
</div>
</div> -->
<!-- 用户搜索部分 -->
<div class="user-section">
<div style="margin-bottom: 12px; font-weight: 600; color: #333">
{{ t('user') }}
</div>
<!-- 有用户 -->
<div v-if="selectedUser.id" class="selected-user">
<div class="user-info">
<div class="user-avatar">
<img
:src="selectedUser.userAvatar || ''"
alt=""
style="
display: block;
object-fit: cover;
width: 100%;
aspect-ratio: 1/1;
border-radius: 50%;
"
@error="handleAvatarImageError"
/>
</div>
<div class="user-details">
<div class="user-name">
{{ selectedUser.name }}
</div>
<div class="user-id">
<span style="display: flex; align-items: center">
<span>ID</span>
<span style="margin: 0 2px">:</span>
<span>{{ selectedUser.account }}</span>
</span>
</div>
</div>
</div>
<button class="change-btn" @click="searchUser">{{ t('change') }}</button>
</div>
<!-- 无用户 -->
<div v-else style="display: flex; justify-content: center; align-items: center">
<div class="search-placeholder" @click="searchUser">
<span>{{ t('search') }}</span>
</div>
</div>
<!-- 充值部分 -->
<div v-if="selectedUser.id" style="margin-top: 12px">
<div style="margin-bottom: 12px; font-weight: 600; color: #333">
{{ t('recharge') }}
</div>
<!-- 输入金额 -->
<div
style="
margin-bottom: 16px;
background-color: white;
padding: 10px 16px;
border-radius: 12px;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
"
>
<input
v-model="rechargeAmount"
type="number"
min="1"
:placeholder="t('enter_recharge_amount')"
class="amount-input"
/>
</div>
<!-- 按钮 -->
<div style="display: flex; justify-content: center">
<button
class="recharge-btn"
:disabled="!canRecharge || isRecharging"
@click="rechargeNow"
>
{{ isRecharging ? t('loading') : t('recharge_now') }}
</button>
</div>
</div>
</div>
</div>
<!-- 遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<div
style="
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
"
>
<!-- 帮助提示弹窗 -->
<div
v-if="showHelpTip"
style="
width: 80%;
border-radius: 12px;
background: #fff;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
"
@click.stop
>
<div style="position: relative; width: 100%">
<div style="text-align: center; color: rgba(0, 0, 0, 0.8); font-weight: 800">
{{ t('tips') }}
</div>
<!-- 关闭按钮 -->
<div style="position: absolute; top: 0; right: 0" @click="closedPopup">X</div>
</div>
<div style="color: rgba(0, 0, 0, 0.5); font-weight: 600; text-align: center">
Recharge agents can choose the minimumamount they can accept from user transfers,which
can be modifed on the frst of eachmonth. (For example, if a recharge agentchanges to
only accept transfers of $10 or more,users with single transfers below $10 will not
beable to transfer funds to that recharge agent.)
</div>
</div>
<!-- 设置转账确认弹窗 -->
<div
v-if="showTransfersTip"
style="
width: 80%;
border-radius: 12px;
background: #fff;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
"
@click.stop
>
<div style="color: rgba(0, 0, 0, 0.8); font-weight: 800">{{ t('tips') }}</div>
<div style="color: rgba(0, 0, 0, 0.5); font-weight: 600; text-align: center">
Are you sure you want to select this transfer amount limit?
</div>
<div
style="width: 100%; display: flex; justify-content: space-around; align-items: center"
>
<div
style="
padding: 12px 20px;
border-radius: 12px;
border: 1px solid #e6e6e6;
color: rgba(0, 0, 0, 0.4);
font-weight: 800;
"
@click="Cancel"
>
{{ t('cancel') }}
</div>
<div
style="
padding: 12px 20px;
border-radius: 12px;
background: linear-gradient(
152deg,
rgba(198, 112, 255, 0.6) 7.01%,
rgba(119, 38, 255, 0.6) 92.99%
);
color: rgba(255, 255, 255, 0.8);
font-weight: 800;
"
@click="Confirm"
>
{{ t('confirm') }}
</div>
</div>
</div>
</div>
</maskLayer>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { setDocumentDirection } from '@/locales/i18n'
import { getSelectedUser } from '@/utils/coinSellerStore.js'
import { showError, showSuccess } from '@/utils/toast.js'
import { usePageInitializationWithConfig } from '@/utils/pageConfig.js'
import { handleAvatarImageError } from '@/utils/imageHandler.js'
import { checkFreightDealer, getFreightBalance, freightRecharge } from '@/api/wallet.js'
import GeneralHeader from '@/components/GeneralHeader.vue'
import maskLayer from '@/components/MaskLayer.vue'
const { t, locale } = useI18n()
const router = useRouter()
// 监听语言变化并设置文档方向
locale.value && setDocumentDirection(locale.value)
// 权限校验状态
const dealerAccess = ref(false)
const checkingAccess = ref(true)
// 金币数量
const coinsAmount = ref('0')
const loadingBalance = ref(false)
const AmountList = ref([
{
id: 1,
type: '>$0',
selectedStatus: true,
},
{
id: 2,
type: '>=$10',
selectedStatus: false,
},
{
id: 3,
type: '>=$20',
selectedStatus: false,
},
{
id: 4,
type: '>=$50',
selectedStatus: false,
},
])
const selectedAmount = ref({})
// 是否为每月一号
const isFirstDay = ref(false)
// 更新是否为每月一号
const updateIsFirstDay = () => {
const today = new Date()
isFirstDay.value = today.getDate() === 1
}
// 选项样式
const amountStyle = (amountItem) => {
if (amountItem.selectedStatus) {
return 'border: 1px solid #7726ff;background: #fff;'
} else if (isFirstDay.value) {
return 'border: 1px solid transparent;background: transparent;'
} else {
return 'border: 1px solid transparent;background: rgba(0, 0, 0, 0.2);'
}
}
// 弹窗提示
const showHelpTip = ref(false) //帮助提示弹窗
const showTransfersTip = ref(false) //设置转账确认弹窗
const maskLayerShow = computed(() => {
return showHelpTip.value || showTransfersTip.value
})
// 选中的用户
const selectedUser = ref({
id: '',
name: '',
})
// 关闭弹窗
const closedPopup = () => {
showHelpTip.value = false
showTransfersTip.value = false
}
// 选择限制转账金额
const selectAmount = (amountItem) => {
// 非每月一号
if (!isFirstDay.value || amountItem.selectedStatus) return
console.log('选择限制转账金额', amountItem)
selectedAmount.value = amountItem //设置选中的金额
showTransfersTip.value = true //展示确认弹窗
}
// 取消选择
const Cancel = () => {
console.log('取消选择')
selectedAmount.value = {} //清空选中的金额
closedPopup()
}
// 确认选择
const Confirm = () => {
console.log('确认选择')
// 发送接口
try {
} catch (error) {}
closedPopup()
}
// 校验销售商权限
const checkDealerAccess = async () => {
try {
checkingAccess.value = true
const response = await checkFreightDealer()
if (response && response.status !== undefined) {
dealerAccess.value = response.body === true
if (!dealerAccess.value) {
// 没有权限,跳转回上一页或首页
showError(t('not_authorized'))
router.go(-1)
return
}
}
} catch (error) {
console.error('Permission verification failed:', error)
dealerAccess.value = false
router.go(-1)
} finally {
checkingAccess.value = false
}
}
// 获取金币余额
const fetchFreightBalance = async () => {
try {
loadingBalance.value = true
const response = await getFreightBalance()
if (response && response.status && response.body !== undefined) {
coinsAmount.value = response.body.toString()
}
} catch (error) {
console.error('获取金币余额失败:', error)
} finally {
loadingBalance.value = false
}
}
// 初始化用户信息
const initializeUser = () => {
const savedUser = getSelectedUser()
if (savedUser && savedUser.id) {
selectedUser.value = { ...savedUser }
}
}
// 充值金额
const rechargeAmount = ref('')
const isRecharging = ref(false)
// 是否可以充值
const canRecharge = computed(() => {
return (
selectedUser.value.id &&
rechargeAmount.value &&
parseFloat(rechargeAmount.value) > 0 &&
!isRecharging.value
)
})
// 跳转到设置
const goToRecords = () => {
router.push('/seller-records')
}
// 搜索用户
const searchUser = () => {
router.push('/coin-seller-search')
}
// 立即充值
const rechargeNow = async () => {
if (!canRecharge.value || isRecharging.value) return
const amount = parseFloat(rechargeAmount.value)
if (amount <= 0) {
showError(t('please_enter_valid_amount'))
return
}
// 需要获取接收用户的ID从用户搜索结果中获取
const acceptUserId = selectedUser.value.id
if (!acceptUserId) {
showError(t('user_not_found_or_search_failed'))
return
}
try {
isRecharging.value = true
const rechargeData = {
acceptUserId: acceptUserId,
quantity: amount,
}
const response = await freightRecharge(rechargeData)
if (response && response.status) {
// 充值成功,更新余额
if (response.body !== undefined) {
coinsAmount.value = response.body.toString()
}
showSuccess(
t('transfer_success', {
price: amount,
name: selectedUser.value.name,
})
)
// 重置表单
rechargeAmount.value = ''
} else {
showError(t('recharge_failed'))
}
} catch (error) {
console.error('充值失败:', error)
showError(t('recharge_error_occurred'))
} finally {
isRecharging.value = false
}
}
const { userInfo } = usePageInitializationWithConfig('COIN_SELLER', {
onDataLoaded: (data) => {
checkDealerAccess()
fetchFreightBalance()
initializeUser()
},
})
// 定时检查(可选)
let dayCheckInterval = null
onMounted(() => {
// 初始化
updateIsFirstDay()
// 每小时检查一次是否进入新一天
dayCheckInterval = setInterval(updateIsFirstDay, 60 * 60 * 1000)
})
onUnmounted(() => {
if (dayCheckInterval) {
clearInterval(dayCheckInterval)
}
})
</script>
<style scoped>
* {
color: rgba(0, 0, 0, 0.8);
font-family: 'SF Pro Text';
}
.coin-seller {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
/* 加载状态 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
color: #666;
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid #f3f3f3;
border-top: 3px solid #f59e0b;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 12px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.content {
padding: 16px;
position: relative;
z-index: 2;
}
/* 用户信息卡片 */
.user-card {
background-color: white;
padding: 16px;
border-radius: 12px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.user-info {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
margin-bottom: 12px;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 25px;
margin-right: 12px;
}
.user-details {
min-width: 0;
flex: 1;
}
.user-details h3 {
margin-bottom: 4px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-details p {
color: rgba(0, 0, 0, 0.4);
font-size: 0.9em;
font-weight: 500;
}
.settings-btn {
width: 32px;
height: 32px;
border: none;
background: none;
cursor: pointer;
}
.account-tags {
display: flex;
gap: 8px;
}
.tag {
padding: 4px 12px;
border-radius: 16px;
font-size: 0.8em;
font-weight: 600;
}
.seller-tag {
background-color: #fef3c7;
color: #d97706;
}
/* 用户搜索部分 */
.user-section {
background-color: white;
padding: 16px;
border-radius: 12px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.selected-user {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
border-radius: 12px;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
}
.selected-user .user-info {
display: flex;
align-items: center;
margin-bottom: 0;
}
.user-avatar {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.selected-user .user-details {
min-width: 0;
flex: 1;
}
.user-name {
font-size: 0.9em;
font-weight: 700;
margin-bottom: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-id {
font-size: 0.8em;
font-weight: 500;
color: rgba(0, 0, 0, 0.4);
}
.change-btn {
border-radius: 12px;
background: linear-gradient(
152deg,
rgba(198, 112, 255, 0.6) 7.01%,
rgba(119, 38, 255, 0.6) 92.99%
);
color: white;
border: none;
padding: 6px 12px;
font-size: 12px;
font-weight: 700;
cursor: pointer;
}
.change-btn:active {
background-color: #a855f7;
}
.search-placeholder {
padding: 12px 20px;
border: 1px solid #ddd;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.2s;
}
.search-placeholder:hover {
border-color: #f59e0b;
background-color: #fef5e7;
}
.search-placeholder span {
color: #666;
font-size: 0.9em;
font-weight: 500;
}
.amount-input {
width: 100%;
border: none;
font-size: 0.9em;
box-sizing: border-box;
}
.amount-input:focus {
outline: none;
}
.amount-input::placeholder {
font-size: 0.7em;
color: rgba(0, 0, 0, 0.4);
font-weight: 500;
}
.currency-suffix {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 0.8em;
color: #666;
}
.recharge-btn {
width: 80%;
padding: 12px;
border-radius: 32px;
background: linear-gradient(135deg, #bb92ff 2.82%, #8b45ff 99.15%);
color: white;
border: none;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.recharge-btn:not(:disabled):active {
background: linear-gradient(135deg, #b080ff 2.82%, #6302ff 99.15%);
}
.recharge-btn:disabled {
background: #d1d5db;
cursor: not-allowed;
}
@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'] .records-icon {
transform: scaleX(-1);
}
[dir='rtl'] .avatar {
margin-right: 0;
margin-left: 12px;
}
[dir='rtl'] .user-avatar {
margin-right: 0;
margin-left: 12px;
}
[dir='rtl'] .change-btn {
margin-left: 0;
margin-right: 12px;
}
</style>