feat: 添加持仓数据类型和组件,支持持仓信息的展示与刷新功能
This commit is contained in:
@@ -76,6 +76,10 @@ export type TradableData = Treaty.Data<typeof client.api.rwa.tokenization.tradab
|
|||||||
|
|
||||||
export type TradableBody = TreatyQuery<typeof client.api.rwa.tokenization.tradable_products.get>;
|
export type TradableBody = TreatyQuery<typeof client.api.rwa.tokenization.tradable_products.get>;
|
||||||
|
|
||||||
|
export type HoldingsData = Treaty.Data<typeof client.api.rwa.holdings.get>;
|
||||||
|
|
||||||
|
export type HoldingItem = HoldingsData extends { data: Array<infer T> } ? T : HoldingsData extends Array<infer T> ? T : never;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用版本信息
|
* 应用版本信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -304,6 +304,22 @@
|
|||||||
},
|
},
|
||||||
"loadMore": "Load More"
|
"loadMore": "Load More"
|
||||||
},
|
},
|
||||||
|
"holdings": {
|
||||||
|
"title": "My Holdings",
|
||||||
|
"empty": "No holdings yet",
|
||||||
|
"nameCode": "Name/Code",
|
||||||
|
"quantity": "Quantity",
|
||||||
|
"totalValue": "Total Value",
|
||||||
|
"purchaseDate": "Purchase Date",
|
||||||
|
"status": {
|
||||||
|
"active": "Active",
|
||||||
|
"tokenized": "Tokenized",
|
||||||
|
"locked": "Locked"
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"shares": "shares"
|
||||||
|
}
|
||||||
|
},
|
||||||
"market": {
|
"market": {
|
||||||
"title": "Market",
|
"title": "Market",
|
||||||
"search": {
|
"search": {
|
||||||
|
|||||||
@@ -310,6 +310,22 @@
|
|||||||
},
|
},
|
||||||
"loadMore": "加载更多"
|
"loadMore": "加载更多"
|
||||||
},
|
},
|
||||||
|
"holdings": {
|
||||||
|
"title": "我的持仓",
|
||||||
|
"empty": "暂无持仓订单",
|
||||||
|
"nameCode": "名称/编号",
|
||||||
|
"quantity": "数量",
|
||||||
|
"totalValue": "总价值",
|
||||||
|
"purchaseDate": "购买日期",
|
||||||
|
"status": {
|
||||||
|
"active": "持有中",
|
||||||
|
"tokenized": "已代币化",
|
||||||
|
"locked": "锁定中"
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"shares": "份"
|
||||||
|
}
|
||||||
|
},
|
||||||
"market": {
|
"market": {
|
||||||
"title": "市场",
|
"title": "市场",
|
||||||
"search": {
|
"search": {
|
||||||
|
|||||||
@@ -1,11 +1,122 @@
|
|||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
|
import type { InfiniteScrollCustomEvent, RefresherCustomEvent } from "@ionic/vue";
|
||||||
|
import type { HoldingItem } from "@/api/types";
|
||||||
|
import CryptocurrencyColorNuls from "~icons/cryptocurrency-color/nuls";
|
||||||
import { client, safeClient } from "@/api";
|
import { client, safeClient } from "@/api";
|
||||||
|
|
||||||
const { data } = safeClient(client.api.rwa.holdings.get());
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const holdingsData = ref<HoldingItem[]>([]);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
|
||||||
|
async function fetchHoldings() {
|
||||||
|
isLoading.value = true;
|
||||||
|
const { data } = await safeClient(() => client.api.rwa.holdings.get());
|
||||||
|
if (data.value) {
|
||||||
|
holdingsData.value = data.value.data || [];
|
||||||
|
}
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRefresh(event: RefresherCustomEvent) {
|
||||||
|
await fetchHoldings();
|
||||||
|
event.target.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusColor(status: string) {
|
||||||
|
switch (status) {
|
||||||
|
case "active":
|
||||||
|
return "success";
|
||||||
|
case "tokenized":
|
||||||
|
return "primary";
|
||||||
|
case "locked":
|
||||||
|
return "warning";
|
||||||
|
default:
|
||||||
|
return "medium";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getStatusText(status: HoldingItem["status"]) {
|
||||||
|
const statusMap = {
|
||||||
|
active: t("holdings.status.active"),
|
||||||
|
tokenized: t("holdings.status.tokenized"),
|
||||||
|
locked: t("holdings.status.locked"),
|
||||||
|
};
|
||||||
|
return statusMap[status];
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchHoldings();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
{{ data }}
|
<ion-refresher slot="fixed" @ion-refresh="handleRefresh($event)">
|
||||||
|
<ion-refresher-content />
|
||||||
|
</ion-refresher>
|
||||||
|
|
||||||
|
<div v-if="isLoading && holdingsData.length === 0" class="flex justify-center items-center h-64">
|
||||||
|
<ion-spinner />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="holdingsData.length === 0" class="flex flex-col justify-center items-center h-64">
|
||||||
|
<div class="text-text-500">
|
||||||
|
{{ t('holdings.empty') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="space-y-3">
|
||||||
|
<div
|
||||||
|
v-for="item in holdingsData"
|
||||||
|
:key="item.id"
|
||||||
|
class="bg-faint rounded-xl p-3"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-center mb-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm font-medium">{{ item.product.name }}/{{ item.product.code }}</span>
|
||||||
|
<ion-badge :color="getStatusColor(item.status)" class="text-[10px] px-1.5 py-0.5">
|
||||||
|
{{ getStatusText(item.status) }}
|
||||||
|
</ion-badge>
|
||||||
|
</div>
|
||||||
|
<!-- operations -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-2 mb-2">
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span class="text-(--ion-text-color-step-400)">单价</span>
|
||||||
|
<span class="text-(--ion-text-color) font-medium">{{ Number(item.quantity).toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span class="text-(--ion-text-color-step-400)">数量</span>
|
||||||
|
<span class="text-(--ion-text-color) font-medium">{{ Number(item.quantity).toFixed(2) }}份</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span class="text-(--ion-text-color-step-400)">阶段</span>
|
||||||
|
<span class="text-(--ion-text-color) font-medium">{{ item.edition.editionName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span class="text-(--ion-text-color-step-400)">总额</span>
|
||||||
|
<span class="text-(--ion-text-color) font-medium">{{ item.subscriptionOrder.totalAmount }} USDT</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span class="text-(--ion-text-color-step-400)">总分红</span>
|
||||||
|
<span class="text-(--ion-text-color) font-medium">{{ Number(item.totalDividendReceived).toFixed(2) }} USDT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-2">
|
||||||
|
<span class="text-[11px] text-(--ion-text-color-step-500)">{{ useDateFormat(item.createdAt, 'YYYY/MM/DD HH:mm') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang='css' scoped></style>
|
<style lang='css' scoped>
|
||||||
|
ion-grid {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-grid:active {
|
||||||
|
background-color: var(--ion-color-light);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user