|
@@ -3,12 +3,13 @@ import { computed, onMounted, ref, watch } from 'vue'
|
|
|
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
|
|
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
|
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
import {
|
|
import {
|
|
|
|
|
+ fetchIdoTeamCount,
|
|
|
fetchMinOpenStakingConfig,
|
|
fetchMinOpenStakingConfig,
|
|
|
fetchStakingConfigList,
|
|
fetchStakingConfigList,
|
|
|
submitStake,
|
|
submitStake,
|
|
|
type StakingConfig,
|
|
type StakingConfig,
|
|
|
} from '@/api/staking'
|
|
} from '@/api/staking'
|
|
|
-import { loadFundCoinBalance, STAKING_COIN_UNIT } from '@/utils/stakingWallet'
|
|
|
|
|
|
|
+import { loadFundCoinBalance, loadStakingWalletSnapshot, STAKING_COIN_UNIT } from '@/utils/stakingWallet'
|
|
|
import {
|
|
import {
|
|
|
estimatedUnlockDate,
|
|
estimatedUnlockDate,
|
|
|
formatUnlockDate,
|
|
formatUnlockDate,
|
|
@@ -33,7 +34,8 @@ const subscribeQty = ref('')
|
|
|
const error = ref<string | null>(null)
|
|
const error = ref<string | null>(null)
|
|
|
const availableBal = ref('0')
|
|
const availableBal = ref('0')
|
|
|
const showTransfer = ref(false)
|
|
const showTransfer = ref(false)
|
|
|
-
|
|
|
|
|
|
|
+const teamCount = ref(0)
|
|
|
|
|
+const lockedTotal = ref('0')
|
|
|
const configIdParam = computed(() => {
|
|
const configIdParam = computed(() => {
|
|
|
const raw = route.params.configId
|
|
const raw = route.params.configId
|
|
|
if (raw === undefined || raw === '' || raw === 'ido') return null
|
|
if (raw === undefined || raw === '' || raw === 'ido') return null
|
|
@@ -141,6 +143,31 @@ async function refreshWithdrawableIbit() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+async function refreshOverviewStats() {
|
|
|
|
|
+ const unit = stakingCoinUnit()
|
|
|
|
|
+ const [stakingSnap, team] = await Promise.all([
|
|
|
|
|
+ loadStakingWalletSnapshot(unit).catch(() => ({
|
|
|
|
|
+ available: '0',
|
|
|
|
|
+ locked: '0',
|
|
|
|
|
+ availableUsdt: '0',
|
|
|
|
|
+ lockedUsdt: '0',
|
|
|
|
|
+ })),
|
|
|
|
|
+ fetchIdoTeamCount().catch(() => 0),
|
|
|
|
|
+ ])
|
|
|
|
|
+ const total =
|
|
|
|
|
+ Number(stakingSnap.available) + Number(stakingSnap.locked)
|
|
|
|
|
+ lockedTotal.value = Number.isFinite(total) ? String(total) : '0'
|
|
|
|
|
+ teamCount.value = team
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function goToBroker() {
|
|
|
|
|
+ router.push({ name: 'broker' })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function goToStakingAccount() {
|
|
|
|
|
+ router.push({ name: 'assets', query: { tab: 'staking' } })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
async function onTransferSuccess() {
|
|
async function onTransferSuccess() {
|
|
|
showTransfer.value = false
|
|
showTransfer.value = false
|
|
|
await load({ silent: true })
|
|
await load({ silent: true })
|
|
@@ -172,7 +199,7 @@ async function load(opts?: { silent?: boolean }) {
|
|
|
error.value = t('finance.configNotFound')
|
|
error.value = t('finance.configNotFound')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- await refreshWithdrawableIbit()
|
|
|
|
|
|
|
+ await Promise.all([refreshWithdrawableIbit(), refreshOverviewStats()])
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
error.value = e instanceof Error ? e.message : t('common.loadFailed')
|
|
error.value = e instanceof Error ? e.message : t('common.loadFailed')
|
|
|
config.value = null
|
|
config.value = null
|
|
@@ -213,6 +240,7 @@ async function onSubmit() {
|
|
|
toast.success(t('finance.stakeSuccess'))
|
|
toast.success(t('finance.stakeSuccess'))
|
|
|
subscribeQty.value = ''
|
|
subscribeQty.value = ''
|
|
|
await load({ silent: true })
|
|
await load({ silent: true })
|
|
|
|
|
+ await refreshOverviewStats()
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
toast.error(e instanceof Error ? e.message : t('finance.stakeFailed'))
|
|
toast.error(e instanceof Error ? e.message : t('finance.stakeFailed'))
|
|
|
} finally {
|
|
} finally {
|
|
@@ -253,6 +281,26 @@ watch(() => route.params.configId, () => load())
|
|
|
</div>
|
|
</div>
|
|
|
</section>
|
|
</section>
|
|
|
|
|
|
|
|
|
|
+ <!-- 团队 / 锁仓总览 -->
|
|
|
|
|
+ <section class="ido-overview-section page-container">
|
|
|
|
|
+ <div class="ido-overview-grid">
|
|
|
|
|
+ <button type="button" class="ido-overview-card" @click="goToBroker">
|
|
|
|
|
+ <span class="ido-overview-label">
|
|
|
|
|
+ {{ t('finance.myTeam') }}
|
|
|
|
|
+ <span class="ido-overview-chevron" aria-hidden="true">></span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span class="ido-overview-value num">{{ fmtAmount(teamCount, 0) }}</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button type="button" class="ido-overview-card" @click="goToStakingAccount">
|
|
|
|
|
+ <span class="ido-overview-label">
|
|
|
|
|
+ {{ t('assets.stakingTotal') }}
|
|
|
|
|
+ <span class="ido-overview-chevron" aria-hidden="true">></span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span class="ido-overview-value num">{{ fmtAmount(lockedTotal, 2) }}</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
<!-- 参与预售 -->
|
|
<!-- 参与预售 -->
|
|
|
<section class="ido-form-section page-container">
|
|
<section class="ido-form-section page-container">
|
|
|
<h2 class="section-title">{{ t('finance.joinPresale') }}</h2>
|
|
<h2 class="section-title">{{ t('finance.joinPresale') }}</h2>
|
|
@@ -461,6 +509,58 @@ watch(() => route.params.configId, () => load())
|
|
|
display: block;
|
|
display: block;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Overview */
|
|
|
|
|
+.ido-overview-section {
|
|
|
|
|
+ max-width: 960px;
|
|
|
|
|
+ padding-bottom: var(--space-6);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.ido-overview-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
|
|
+ gap: var(--space-4);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.ido-overview-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: var(--space-4);
|
|
|
|
|
+ padding: var(--space-5) var(--space-6);
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.04);
|
|
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ transition: border-color 0.2s, background 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.ido-overview-card:hover {
|
|
|
|
|
+ border-color: rgba(240, 185, 11, 0.35);
|
|
|
|
|
+ background: rgba(240, 185, 11, 0.06);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.ido-overview-label {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: rgba(255, 255, 255, 0.55);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.ido-overview-chevron {
|
|
|
|
|
+ color: rgba(255, 255, 255, 0.35);
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.ido-overview-value {
|
|
|
|
|
+ font-size: clamp(28px, 4vw, 36px);
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ line-height: 1.1;
|
|
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/* Form */
|
|
/* Form */
|
|
|
.ido-form-section {
|
|
.ido-form-section {
|
|
|
max-width: 960px;
|
|
max-width: 960px;
|
|
@@ -695,6 +795,14 @@ watch(() => route.params.configId, () => load())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@media (max-width: 640px) {
|
|
@media (max-width: 640px) {
|
|
|
|
|
+ .ido-overview-grid {
|
|
|
|
|
+ gap: var(--space-3);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .ido-overview-card {
|
|
|
|
|
+ padding: var(--space-4) var(--space-5);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
.subscribe-grid {
|
|
.subscribe-grid {
|
|
|
grid-template-columns: 1fr;
|
|
grid-template-columns: 1fr;
|
|
|
}
|
|
}
|