소스 검색

feat:新增页面

st 2 달 전
부모
커밋
98996a468b
4개의 변경된 파일443개의 추가작업 그리고 2개의 파일을 삭제
  1. 1 1
      src/main.ts
  2. 8 0
      src/pages.json
  3. 432 0
      src/pages/trophy/wallet.vue
  4. 2 1
      src/types/uni-pages.d.ts

+ 1 - 1
src/main.ts

@@ -7,7 +7,7 @@ import { routeInterceptor, requestInterceptor, prototypeInterceptor } from './in
 import 'virtual:uno.css'
 import '@/style/index.scss'
 
-import '@/utils/vConsole'
+// import '@/utils/vConsole'
 
 export function createApp() {
   const app = createSSRApp(App)

+ 8 - 0
src/pages.json

@@ -87,6 +87,14 @@
         "navigationStyle": "custom",
         "navigationBarTitleText": ""
       }
+    },
+    {
+      "path": "pages/trophy/wallet",
+      "type": "page",
+      "style": {
+        "navigationStyle": "custom",
+        "navigationBarTitleText": ""
+      }
     }
   ],
   "subPackages": []

+ 432 - 0
src/pages/trophy/wallet.vue

@@ -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>

+ 2 - 1
src/types/uni-pages.d.ts

@@ -8,7 +8,8 @@ interface NavigateToOptions {
        "/pages/purse/index" |
        "/pages/task/index" |
        "/pages/team/index" |
-       "/pages/trophy/index";
+       "/pages/trophy/index" |
+       "/pages/trophy/wallet";
 }
 interface RedirectToOptions extends NavigateToOptions {}