|
@@ -0,0 +1,432 @@
|
|
|
|
+<route lang="json5">
|
|
|
|
+{
|
|
|
|
+ style: {
|
|
|
|
+ navigationStyle: 'custom',
|
|
|
|
+ navigationBarTitleText: '',
|
|
|
|
+ },
|
|
|
|
+}
|
|
|
|
+</route>
|
|
|
|
+<template>
|
|
|
|
+ <view class="w-full min-h-screen bg-bgc flex flex-col items-center text-white">
|
|
|
|
+ <text class="mt-70rpx fw-540 text-[45rpx]">{{ $t('purse.Withdraw') }}</text>
|
|
|
|
+ <!-- 余额显示 -->
|
|
|
|
+ <view class="w-690rpx h-538rpx rounded-30rpx bg-[#27272A] flex items-center flex-col mt-45rpx">
|
|
|
|
+ <!-- TON输入框 -->
|
|
|
|
+ <view class="custom-input mt-30rpx">
|
|
|
|
+ <image src="@/static/images/purse/u.png" class="w-70rpx h-70rpx ml-14rpx"></image>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="tonInput"
|
|
|
|
+ @input="handleTonInput"
|
|
|
|
+ placeholder="0"
|
|
|
|
+ class="input-field"
|
|
|
|
+ />
|
|
|
|
+ </view>
|
|
|
|
+
|
|
|
|
+ <!-- 提示信息 -->
|
|
|
|
+ <text
|
|
|
|
+ class="text-26rpx text-left w-full mt-30rpx ml-56rpx"
|
|
|
|
+ :class="{ 'text-red-500': showError }"
|
|
|
|
+ >
|
|
|
|
+ {{ errorMessage || i18n.global.t('purse.Minimum') }}
|
|
|
|
+ </text>
|
|
|
|
+
|
|
|
|
+ <!-- 金币输入框 -->
|
|
|
|
+ <view class="custom-input mt-40rpx">
|
|
|
|
+ <image src="@/static/images/purse/blackGB.png" class="w-70rpx h-70rpx ml-14rpx"></image>
|
|
|
|
+ <input
|
|
|
|
+ type="text"
|
|
|
|
+ v-model="coinInput"
|
|
|
|
+ @input="handleCoinInput"
|
|
|
|
+ placeholder="Enter withdrawal address"
|
|
|
|
+ class="input-field"
|
|
|
|
+ />
|
|
|
|
+ </view>
|
|
|
|
+
|
|
|
|
+ <!-- 连接钱包/提现按钮 -->
|
|
|
|
+ <view
|
|
|
|
+ @click="triggerConnect"
|
|
|
|
+ class="w-630rpx h-100rpx rounded-20rpx btn-bg mt-60rpx flex items-center justify-center cursor-pointer"
|
|
|
|
+ >
|
|
|
|
+ <text class="text-32rpx text-white">{{ wallet ? 'Withdraw' : 'Connect' }}</text>
|
|
|
|
+ </view>
|
|
|
|
+ </view>
|
|
|
|
+ <!-- 记录刷新区 -->
|
|
|
|
+ <view class="w-690rpx mt-49rpx flex items-center justify-between">
|
|
|
|
+ <text>{{ $t('purse.Record') }}</text>
|
|
|
|
+ <image
|
|
|
|
+ @click="refreshRecords"
|
|
|
|
+ src="@/static/images/purse/shuaxin.png"
|
|
|
|
+ class="w-42rpx h-42rpx"
|
|
|
|
+ ></image>
|
|
|
|
+ </view>
|
|
|
|
+ <view
|
|
|
|
+ v-for="(record, index) in pcamaignList"
|
|
|
|
+ :key="index"
|
|
|
|
+ class="w-690rpx h-130rpx mt-30rpx rounded-20rpx bg-[#13355A] flex items-center justify-between mt-20rpx"
|
|
|
|
+ >
|
|
|
|
+ <view class="flex items-center ml-20rpx justify-between w-full">
|
|
|
|
+ <view>
|
|
|
|
+ <image src="@/static/images/purse/blackGB.png" class="w-70rpx h-70rpx mr-29rpx" />
|
|
|
|
+ <text class="text-24rpx text-white mr-20rpx">{{ record.pay_time }}</text>
|
|
|
|
+ </view>
|
|
|
|
+
|
|
|
|
+ <view class="flex items-center">
|
|
|
|
+ <text class="text-24rpx text-white mr-40rpx">+{{ record.coin_exchange }}</text>
|
|
|
|
+ </view>
|
|
|
|
+ </view>
|
|
|
|
+ </view>
|
|
|
|
+ </view>
|
|
|
|
+ <wd-notify />
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup lang="ts">
|
|
|
|
+import {
|
|
|
|
+ useTonWallet,
|
|
|
|
+ useTonConnectUI,
|
|
|
|
+ useTonConnectModal,
|
|
|
|
+ useTonAddress,
|
|
|
|
+} from '@townsquarelabs/ui-vue'
|
|
|
|
+import { useTelegramBackButton } from '@/hooks/useTelegramBackButton'
|
|
|
|
+import {
|
|
|
|
+ getCoinAddress,
|
|
|
|
+ getRecharge,
|
|
|
|
+ postRechargeCoin,
|
|
|
|
+ postRechargeCoinReturn,
|
|
|
|
+ RechargeCoinList,
|
|
|
|
+} from '@/service/purse'
|
|
|
|
+import i18n from '@/locale/index'
|
|
|
|
+import { useNotify } from 'wot-design-uni'
|
|
|
|
+
|
|
|
|
+const { showNotify } = useNotify()
|
|
|
|
+const { open, close } = useTonConnectModal()
|
|
|
|
+const { isVisible } = useTelegramBackButton(() => {
|
|
|
|
+ uni.navigateBack()
|
|
|
|
+})
|
|
|
|
+const userFriendlyAddress = useTonAddress()
|
|
|
|
+// 状态管理
|
|
|
|
+const wallet = useTonWallet()
|
|
|
|
+const { tonConnectUI } = useTonConnectUI()
|
|
|
|
+const tonInput = ref('') // TON 输入值
|
|
|
|
+const coinInput = ref('') // 金币输入值
|
|
|
|
+const userBalance = ref(0)
|
|
|
|
+const errorMessage = ref('')
|
|
|
|
+const showError = ref(false)
|
|
|
|
+const inputMode = ref('ton') // 当前输入模式: 'ton' | 'coin'
|
|
|
|
+
|
|
|
|
+// 计算最小提现金额(以nanoTON为单位)
|
|
|
|
+const MIN_WITHDRAWAL = 0.1 // 1 TON
|
|
|
|
+
|
|
|
|
+let balanceInterval: number | null = null
|
|
|
|
+
|
|
|
|
+// 获取钱包余额
|
|
|
|
+const getWalletBalance = async (address: string) => {
|
|
|
|
+ try {
|
|
|
|
+ const response = await fetch(
|
|
|
|
+ `https://toncenter.com/api/v2/getAddressBalance?address=${address}`,
|
|
|
|
+ )
|
|
|
|
+ const data = await response.json()
|
|
|
|
+
|
|
|
|
+ if (data.ok) {
|
|
|
|
+ userBalance.value = Number(data.result) / 1000000000
|
|
|
|
+ console.log('Wallet balance:', userBalance.value, 'TON')
|
|
|
|
+ } else {
|
|
|
|
+ console.error('Failed to get balance, invalid response:', data)
|
|
|
|
+ userBalance.value = 0
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('Failed to fetch balance:', error)
|
|
|
|
+ userBalance.value = 0
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取兑换比例
|
|
|
|
+const proportion = ref(0)
|
|
|
|
+const getProportion = async () => {
|
|
|
|
+ const { data } = await getRecharge()
|
|
|
|
+ proportion.value = data.proportion
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 获取系统充值钱包地址
|
|
|
|
+const coinAddress = ref()
|
|
|
|
+const getCoinAddressFn = async () => {
|
|
|
|
+ const { data } = await getCoinAddress()
|
|
|
|
+ coinAddress.value = data.coin_address
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const formatNumberInput = (value: string) => {
|
|
|
|
+ // 移除非数字和小数点
|
|
|
|
+ value = value.replace(/[^\d.]/g, '')
|
|
|
|
+
|
|
|
|
+ // 处理前导零
|
|
|
|
+ if (value.startsWith('0') && value.length > 1 && !value.startsWith('0.')) {
|
|
|
|
+ value = value.replace(/^0+/, '')
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 如果是第一次输入小数点且在开头,自动补0
|
|
|
|
+ if (value.startsWith('.')) {
|
|
|
|
+ value = '0' + value
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 限制只能有一个小数点
|
|
|
|
+ if (value.includes('.') && value.split('.').length > 2) {
|
|
|
|
+ return value.substring(0, value.lastIndexOf('.'))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 限制小数位数为6位
|
|
|
|
+ if (value.includes('.')) {
|
|
|
|
+ const [integer, decimal] = value.split('.')
|
|
|
|
+ return `${integer}.${decimal.slice(0, 6)}`
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return value
|
|
|
|
+}
|
|
|
|
+// 常量定义
|
|
|
|
+const MIN_TON = 0.1 // 最小兑换数量改为 0.1 TON
|
|
|
|
+const MAX_TON = 1000000 // 最大 TON 输入限制
|
|
|
|
+const MAX_DECIMALS_TON = 6 // TON 最大小数位数
|
|
|
|
+const MAX_DECIMALS_COIN = 2 // 金币最大小数位数
|
|
|
|
+// 处理 TON 输入
|
|
|
|
+const handleTonInput = (e: any) => {
|
|
|
|
+ try {
|
|
|
|
+ let value = formatNumberInput(e.detail.value)
|
|
|
|
+ const numValue = Number(value)
|
|
|
|
+
|
|
|
|
+ // 检查最大值限制
|
|
|
|
+ if (numValue > MAX_TON) {
|
|
|
|
+ value = MAX_TON.toString()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 更新 TON 输入值
|
|
|
|
+ tonInput.value = value
|
|
|
|
+
|
|
|
|
+ // 计算对应的金币数量
|
|
|
|
+ if (value && proportion.value) {
|
|
|
|
+ const coins = numValue * proportion.value
|
|
|
|
+ coinInput.value = Number(coins.toFixed(MAX_DECIMALS_COIN)).toString()
|
|
|
|
+ } else {
|
|
|
|
+ coinInput.value = ''
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 输入值校验
|
|
|
|
+ if (!value) {
|
|
|
|
+ showError.value = false
|
|
|
|
+ errorMessage.value = ''
|
|
|
|
+ } else {
|
|
|
|
+ if (numValue < MIN_TON) {
|
|
|
|
+ showError.value = true
|
|
|
|
+ errorMessage.value = i18n.global.t('purse.Minimum') // 'Minimum withdrawal amount is 0.1 TON'
|
|
|
|
+ } else if (numValue > userBalance.value) {
|
|
|
|
+ showError.value = true
|
|
|
|
+ errorMessage.value = i18n.global.t('purse.balance') // 'Insufficient balance'
|
|
|
|
+ } else {
|
|
|
|
+ showError.value = false
|
|
|
|
+ errorMessage.value = ''
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('Error in handleTonInput:', error)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理金币输入
|
|
|
|
+const handleCoinInput = (e: any) => {
|
|
|
|
+ try {
|
|
|
|
+ let value = formatNumberInput(e.detail.value)
|
|
|
|
+ const numValue = Number(value)
|
|
|
|
+
|
|
|
|
+ // 检查最大值限制
|
|
|
|
+ const maxCoins = MAX_TON * proportion.value
|
|
|
|
+ if (numValue > maxCoins) {
|
|
|
|
+ value = maxCoins.toString()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 更新金币输入值
|
|
|
|
+ coinInput.value = value
|
|
|
|
+
|
|
|
|
+ // 计算对应的 TON 数量
|
|
|
|
+ if (value && proportion.value) {
|
|
|
|
+ const tons = numValue / proportion.value
|
|
|
|
+ tonInput.value = Number(tons.toFixed(MAX_DECIMALS_TON)).toString()
|
|
|
|
+
|
|
|
|
+ // 根据计算出的 TON 值进行验证
|
|
|
|
+ if (tons < MIN_TON) {
|
|
|
|
+ showError.value = true
|
|
|
|
+ errorMessage.value = i18n.global.t('purse.Minimum')
|
|
|
|
+ } else if (tons > userBalance.value) {
|
|
|
|
+ showError.value = true
|
|
|
|
+ errorMessage.value = i18n.global.t('purse.balance')
|
|
|
|
+ } else {
|
|
|
|
+ showError.value = false
|
|
|
|
+ errorMessage.value = ''
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ tonInput.value = ''
|
|
|
|
+ showError.value = false
|
|
|
|
+ errorMessage.value = ''
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('Error in handleCoinInput:', error)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 验证金额 - 仅在提交时使用
|
|
|
|
+const validateAmount = () => {
|
|
|
|
+ const tonAmount = Number(tonInput.value || 0)
|
|
|
|
+
|
|
|
|
+ if (!tonInput.value || tonAmount <= 0) {
|
|
|
|
+ errorMessage.value = i18n.global.t('purse.amount')
|
|
|
|
+ showError.value = true
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (tonAmount < MIN_TON) {
|
|
|
|
+ errorMessage.value = i18n.global.t('purse.Minimum')
|
|
|
|
+ showError.value = true
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (tonAmount > userBalance.value) {
|
|
|
|
+ errorMessage.value = i18n.global.t('purse.balance')
|
|
|
|
+ showError.value = true
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 连接钱包
|
|
|
|
+const triggerConnect = async () => {
|
|
|
|
+ if (!wallet.value) {
|
|
|
|
+ try {
|
|
|
|
+ await open()
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('Connection failed:', error)
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ handleWithdraw()
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 处理兑换
|
|
|
|
+const handleWithdraw = async () => {
|
|
|
|
+ if (!validateAmount()) {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if (!coinAddress.value) {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ try {
|
|
|
|
+ const transaction = {
|
|
|
|
+ validUntil: Math.floor(Date.now() / 1000) + 60,
|
|
|
|
+ messages: [
|
|
|
|
+ {
|
|
|
|
+ address: coinAddress.value,
|
|
|
|
+ // 转换为 nanoTON
|
|
|
|
+ amount: (Number(tonInput.value) * 1000000000).toString(),
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const result = await tonConnectUI.sendTransaction(transaction)
|
|
|
|
+
|
|
|
|
+ if (result) {
|
|
|
|
+ if (wallet.value?.account?.address) {
|
|
|
|
+ await getWalletBalance(wallet.value.account.address)
|
|
|
|
+ }
|
|
|
|
+ const { code, msg } = await postRechargeCoin({
|
|
|
|
+ coin: (Number(tonInput.value) * 1000000000).toString(),
|
|
|
|
+ coin_address: userFriendlyAddress.value,
|
|
|
|
+ boc: result.boc,
|
|
|
|
+ favorable: '',
|
|
|
|
+ })
|
|
|
|
+ if (code === 1) {
|
|
|
|
+ showNotify({ type: 'success', message: msg })
|
|
|
|
+ } else {
|
|
|
|
+ showNotify({ type: 'warning', message: msg })
|
|
|
|
+ }
|
|
|
|
+ tonInput.value = ''
|
|
|
|
+ coinInput.value = ''
|
|
|
|
+ showError.value = false
|
|
|
|
+ errorMessage.value = ''
|
|
|
|
+ // 刷新记录
|
|
|
|
+ refreshRecords()
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('Withdrawal failed:', error)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+const pcamaignList = ref<RechargeCoinList[]>([])
|
|
|
|
+// 刷新记录
|
|
|
|
+const refreshRecords = async () => {
|
|
|
|
+ try {
|
|
|
|
+ // 这里调用你的API获取最新记录
|
|
|
|
+ const { data } = await postRechargeCoinReturn()
|
|
|
|
+ pcamaignList.value = data
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('Failed to refresh records:', error)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 监听钱包连接状态
|
|
|
|
+onMounted(async () => {
|
|
|
|
+ await getProportion()
|
|
|
|
+ await getCoinAddressFn()
|
|
|
|
+ await refreshRecords()
|
|
|
|
+ if (wallet.value?.account?.address) {
|
|
|
|
+ console.info('🚀 ~ file:index method: line:296 -----', 1111)
|
|
|
|
+ getWalletBalance(wallet.value.account.address)
|
|
|
|
+
|
|
|
|
+ if (balanceInterval) clearInterval(balanceInterval)
|
|
|
|
+ balanceInterval = setInterval(() => {
|
|
|
|
+ getWalletBalance(wallet.value.account.address)
|
|
|
|
+ }, 30000)
|
|
|
|
+ } else {
|
|
|
|
+ userBalance.value = 0
|
|
|
|
+ if (balanceInterval) {
|
|
|
|
+ clearInterval(balanceInterval)
|
|
|
|
+ balanceInterval = null
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+onUnmounted(() => {
|
|
|
|
+ if (balanceInterval) {
|
|
|
|
+ clearInterval(balanceInterval)
|
|
|
|
+ }
|
|
|
|
+ tonConnectUI.removeStatusChangeListener()
|
|
|
|
+})
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
+.custom-input {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ width: 630rpx;
|
|
|
|
+ height: 100rpx;
|
|
|
|
+ padding-right: 20rpx;
|
|
|
|
+ background-color: white;
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.input-field {
|
|
|
|
+ flex: 1;
|
|
|
|
+ height: 100rpx;
|
|
|
|
+ padding-right: 20rpx;
|
|
|
|
+ font-size: 38rpx;
|
|
|
|
+ line-height: 100rpx;
|
|
|
|
+ color: black;
|
|
|
|
+ text-align: right;
|
|
|
|
+ background: transparent;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 修改 placeholder 样式 */
|
|
|
|
+.input-field::placeholder {
|
|
|
|
+ font-size: 38rpx;
|
|
|
|
+ color: #999;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.btn-bg {
|
|
|
|
+ background: #8ae54a;
|
|
|
|
+}
|
|
|
|
+</style>
|