فهرست منبع

优化资产页锁仓 Tab 样式,与设计稿对齐。

锁仓概览改为纵向卡片布局,订单列表改为表格展示,并补充多语言文案。

Co-authored-by: Cursor <cursoragent@cursor.com>
jean 1 ماه پیش
والد
کامیت
9856dddd92

+ 2 - 0
code/src/locales/en.ts

@@ -294,11 +294,13 @@ export default {
     spotTradingAccount: 'Spot Trading Account',
     ibitAccount: 'iBit Account',
     lockedStakingAccount: 'Locked Account',
+    stakingTab: 'Lock-up',
     ibitUnit: 'iBit',
     releaseTotal: 'Total Released',
     totalLockedBalance: 'Total Locked Balance (iBit)',
     myStakingOrders: 'My Locked Orders',
     stakingOrderNo: 'Order No.',
+    purchaseQty: 'Purchase Qty',
     stakingTotal: 'Locked Amount',
     released: 'Released',
     pendingRelease: 'Pending Release',

+ 2 - 0
code/src/locales/hi.ts

@@ -296,11 +296,13 @@ export default {
     spotTradingAccount: 'स्पॉट ट्रेडिंग खाता',
     ibitAccount: 'iBit Account',
     lockedStakingAccount: 'Locked Account',
+    stakingTab: 'लॉक',
     ibitUnit: 'iBit',
     releaseTotal: 'कुल रिलीज़',
     totalLockedBalance: 'कुल लॉक बैलेंस (iBit)',
     myStakingOrders: 'मेरे लॉक ऑर्डर',
     stakingOrderNo: 'ऑर्डर नंबर',
+    purchaseQty: 'खरीद मात्रा',
     stakingTotal: 'लॉक राशि',
     released: 'रिलीज़ हुआ',
     pendingRelease: 'रिलीज़ लंबित',

+ 2 - 0
code/src/locales/id.ts

@@ -296,11 +296,13 @@ export default {
     spotTradingAccount: 'Akun Trading Spot',
     ibitAccount: 'iBit Account',
     lockedStakingAccount: 'Locked Account',
+    stakingTab: 'Lock',
     ibitUnit: 'iBit',
     releaseTotal: 'Total Dirilis',
     totalLockedBalance: 'Total Saldo Terkunci (iBit)',
     myStakingOrders: 'Order Terkunci Saya',
     stakingOrderNo: 'No. Order',
+    purchaseQty: 'Jumlah Beli',
     stakingTotal: 'Jumlah Terkunci',
     released: 'Dirilis',
     pendingRelease: 'Menunggu Rilis',

+ 2 - 0
code/src/locales/ja.ts

@@ -297,11 +297,13 @@ export default {
     spotTradingAccount: '現物取引アカウント',
     ibitAccount: 'iBit Account',
     lockedStakingAccount: 'Locked Account',
+    stakingTab: 'ロック',
     ibitUnit: 'iBit',
     releaseTotal: 'リリース総量',
     totalLockedBalance: '総ロック残高(iBit)',
     myStakingOrders: 'ロック注文',
     stakingOrderNo: '注文番号',
+    purchaseQty: '購入数量',
     stakingTotal: 'ロック総量',
     released: 'リリース済み',
     pendingRelease: 'リリース待ち',

+ 2 - 0
code/src/locales/ko.ts

@@ -297,11 +297,13 @@ export default {
     spotTradingAccount: '현물 거래 계좌',
     ibitAccount: 'iBit Account',
     lockedStakingAccount: 'Locked Account',
+    stakingTab: '락업',
     ibitUnit: 'iBit',
     releaseTotal: '총 해제 수량',
     totalLockedBalance: '총 락업 잔액(iBit)',
     myStakingOrders: '내 락업 주문',
     stakingOrderNo: '주문 번호',
+    purchaseQty: '구매 수량',
     stakingTotal: '락업 총액',
     released: '해제됨',
     pendingRelease: '해제 대기',

+ 2 - 0
code/src/locales/zh-CN.ts

@@ -297,11 +297,13 @@ export default {
     spotTradingAccount: '现货交易账户',
     ibitAccount: 'iBit账户',
     lockedStakingAccount: '锁仓账户',
+    stakingTab: '锁仓',
     ibitUnit: 'iBit',
     releaseTotal: '释放总量',
     totalLockedBalance: '总锁仓余额(iBit)',
     myStakingOrders: '我的锁仓订单',
     stakingOrderNo: '订单编号',
+    purchaseQty: '购买数量',
     stakingTotal: '锁仓总量',
     released: '已释放',
     pendingRelease: '待释放',

+ 2 - 0
code/src/locales/zh-TW.ts

@@ -294,11 +294,13 @@ export default {
     spotTradingAccount: '現貨交易帳戶',
     ibitAccount: 'iBit帳戶',
     lockedStakingAccount: '鎖倉帳戶',
+    stakingTab: '鎖倉',
     ibitUnit: 'iBit',
     releaseTotal: '釋放總量',
     totalLockedBalance: '總鎖倉餘額(iBit)',
     myStakingOrders: '我的鎖倉訂單',
     stakingOrderNo: '訂單編號',
+    purchaseQty: '購買數量',
     stakingTotal: '鎖倉總量',
     released: '已釋放',
     pendingRelease: '待釋放',

+ 146 - 124
code/src/views/assets/AssetsView.vue

@@ -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 仓位列表 ── */