Files
chuanqi-qycq-web/module/web/src/views/withdraw.vue
艾贤凌 6d4a72161f inint
2026-03-16 12:05:55 +08:00

401 lines
10 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.
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import request from '@/utils/request'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter()
// 账号信息(从 JWT 解析)
const currentAccount = ref('')
const currentServerId = ref(1)
// 表单
const serverId = ref('')
const roleId = ref('')
const roleName = ref('')
const payType = ref(0) // 0=支付宝 1=微信
const payAccount = ref('')
const amount = ref('')
// 区服列表
const servers = ref([])
// 游戏配置
const gameConfig = ref({})
const submitting = ref(false)
// 计算兑换金额
const money = computed(() => {
const ratio = gameConfig.value?.withdrawRatio || 10000
const a = parseInt(amount.value)
if (!a || a < ratio) return 0
return Math.floor(a / ratio)
})
const currencyName = computed(() => gameConfig.value?.currencyName || '货币')
/**
* 解析 JWT payload
*/
function parseJwt(token) {
try {
const base64Url = token.split('.')[1]
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
return JSON.parse(decodeURIComponent(escape(atob(base64))))
} catch {
return null
}
}
async function loadConfig() {
try {
const res = await request.get('/api/config')
if (res?.data) {
gameConfig.value = {
...gameConfig.value,
gameName: res.data.gameName,
withdrawRatio: res.data.withdrawRatio || 10000,
withdrawMinOnce: res.data.withdrawMinOnce || 20,
currencyName: res.data.currencyName || '货币',
}
}
} catch {}
}
async function loadServers() {
try {
const res = await request.get('/api/server/list')
if (res?.code === 0) {
servers.value = res.serverlist?.[0]?.serverlist || []
}
} catch {}
}
async function handleWithdraw() {
if (!serverId.value) return ElMessage.error('请选择区服')
if (!roleId.value) return ElMessage.error('请输入角色 ID')
if (!roleName.value) return ElMessage.error('请输入角色名')
if (!payAccount.value) return ElMessage.error('请输入收款账户')
const a = parseInt(amount.value)
if (!a || a <= 0) return ElMessage.error('请输入提现数量')
const ratio = gameConfig.value?.withdrawRatio || 10000
const minOnce = gameConfig.value?.withdrawMinOnce || 20
if (a < ratio * minOnce) return ElMessage.error(`单次提现数量不能低于 ${ratio * minOnce}`)
const payTypeName = payType.value === 0 ? '支付宝' : '微信'
try {
await ElMessageBox.confirm(
`确认提现 ${a} ${currencyName.value},折合人民币 ${money.value} 元?\n收款方式${payTypeName}${payAccount.value}`,
'确认提现',
{ confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' }
)
} catch {
return
}
submitting.value = true
try {
const res = await request.post('/api/game/withdraw', {
server_id: 's' + serverId.value,
account: currentAccount.value,
role_id: roleId.value,
role_name: roleName.value,
pay_type: payType.value,
pay_account: payAccount.value,
amount: a,
})
if (res.code === 0) {
ElMessageBox.alert(res.message, '提现成功', { type: 'success', confirmButtonText: '知道了' })
amount.value = ''
} else {
ElMessage.error(res.message || '提现失败')
}
} catch {} finally {
submitting.value = false
}
}
onMounted(async () => {
const token = sessionStorage.getItem('CQ-TOKEN')
if (!token) {
router.replace('/login')
return
}
const payload = parseJwt(token)
if (payload?.username) {
currentAccount.value = payload.username
currentServerId.value = payload.server_id || 1
serverId.value = currentServerId.value
}
await Promise.all([loadConfig(), loadServers()])
})
</script>
<template>
<div class="withdraw-page pagebg">
<div class="withdraw-box">
<div class="header">
<button class="back-btn" @click="router.back()"> 返回</button>
<h2>游戏提现</h2>
</div>
<div class="account-info">
当前账号<strong>{{ currentAccount }}</strong>
</div>
<div class="form">
<!-- 区服 -->
<div class="form-item">
<label>选择区服</label>
<select v-model="serverId">
<option value="">请选择区服</option>
<option v-for="s in servers" :key="s.srvid" :value="s.srvid">
{{ s.serverName }}
</option>
</select>
</div>
<!-- 角色 ID -->
<div class="form-item">
<label>角色 ID</label>
<input
v-model="roleId"
type="number"
placeholder="请输入游戏内角色 ID"
min="1"
/>
</div>
<!-- 角色名 -->
<div class="form-item">
<label>角色名</label>
<input
v-model="roleName"
type="text"
placeholder="请输入游戏内角色名"
maxlength="24"
/>
</div>
<!-- 提现数量 -->
<div class="form-item">
<label>提现数量{{ currencyName }}</label>
<input
v-model="amount"
type="number"
:placeholder="`最少 ${(gameConfig.withdrawRatio || 10000) * (gameConfig.withdrawMinOnce || 20)} ${currencyName}`"
min="0"
/>
<div v-if="money > 0" class="hint"> 人民币 {{ money }} </div>
</div>
<!-- 收款方式 -->
<div class="form-item">
<label>收款方式</label>
<div class="pay-type-row">
<label class="radio-label" :class="{ active: payType === 0 }" @click="payType = 0">
支付宝
</label>
<label class="radio-label" :class="{ active: payType === 1 }" @click="payType = 1">
微信
</label>
</div>
</div>
<!-- 收款账户 -->
<div class="form-item">
<label>{{ payType === 0 ? '支付宝账号' : '微信号' }}</label>
<input
v-model="payAccount"
type="text"
:placeholder="payType === 0 ? '请输入支付宝账号(手机号/邮箱)' : '请输入微信号'"
maxlength="30"
/>
</div>
<div class="tip">
提示提现到账时间为工作日如有疑问请联系客服
</div>
<button
class="submit-btn"
:disabled="submitting"
@click="handleWithdraw"
>
{{ submitting ? '处理中…' : '申请提现' }}
</button>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.withdraw-page {
min-height: 100vh;
background: #0a0a0a;
display: flex;
justify-content: center;
padding: 20px;
box-sizing: border-box;
.withdraw-box {
width: 100%;
max-width: 480px;
color: #ddd;
.header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
h2 {
color: #f5bd10;
font-size: 18px;
margin: 0;
}
.back-btn {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.2);
color: rgba(255, 255, 255, 0.7);
padding: 6px 14px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
white-space: nowrap;
&:hover {
border-color: #f5bd10;
color: #f5bd10;
}
}
}
.account-info {
background: rgba(245, 189, 16, 0.08);
border: 1px solid rgba(245, 189, 16, 0.2);
border-radius: 8px;
padding: 10px 16px;
font-size: 14px;
margin-bottom: 20px;
strong {
color: #f5bd10;
}
}
.form {
display: flex;
flex-direction: column;
gap: 16px;
.form-item {
display: flex;
flex-direction: column;
gap: 6px;
label {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
}
input, select {
height: 38px;
padding: 0 12px;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
background: rgba(255, 255, 255, 0.06);
color: #fff;
font-size: 14px;
outline: none;
box-sizing: border-box;
&::placeholder {
color: rgba(255, 255, 255, 0.3);
}
&:focus {
border-color: #f5bd10;
}
option {
color: #333;
}
}
.hint {
font-size: 12px;
color: #f5bd10;
}
.pay-type-row {
display: flex;
gap: 12px;
.radio-label {
flex: 1;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
cursor: pointer;
font-size: 14px;
color: rgba(255, 255, 255, 0.5);
transition: all 0.2s;
&.active {
border-color: #f5bd10;
color: #f5bd10;
background: rgba(245, 189, 16, 0.08);
}
&:hover:not(.active) {
border-color: rgba(255, 255, 255, 0.3);
}
}
}
}
.tip {
font-size: 12px;
color: rgba(255, 255, 255, 0.35);
padding: 8px 12px;
background: rgba(255, 255, 255, 0.03);
border-radius: 6px;
border-left: 3px solid rgba(245, 189, 16, 0.4);
}
.submit-btn {
height: 44px;
border-radius: 8px;
border: none;
background: #f5bd10;
color: #000;
font-weight: bold;
font-size: 16px;
letter-spacing: 4px;
cursor: pointer;
transition: opacity 0.2s;
&:hover:not(:disabled) {
opacity: 0.85;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
}
}
}
</style>