|
|
@@ -650,7 +650,7 @@ const tabs = computed(() => [
|
|
|
t('assets.spot'),
|
|
|
t('assets.futures'),
|
|
|
t('assets.copyTrade'),
|
|
|
- t('assets.lockedStakingAccount'),
|
|
|
+ t('assets.stakingTab'),
|
|
|
])
|
|
|
|
|
|
// ── 划转弹窗(资产页内快速划转,与独立路由 TransferView 并存)────
|
|
|
@@ -844,33 +844,29 @@ function stakingPendingAmount(order: StakingOrderItem): number {
|
|
|
return Number.isFinite(pending) && pending > 0 ? pending : 0
|
|
|
}
|
|
|
|
|
|
-function stakingOrderStatusLabel(order: StakingOrderItem): string {
|
|
|
- const status = Number(order.status)
|
|
|
- if (status === 0) {
|
|
|
- return t('assets.subscribing')
|
|
|
- }
|
|
|
- if (status === 2) {
|
|
|
- return t('assets.completed')
|
|
|
- }
|
|
|
- return t('assets.releasing')
|
|
|
+function stakingOrderCoinUnit(order: StakingOrderItem): string {
|
|
|
+ const u = order.coinUnit?.trim()
|
|
|
+ if (!u) return 'IBIT'
|
|
|
+ return u.toUpperCase() === 'IBIT' ? 'IBIT' : u
|
|
|
}
|
|
|
|
|
|
-function stakingOrderStatusClass(order: StakingOrderItem): string {
|
|
|
- const status = Number(order.status)
|
|
|
- if (status === 0) {
|
|
|
- return 'is-subscribing'
|
|
|
- }
|
|
|
- if (status === 2) {
|
|
|
- return 'is-completed'
|
|
|
- }
|
|
|
- return 'is-releasing'
|
|
|
+function formatStakingOrderId(order: StakingOrderItem): string {
|
|
|
+ return `ORD${order.id}`
|
|
|
+}
|
|
|
+
|
|
|
+function formatStakingOrderAmount(
|
|
|
+ order: StakingOrderItem,
|
|
|
+ value: string | number | undefined,
|
|
|
+): string {
|
|
|
+ if (obscure.value) return '****'
|
|
|
+ return `${fmt(value)} ${stakingOrderCoinUnit(order)}`
|
|
|
}
|
|
|
|
|
|
function shortDate(value?: string) {
|
|
|
if (!value) {
|
|
|
return '-'
|
|
|
}
|
|
|
- return value.replace('T', ' ').slice(0, 19)
|
|
|
+ return value.replace('T', ' ').slice(0, 10)
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
@@ -1205,77 +1201,76 @@ function shortDate(value?: string) {
|
|
|
<!-- Tab 5:锁仓账户 -->
|
|
|
<template v-if="activeTab === ASSET_TABS.STAKING">
|
|
|
<div class="staking-hero dark-card">
|
|
|
- <div>
|
|
|
- <p class="label-sm">{{ t('assets.releaseTotal') }}</p>
|
|
|
- <div class="staking-main-balance">
|
|
|
- <span>{{ obscure ? '****' : fmt(stakingOverviewLocked) }}</span>
|
|
|
- <span class="unit">{{ t('assets.ibitUnit') }}</span>
|
|
|
- </div>
|
|
|
- <div class="staking-total-row">
|
|
|
- <span>{{ t('assets.totalLockedBalance') }}</span>
|
|
|
- <span>{{ obscure ? '****' : fmt(Number(stakingOverviewAvailable) + Number(stakingOverviewLocked)) }}</span>
|
|
|
- </div>
|
|
|
+ <p class="label-sm">{{ t('assets.releaseTotal') }}</p>
|
|
|
+ <div class="staking-main-balance">
|
|
|
+ <span class="staking-main-num">{{ obscure ? '****' : fmt(stakingOverviewLocked) }}</span>
|
|
|
+ <span class="unit">{{ t('assets.ibitUnit') }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="staking-total-row">
|
|
|
+ <span>{{ t('assets.totalLockedBalance') }}</span>
|
|
|
+ <span class="staking-total-val">
|
|
|
+ {{ obscure ? '****' : fmt(Number(stakingOverviewAvailable) + Number(stakingOverviewLocked)) }}
|
|
|
+ </span>
|
|
|
</div>
|
|
|
<div class="staking-actions">
|
|
|
<button class="act-btn act-primary" type="button" @click="router.push('/assets/deposit')">{{ t('assets.depositCoin') }}</button>
|
|
|
- <button class="act-btn" type="button" @click="router.push('/assets/withdraw')">{{ t('assets.withdrawCoin') }}</button>
|
|
|
- <button class="act-btn" type="button" @click="openTransfer('SPOT', 'SWAP')">{{ t('assets.transfer') }}</button>
|
|
|
+ <button class="act-btn act-muted" type="button" @click="router.push('/assets/withdraw')">{{ t('assets.withdrawCoin') }}</button>
|
|
|
+ <button class="act-btn act-muted" type="button" @click="openTransfer('SPOT', 'SWAP')">{{ t('assets.transfer') }}</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="staking-orders-head">
|
|
|
- <p class="staking-orders-title">{{ t('assets.myStakingOrders') }}</p>
|
|
|
- <select
|
|
|
- v-model="stakingOrderStatusFilter"
|
|
|
- class="staking-status-select"
|
|
|
- :disabled="stakingOrdersLoading"
|
|
|
- @change="changeStakingOrderStatus"
|
|
|
- >
|
|
|
- <option v-for="option in stakingOrderStatusOptions" :key="option.value" :value="option.value">
|
|
|
- {{ option.label }}
|
|
|
- </option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="staking-pull-area"
|
|
|
- @touchstart.passive="beginStakingPull"
|
|
|
- @touchmove.passive="moveStakingPull"
|
|
|
- @touchend="endStakingPull"
|
|
|
- @touchcancel="endStakingPull"
|
|
|
- >
|
|
|
+ <div class="staking-orders-section">
|
|
|
+ <div class="staking-orders-head">
|
|
|
+ <p class="staking-orders-title">{{ t('assets.myStakingOrders') }}</p>
|
|
|
+ <select
|
|
|
+ v-model="stakingOrderStatusFilter"
|
|
|
+ class="staking-status-select"
|
|
|
+ :disabled="stakingOrdersLoading"
|
|
|
+ @change="changeStakingOrderStatus"
|
|
|
+ >
|
|
|
+ <option v-for="option in stakingOrderStatusOptions" :key="option.value" :value="option.value">
|
|
|
+ {{ option.label }}
|
|
|
+ </option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
<div
|
|
|
- class="staking-refresh-hint"
|
|
|
- :style="{ height: `${stakingPullDistance}px`, opacity: stakingPullDistance > 0 || stakingOrdersLoading ? 1 : 0 }"
|
|
|
+ class="staking-pull-area"
|
|
|
+ @touchstart.passive="beginStakingPull"
|
|
|
+ @touchmove.passive="moveStakingPull"
|
|
|
+ @touchend="endStakingPull"
|
|
|
+ @touchcancel="endStakingPull"
|
|
|
>
|
|
|
- {{ stakingRefreshText }}
|
|
|
- </div>
|
|
|
- <div v-if="stakingOrdersLoading && !stakingOrders.length" class="follow-loading">{{ t('common.loading') }}</div>
|
|
|
- <div v-else-if="!stakingOrders.length" class="follow-empty">{{ t('common.noData') }}</div>
|
|
|
- <div v-else class="staking-order-list">
|
|
|
- <div v-for="order in stakingOrders" :key="order.id" class="staking-order-card dark-card">
|
|
|
- <div class="staking-order-no">{{ t('assets.stakingOrderNo') }}: ORD{{ order.id }}</div>
|
|
|
- <div class="staking-order-row">
|
|
|
- <span>{{ t('assets.stakingTotal') }}</span>
|
|
|
- <span>{{ obscure ? '****' : fmt(order.amount) }} {{ order.coinUnit || t('assets.ibitUnit').toUpperCase() }}</span>
|
|
|
- </div>
|
|
|
- <div class="staking-order-row">
|
|
|
- <span>{{ t('assets.released') }}</span>
|
|
|
- <span>{{ obscure ? '****' : fmt(order.releasedAmount) }} {{ order.coinUnit || t('assets.ibitUnit').toUpperCase() }}</span>
|
|
|
- </div>
|
|
|
- <div class="staking-order-row">
|
|
|
- <span>{{ t('assets.pendingRelease') }}</span>
|
|
|
- <span>{{ obscure ? '****' : fmt(stakingPendingAmount(order)) }}{{ order.coinUnit || t('assets.ibitUnit').toUpperCase() }}</span>
|
|
|
- </div>
|
|
|
- <div class="staking-order-row">
|
|
|
- <span>{{ t('assets.startTime') }}</span>
|
|
|
- <span>{{ shortDate(order.stakingTime || order.createTime) }}</span>
|
|
|
- </div>
|
|
|
- <div class="staking-order-row">
|
|
|
- <span>{{ t('common.status') }}</span>
|
|
|
- <span class="staking-order-status" :class="stakingOrderStatusClass(order)">
|
|
|
- {{ stakingOrderStatusLabel(order) }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
+ <div
|
|
|
+ class="staking-refresh-hint"
|
|
|
+ :style="{ height: `${stakingPullDistance}px`, opacity: stakingPullDistance > 0 || stakingOrdersLoading ? 1 : 0 }"
|
|
|
+ >
|
|
|
+ {{ stakingRefreshText }}
|
|
|
+ </div>
|
|
|
+ <div v-if="stakingOrdersLoading && !stakingOrders.length" class="follow-loading">{{ t('common.loading') }}</div>
|
|
|
+ <div v-else-if="!stakingOrders.length" class="follow-empty">{{ t('common.noData') }}</div>
|
|
|
+ <div v-else class="staking-table-wrap">
|
|
|
+ <table class="staking-table">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>{{ t('assets.stakingOrderNo') }}</th>
|
|
|
+ <th>{{ t('assets.purchaseQty') }}</th>
|
|
|
+ <th>{{ t('assets.stakingTotal') }}</th>
|
|
|
+ <th>{{ t('assets.released') }}</th>
|
|
|
+ <th>{{ t('assets.pendingRelease') }}</th>
|
|
|
+ <th>{{ t('assets.startTime') }}</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr v-for="order in stakingOrders" :key="order.id">
|
|
|
+ <td class="staking-td-id">{{ formatStakingOrderId(order) }}</td>
|
|
|
+ <td>{{ formatStakingOrderAmount(order, order.amount) }}</td>
|
|
|
+ <td>{{ formatStakingOrderAmount(order, order.amount) }}</td>
|
|
|
+ <td>{{ formatStakingOrderAmount(order, order.releasedAmount) }}</td>
|
|
|
+ <td>{{ formatStakingOrderAmount(order, stakingPendingAmount(order)) }}</td>
|
|
|
+ <td class="staking-td-time">{{ shortDate(order.stakingTime || order.createTime) }}</td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -1491,52 +1486,79 @@ function shortDate(value?: string) {
|
|
|
}
|
|
|
.follow-actions .act-btn { flex: 1; }
|
|
|
|
|
|
+.act-btn.act-muted {
|
|
|
+ background: rgba(255, 255, 255, 0.06);
|
|
|
+ border-color: rgba(255, 255, 255, 0.12);
|
|
|
+ color: var(--color-text-secondary);
|
|
|
+}
|
|
|
+.act-btn.act-muted:hover {
|
|
|
+ border-color: rgba(255, 255, 255, 0.22);
|
|
|
+ color: var(--color-text-primary);
|
|
|
+}
|
|
|
+
|
|
|
/* ── 锁仓 Tab ── */
|
|
|
.staking-hero {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: var(--space-2);
|
|
|
padding: var(--space-6);
|
|
|
margin-bottom: var(--space-5);
|
|
|
- background: #1b252c;
|
|
|
}
|
|
|
.staking-main-balance {
|
|
|
display: flex;
|
|
|
align-items: baseline;
|
|
|
gap: var(--space-2);
|
|
|
- margin-bottom: var(--space-4);
|
|
|
- font-size: 28px;
|
|
|
+ margin: 0 0 var(--space-2);
|
|
|
+}
|
|
|
+.staking-main-num {
|
|
|
+ font-size: 32px;
|
|
|
font-weight: var(--fw-bold);
|
|
|
color: var(--color-text-primary);
|
|
|
font-family: var(--font-family-num);
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+.staking-main-balance .unit {
|
|
|
+ font-size: var(--text-body);
|
|
|
+ color: var(--color-text-secondary);
|
|
|
}
|
|
|
.staking-total-row {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
gap: var(--space-6);
|
|
|
+ padding-bottom: var(--space-4);
|
|
|
+ margin-bottom: var(--space-2);
|
|
|
+ border-bottom: 1px solid var(--color-border);
|
|
|
color: var(--color-text-secondary);
|
|
|
font-size: var(--text-small);
|
|
|
}
|
|
|
-.staking-total-row span:last-child {
|
|
|
+.staking-total-val {
|
|
|
color: var(--color-text-primary);
|
|
|
font-family: var(--font-family-num);
|
|
|
+ font-weight: var(--fw-medium);
|
|
|
}
|
|
|
.staking-actions {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(3, 1fr);
|
|
|
- gap: var(--space-4);
|
|
|
- margin-top: var(--space-5);
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: var(--space-3);
|
|
|
}
|
|
|
.staking-actions .act-btn {
|
|
|
- justify-content: center;
|
|
|
- min-width: 0;
|
|
|
+ min-width: 88px;
|
|
|
+ padding: var(--space-2) var(--space-6);
|
|
|
+}
|
|
|
+.staking-orders-section {
|
|
|
+ margin-top: var(--space-2);
|
|
|
}
|
|
|
.staking-orders-head {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
gap: var(--space-3);
|
|
|
- margin: var(--space-5) 0 var(--space-3);
|
|
|
+ margin-bottom: var(--space-4);
|
|
|
}
|
|
|
.staking-orders-title {
|
|
|
- font-size: var(--text-body);
|
|
|
+ font-size: var(--text-h3);
|
|
|
font-weight: var(--fw-semibold);
|
|
|
color: var(--color-text-primary);
|
|
|
margin: 0;
|
|
|
@@ -1547,7 +1569,7 @@ function shortDate(value?: string) {
|
|
|
padding: 0 var(--space-3);
|
|
|
border: 1px solid var(--color-border);
|
|
|
border-radius: var(--radius-md);
|
|
|
- background: #1b252c;
|
|
|
+ background: var(--color-bg-input);
|
|
|
color: var(--color-text-primary);
|
|
|
font-size: var(--text-small);
|
|
|
outline: none;
|
|
|
@@ -1556,7 +1578,7 @@ function shortDate(value?: string) {
|
|
|
opacity: .55;
|
|
|
}
|
|
|
.staking-status-select option {
|
|
|
- background: #1a1a1a;
|
|
|
+ background: var(--color-bg-secondary);
|
|
|
}
|
|
|
.staking-pull-area {
|
|
|
touch-action: pan-y;
|
|
|
@@ -1570,42 +1592,42 @@ function shortDate(value?: string) {
|
|
|
font-size: var(--text-small);
|
|
|
transition: height .18s ease, opacity .18s ease;
|
|
|
}
|
|
|
-.staking-order-list {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: var(--space-3);
|
|
|
-}
|
|
|
-.staking-order-card {
|
|
|
- padding: var(--space-5) var(--space-6);
|
|
|
- background: #1b252c;
|
|
|
-}
|
|
|
-.staking-order-no {
|
|
|
- font-size: var(--text-small);
|
|
|
- font-weight: var(--fw-semibold);
|
|
|
- color: var(--color-text-primary);
|
|
|
- margin-bottom: var(--space-4);
|
|
|
+.staking-table-wrap {
|
|
|
+ overflow-x: auto;
|
|
|
+ -webkit-overflow-scrolling: touch;
|
|
|
}
|
|
|
-.staking-order-row {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- gap: var(--space-4);
|
|
|
- padding: var(--space-2) 0;
|
|
|
+.staking-table {
|
|
|
+ width: 100%;
|
|
|
+ min-width: 720px;
|
|
|
+ border-collapse: collapse;
|
|
|
font-size: var(--text-small);
|
|
|
}
|
|
|
-.staking-order-row span:first-child {
|
|
|
- color: var(--color-text-secondary);
|
|
|
+.staking-table th {
|
|
|
+ padding: var(--space-3) var(--space-4) var(--space-4);
|
|
|
+ font-weight: var(--fw-normal);
|
|
|
+ font-size: var(--text-caption);
|
|
|
+ color: var(--color-text-muted);
|
|
|
+ text-align: left;
|
|
|
+ white-space: nowrap;
|
|
|
+ border-bottom: 1px solid var(--color-border);
|
|
|
}
|
|
|
-.staking-order-row span:last-child {
|
|
|
+.staking-table td {
|
|
|
+ padding: var(--space-5) var(--space-4);
|
|
|
color: var(--color-text-primary);
|
|
|
font-family: var(--font-family-num);
|
|
|
- text-align: right;
|
|
|
+ white-space: nowrap;
|
|
|
+ vertical-align: middle;
|
|
|
}
|
|
|
-.staking-order-row .staking-order-status.is-subscribing,
|
|
|
-.staking-order-row .staking-order-status.is-releasing {
|
|
|
- color: var(--color-primary);
|
|
|
+.staking-table tbody tr:last-child td {
|
|
|
+ padding-bottom: var(--space-2);
|
|
|
+}
|
|
|
+.staking-td-id {
|
|
|
+ font-weight: var(--fw-medium);
|
|
|
+ font-family: var(--font-family);
|
|
|
}
|
|
|
-.staking-order-row .staking-order-status.is-completed {
|
|
|
+.staking-td-time {
|
|
|
color: var(--color-text-secondary);
|
|
|
+ font-family: var(--font-family);
|
|
|
}
|
|
|
|
|
|
/* ── 跟单 Tab 仓位列表 ── */
|