<template>
|
<section class="personal-page">
|
<!-- 头部:投资组合 + 右侧菜单图标 -->
|
<div class="page-header">
|
<h1 class="header-title">{{ $t('投资组合') }}</h1>
|
<div class="header-menu" @click="onMenuClick">
|
<div class="menu-icon">
|
<span class="line"></span>
|
<span class="line"></span>
|
<span class="line"></span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 资产卡片 -->
|
<div class="asset-card">
|
<div class="asset-label-row">
|
<span class="asset-label">{{ $t('资产') }}</span>
|
<div class="eye-wrap" @click="assetVisible = !assetVisible">
|
<Icon :name="assetVisible ? 'eye-o' : 'closed-eye'" class="eye-icon" />
|
</div>
|
</div>
|
<div class="asset-amount">
|
{{ assetVisible ? (assetsObj.totalAssets + ' USD') : '******' }}
|
</div>
|
|
<div class="asset-label-row">
|
<span class="asset-label">XAUT</span>
|
</div>
|
<div class="asset-amount">
|
{{ assetVisible ? (assetsObj.xautProfit) : '******' }}
|
</div>
|
|
<!-- 三个圆形按钮:充值、提现、兑换(模拟账户隐藏充值/提现,显示重置资金) -->
|
<div class="action-btns">
|
<template v-if="!isSimulation">
|
<div class="action-btn" @click="$router.push('/cryptos/recharge/rechargeList?isForeign=true')">
|
<div class="btn-icon">
|
<img src="@/assets/image/cz.png" alt="" class="btn-icon-img" />
|
</div>
|
<span class="btn-text">{{ $t('充值') }}</span>
|
</div>
|
<div class="action-btn" @click="$router.push('/cryptos/withdraw/withdrawPage?type=exchange')">
|
<div class="btn-icon">
|
<img src="@/assets/image/tx.png" alt="" class="btn-icon-img" />
|
</div>
|
<span class="btn-text">{{ $t('提现') }}</span>
|
</div>
|
</template>
|
<div v-if="isSimulation" class="action-btn" @click="onResetSimFunds">
|
<div class="btn-icon">
|
<img src="@/assets/image/dk.png" alt="" class="btn-icon-img" />
|
</div>
|
<span class="btn-text">{{ $t('重置') }}{{ $t('资金') }}</span>
|
</div>
|
<div class="action-btn" @click="$router.push('/cryptos/exchangePage')">
|
<div class="btn-icon">
|
<img src="@/assets/image/dh.png" alt="" class="btn-icon-img" />
|
</div>
|
<span class="btn-text">{{ $t('兑换') }}</span>
|
</div>
|
</div>
|
</div>
|
|
<!-- Tab:期权交易 / 合约交易 -->
|
<div class="tabs-wrap">
|
<div class="tabs-bar">
|
<div class="tab-item" :class="{ active: activeTab === 0 }" @click="activeTab = 0">
|
{{ $t('期权交易') }}
|
</div>
|
<div class="tab-item" :class="{ active: activeTab === 1 }" @click="activeTab = 1">
|
{{ $t('合约交易') }}
|
</div>
|
</div>
|
<div class="tab-content">
|
<!-- 期权交易:交割持仓列表 -->
|
<template v-if="activeTab === 0">
|
<delivery-hold-list v-if="optionsPositionList.length" :list-data="optionsPositionList" :price="0" />
|
<div v-else class="empty-state">
|
<img src="@/assets/image/kong.png" alt="" class="empty-icon" />
|
<div class="empty-text">{{ $t('暂无数据') }}</div>
|
</div>
|
</template>
|
<!-- 合约交易:永续持仓列表(平仓成功后刷新列表) -->
|
<template v-else>
|
<perpetual-position-list v-if="contractPositionList.length" :list-data="contractPositionList"
|
@sell="fetchContractHold" />
|
<div v-else class="empty-state">
|
<img src="@/assets/image/kong.png" alt="" class="empty-icon" />
|
<div class="empty-text">{{ $t('暂无数据') }}</div>
|
</div>
|
</template>
|
</div>
|
</div>
|
</section>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted, watch } from 'vue'
|
import { useRouter } from 'vue-router'
|
import { useStore } from 'vuex'
|
import { Icon } from 'vant'
|
import { showToast } from 'vant'
|
import { useI18n } from 'vue-i18n'
|
import { useUserStore } from '@/store/user'
|
import { _assetsTradeTop, _resetSimFunds } from '@/service/user.api'
|
import { contractOrder, _futrueOrderList } from '@/service/trade.api'
|
import PerpetualPositionList from '@/components/Transform/perpetual-position-list/index.vue'
|
import DeliveryHoldList from '@/components/Transform/deliveryContract/hold.vue'
|
|
const router = useRouter()
|
const store = useStore()
|
const userStore = useUserStore()
|
const { t } = useI18n()
|
|
const isSimulation = computed(() => userStore.userInfo?.accountType === 1)
|
|
const assetVisible = ref(true)
|
const activeTab = ref(0) // 0 期权交易(交割持仓) 1 合约交易(永续持仓)
|
const assetsObj = ref({})
|
const symbolType = ref('indices')
|
/** 期权交易(交割)持仓、合约交易(永续)持仓分开存,避免 tab 切换时互相覆盖 */
|
const optionsPositionList = ref([])
|
const contractPositionList = ref([])
|
const positionLoading = ref(false)
|
|
function formatAmount(v) {
|
if (v == null || v === '') return '0.0000'
|
const n = Number(v)
|
return isNaN(n) ? '0.0000' : n.toFixed(4)
|
}
|
|
function onMenuClick() {
|
router.push('/my/index')
|
}
|
|
/** 刷新资产与持仓数据 */
|
function refreshAssetData() {
|
assetsTradeTopFn()
|
fetchPositionList()
|
}
|
|
/** 模拟账户:重置资金,成功后刷新资产 */
|
function onResetSimFunds() {
|
_resetSimFunds()
|
.then(() => {
|
showToast(t('操作成功') || '操作成功')
|
refreshAssetData()
|
})
|
.catch(() => { })
|
}
|
|
const assetsTradeTopFn = () => {
|
_assetsTradeTop({
|
symbolType: symbolType.value,
|
tradeType: symbolType.value === 'cryptos' ? 'contract' : 'exchange'
|
}).then((res) => {
|
assetsObj.value = res || {}
|
}).catch(() => { })
|
}
|
|
/** 期权交易:交割持仓(与 /trade/options 交割一致) */
|
function fetchOptionsHold() {
|
positionLoading.value = true
|
_futrueOrderList('', 'orders', 1, '')
|
.then((data) => {
|
optionsPositionList.value = Array.isArray(data) ? data : []
|
})
|
.catch(() => {
|
optionsPositionList.value = []
|
})
|
.finally(() => {
|
positionLoading.value = false
|
})
|
}
|
|
/** 合约交易:永续持仓(与 /trade/options 永续一致) */
|
function fetchContractHold() {
|
positionLoading.value = true
|
contractOrder({
|
// symbol: '',
|
type: 'orders',
|
page_no: 1,
|
// symbolType: 'cryptos'
|
})
|
.then((data) => {
|
contractPositionList.value = Array.isArray(data) ? data : []
|
})
|
.catch(() => {
|
contractPositionList.value = []
|
})
|
.finally(() => {
|
positionLoading.value = false
|
})
|
}
|
|
function fetchPositionList() {
|
if (activeTab.value === 0) {
|
fetchOptionsHold()
|
} else {
|
fetchContractHold()
|
}
|
}
|
|
onMounted(async () => {
|
await store.dispatch('home/SET_CURRENCY')
|
assetsTradeTopFn()
|
fetchPositionList()
|
})
|
|
watch(symbolType, () => {
|
assetsTradeTopFn()
|
})
|
|
watch(activeTab, () => {
|
fetchPositionList()
|
})
|
</script>
|
|
<style lang="scss" scoped>
|
@import '@/assets/css/variable.scss';
|
|
.personal-page {
|
min-height: 100vh;
|
background: $main_background;
|
padding-bottom: calc(80px + constant(safe-area-inset-bottom));
|
padding-bottom: calc(80px + env(safe-area-inset-bottom));
|
box-sizing: border-box;
|
}
|
|
.page-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
height: 8.8rem;
|
padding: 0 2.4rem;
|
background: #fff;
|
box-sizing: border-box;
|
|
.header-title {
|
font-size: 3.4rem;
|
font-weight: 700;
|
color: $text_color;
|
margin: 0;
|
}
|
|
.header-menu {
|
width: 4.4rem;
|
height: 4.4rem;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
cursor: pointer;
|
}
|
|
.menu-icon {
|
width: 4rem;
|
height: 4rem;
|
border: 0.15rem solid $text_color1;
|
border-radius: 50%;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
gap: 0.4rem;
|
|
.line {
|
display: block;
|
width: 1.6rem;
|
height: 0.12rem;
|
background: $text_color1;
|
border-radius: 0.06rem;
|
}
|
}
|
}
|
|
.asset-card {
|
margin: 2rem 2.4rem;
|
padding: 4rem 2rem;
|
background: #fff;
|
border-radius: 1.2rem;
|
box-shadow: 0rem 0.8rem 3.2rem 0rem rgba(0, 0, 0, 0.13);
|
border: 1 solid $border_color;
|
box-sizing: border-box;
|
}
|
|
.asset-label-row {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
gap: 0.6rem;
|
margin-bottom: 1.2rem;
|
|
.asset-label {
|
font-size: 2.5rem;
|
color: $text_color1;
|
}
|
|
.eye-wrap {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
cursor: pointer;
|
}
|
|
.eye-icon {
|
font-size: 3.2rem;
|
color: $text_color1;
|
}
|
}
|
|
.asset-amount {
|
font-size: 3.4rem;
|
font-weight: 700;
|
color: $text_color;
|
text-align: center;
|
margin-bottom: 2.4rem;
|
letter-spacing: -0.02em;
|
}
|
|
.action-btns {
|
display: flex;
|
justify-content: space-around;
|
align-items: center;
|
padding-top: 1rem;
|
}
|
|
.action-btn {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
cursor: pointer;
|
|
.btn-icon {
|
width: 5rem;
|
height: 5rem;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-bottom: 0.8rem;
|
overflow: hidden;
|
}
|
|
.btn-icon-img {
|
width: 100%;
|
height: 100%;
|
object-fit: contain;
|
}
|
|
.btn-text {
|
font-size: 2.6rem;
|
color: $text_color;
|
}
|
}
|
|
.tabs-wrap {
|
margin: 0 2.4rem;
|
background: #fff;
|
border-radius: 1.2rem;
|
overflow: hidden;
|
}
|
|
.tabs-bar {
|
display: flex;
|
padding: 0.6rem;
|
gap: 0.6rem;
|
background: #fff;
|
box-shadow: 0 0.2rem 0.8rem rgba(0, 0, 0, 0.06);
|
border: 0.1rem solid $border_color;
|
border-radius: 1rem;
|
|
.tab-item {
|
flex: 1;
|
text-align: center;
|
padding: 1.4rem 0.8rem;
|
font-size: 2.8rem;
|
color: $text_color1;
|
border-radius: 0.8rem;
|
transition: all 0.2s ease;
|
cursor: pointer;
|
|
&.active {
|
background: linear-gradient(135deg, #92D1FF 0%, #7BB8FF 100%);
|
color: #fff;
|
font-weight: 500;
|
}
|
}
|
}
|
|
.tab-content {
|
min-height: 24rem;
|
padding: 2rem 0;
|
}
|
|
.empty-state {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
padding: 4rem 2rem;
|
|
.empty-icon {
|
width: 12rem;
|
margin-bottom: 1.6rem;
|
opacity: 0.5;
|
}
|
|
.empty-text {
|
font-size: 2.8rem;
|
color: $text_color1;
|
}
|
}
|
</style>
|