This commit is contained in:
艾贤凌
2026-03-16 12:05:55 +08:00
parent af3a7c83e8
commit 6d4a72161f
33 changed files with 5671 additions and 178 deletions

View File

@@ -0,0 +1,400 @@
<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>