feat: 添加 SubscribeRwa 组件,优化 RWA 申购功能并更新相关类型定义

This commit is contained in:
2025-12-19 22:02:52 +07:00
parent b91f371115
commit c3321bfbf1
6 changed files with 96 additions and 12 deletions

2
auto-imports.d.ts vendored
View File

@@ -270,6 +270,7 @@ declare global {
const useStorage: typeof import('@vueuse/core').useStorage
const useStorageAsync: typeof import('@vueuse/core').useStorageAsync
const useStyleTag: typeof import('@vueuse/core').useStyleTag
const useSubscribeModal: typeof import('./src/composables/useSubscribeModal').useSubscribeModal
const useSupported: typeof import('@vueuse/core').useSupported
const useSwipe: typeof import('@vueuse/core').useSwipe
const useTemplateRef: typeof import('vue').useTemplateRef
@@ -611,6 +612,7 @@ declare module 'vue' {
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
readonly useSubscribeModal: UnwrapRef<typeof import('./src/composables/useSubscribeModal')['useSubscribeModal']>
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>

4
components.d.ts vendored
View File

@@ -35,6 +35,7 @@ declare module 'vue' {
IonIcon: typeof import('@ionic/vue')['IonIcon']
IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
IonInfiniteScrollContent: typeof import('@ionic/vue')['IonInfiniteScrollContent']
IonInput: typeof import('@ionic/vue')['IonInput']
IonInputOtp: typeof import('@ionic/vue')['IonInputOtp']
IonItem: typeof import('@ionic/vue')['IonItem']
IonLabel: typeof import('@ionic/vue')['IonLabel']
@@ -61,6 +62,7 @@ declare module 'vue' {
QrScanner: typeof import('./src/components/qr-scanner/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SubscribeRwa: typeof import('./src/components/subscribe-rwa/index.vue')['default']
UiAvatar: typeof import('./src/components/ui/avatar/index.vue')['default']
UiCollapse: typeof import('./src/components/ui/collapse/index.vue')['default']
UiDatetime: typeof import('./src/components/ui/datetime/index.vue')['default']
@@ -99,6 +101,7 @@ declare global {
const IonIcon: typeof import('@ionic/vue')['IonIcon']
const IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll']
const IonInfiniteScrollContent: typeof import('@ionic/vue')['IonInfiniteScrollContent']
const IonInput: typeof import('@ionic/vue')['IonInput']
const IonInputOtp: typeof import('@ionic/vue')['IonInputOtp']
const IonItem: typeof import('@ionic/vue')['IonItem']
const IonLabel: typeof import('@ionic/vue')['IonLabel']
@@ -125,6 +128,7 @@ declare global {
const QrScanner: typeof import('./src/components/qr-scanner/index.vue')['default']
const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView']
const SubscribeRwa: typeof import('./src/components/subscribe-rwa/index.vue')['default']
const UiAvatar: typeof import('./src/components/ui/avatar/index.vue')['default']
const UiCollapse: typeof import('./src/components/ui/collapse/index.vue')['default']
const UiDatetime: typeof import('./src/components/ui/datetime/index.vue')['default']

View File

@@ -0,0 +1,45 @@
<script lang='ts' setup>
const props = defineProps<{
unitPrice: number;
}>();
const walletStore = useWalletStore();
const { balances } = storeToRefs(walletStore);
const currentUSDTBalance = computed(() => balances.value[0].available);
const maxSubscribeNumber = computed(() => {
return Math.floor(Number(currentUSDTBalance.value) / props.unitPrice);
});
</script>
<template>
<ion-content class="ion-padding" :scroll-y="false">
<div class="space-y-5 mt-3">
<div>申购RWA</div>
<ion-input :placeholder="`最大可申购数量: ${maxSubscribeNumber}`" type="number" :max="maxSubscribeNumber" />
<div class="mt-4 text-sm color-(--ion-text-color-secondary)">
单价: ${{ unitPrice }}
</div>
<div class="mt-4 text-sm color-(--ion-text-color-secondary)">
可用余额: ${{ Number(currentUSDTBalance) }}
</div>
<ion-button expand="block" class="mt-6 ui-button" color="primary">
确认申购
</ion-button>
</div>
</ion-content>
</template>
<style scoped>
@reference "tailwindcss";
ion-input {
@apply rounded-lg;
--padding-start: 16px;
--padding-end: 16px;
border: 1px solid var(--ion-color-light-shade);
}
</style>

View File

@@ -47,26 +47,34 @@ const { data } = safeClient(client.api.rwa.subscription.available_editions({ edi
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<div class="label">
单价
</div>
<div>${{ formatAmount(data?.unitPrice) }}</div>
</ion-col>
<ion-col>
<div class="label">
总发行量
</div>
<div>{{ Number(data?.totalSupply) }}</div>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<div class="label">
每人限量
</div>
<div>{{ Number(data?.perUserLimit) }}</div>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<div class="label">
发行时间
</div>
<div>{{ useDateFormat(data?.launchDate || '', 'YYYY/MM/DD') }}</div>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<div class="label">
认购截止时间
@@ -90,12 +98,13 @@ const { data } = safeClient(client.api.rwa.subscription.available_editions({ edi
<ion-footer>
<ion-toolbar class="ion-padding-horizontal ui-toolbar">
<div class="flex justify-center my-2 gap-5">
<ion-button shape="round" expand="block" color="success">
<ion-button id="open-modal" shape="round" expand="block" color="success">
申购
</ion-button>
<ion-button shape="round" expand="block" color="danger">
赎回
</ion-button>
<ion-modal trigger="open-modal" :initial-breakpoint="0.4" :breakpoints="[0, 0.4]">
<subscribe-rwa :unit-price="Number(data?.unitPrice || 0)" />
</ion-modal>
</div>
</ion-toolbar>
</ion-footer>

View File

@@ -1,11 +1,36 @@
<script lang='ts' setup>
import type { Animation } from "@ionic/vue";
import { createAnimation } from "@ionic/vue";
import { cartOutline } from "ionicons/icons";
const model = defineModel<"sale" | "buy" | null>();
const operationInst = useTemplateRef<HTMLDivElement>("operationInst");
const targetIsVisible = useElementVisibility(operationInst);
const animation = ref<Animation | null>(null);
watch(targetIsVisible, (visible) => {
if (visible) {
animation.value?.easing("ease-in")
.fromTo("opacity", "0", "1")
.play();
}
else {
animation.value?.easing("ease-out")
.fromTo("opacity", "1", "0")
.play();
}
});
onMounted(() => {
animation.value = createAnimation()
.addElement(operationInst.value!)
.duration(500);
});
</script>
<template>
<div v-if="model === null" class="operation-container">
<div v-if="model === null" ref="operationInst" class="operation-container">
<div class="wrapper" />
<div class="box">

View File

@@ -3,18 +3,17 @@ import { cartOutline } from "ionicons/icons";
import OperationWrapper from "./components/operation-wrapper.vue";
const { t } = useI18n();
const current = ref<"sale" | "buy" | null>(null);
</script>
<template>
<IonPage>
<IonHeader>
<IonToolbar>
<IonHeader class="ion-no-border">
<ion-toolbar class="ui-toolbar">
<IonTitle>{{ t('tabs.trade') }}</IonTitle>
</IonToolbar>
</ion-toolbar>
</IonHeader>
<IonContent :fullscreen="true">
<OperationWrapper v-model="current" />
<!-- <OperationWrapper v-model="current" /> -->
</IonContent>
</IonPage>
</template>