feat: 实现发行申请提交

This commit is contained in:
2025-12-16 03:49:39 +07:00
parent 831bc78ec5
commit 2d1454d08e
21 changed files with 870 additions and 40 deletions

View File

@@ -0,0 +1,127 @@
<script lang='ts' setup>
import type { GenericObject } from "vee-validate";
import type { RwaIssuanceCategoriesData, RwaIssuanceProductBody } from "@/api/types";
import { toTypedSchema } from "@vee-validate/yup";
import { ErrorMessage, Field, FieldArray, Form } from "vee-validate";
import { useI18n } from "vue-i18n";
import * as yup from "yup";
import { client, safeClient } from "@/api";
const props = defineProps<{
initialData: RwaIssuanceProductBody["product"];
categories: RwaIssuanceCategoriesData;
}>();
const emit = defineEmits<{
(e: "next", values: GenericObject): void;
}>();
const { t } = useI18n();
const schema = toTypedSchema(
yup.object({
name: yup.string().required(t("asset.issue.apply.validation.nameRequired")),
code: yup.string().required(t("asset.issue.apply.validation.codeRequired")),
categoryId: yup.string().required(t("asset.issue.apply.validation.categoryRequired")),
}),
);
function handleSubmit(values: GenericObject) {
emit("next", values);
}
</script>
<template>
<Form
:validation-schema="schema" :initial-values="initialData"
@submit="handleSubmit"
>
<div class="space-y-5">
<div>
<Field name="name" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
:label="t('asset.issue.apply.productName')"
:placeholder="t('asset.issue.apply.enterProductName')"
/>
</template>
</Field>
<ErrorMessage name="name" />
</div>
<div>
<Field name="code" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
:label="t('asset.issue.apply.productCode')"
:placeholder="t('asset.issue.apply.enterProductCode')"
/>
</template>
</Field>
<ErrorMessage name="code" />
</div>
<div>
<Field name="categoryId" type="text">
<template #default="{ field }">
<ion-select class="ui-select" interface="action-sheet" toggle-icon="" v-bind="field" :label="t('asset.issue.apply.productType')" :placeholder="t('asset.issue.apply.chooseProductType')">
<ion-select-option v-for="item in categories" :key="item.id" :value="item.id">
{{ item.name }}
</ion-select-option>
</ion-select>
</template>
</Field>
<ErrorMessage name="categoryId" />
</div>
<div>
<Field name="estimatedValue" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="text"
inputmode="numeric"
:label="t('asset.issue.apply.productValue')"
:placeholder="t('asset.issue.apply.enterProductValue')"
/>
</template>
</Field>
<ErrorMessage name="estimatedValue" />
</div>
<div>
<Field name="proofDocuments" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
:label="t('asset.issue.apply.assetProof')"
:placeholder="t('asset.issue.apply.enterAssetProof')"
/>
</template>
</Field>
<ErrorMessage name="proofDocuments" />
</div>
<div>
<Field name="totalSupplyLimit" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.totalSupplyLimit')"
:placeholder="t('asset.issue.apply.enterTotalSupplyLimit')"
/>
</template>
</Field>
<ErrorMessage name="totalSupplyLimit" />
</div>
<ion-button type="submit" expand="block">
{{ t('asset.issue.apply.next') }}
</ion-button>
</div>
</Form>
</template>
<style lang='css' scoped></style>

View File

@@ -0,0 +1,27 @@
<script lang='ts' setup>
import { ribbonOutline } from "ionicons/icons";
const { t } = useI18n();
</script>
<template>
<div>
<ui-result
:icon="ribbonOutline"
:title="t('asset.issue.apply.done.title')"
:description="t('asset.issue.apply.done.description')"
>
<template #extra>
<ion-button
expand="block"
color="primary"
router-link="/issue/issuance-products"
>
{{ t('asset.issue.apply.done.viewProducts') }}
</ion-button>
</template>
</ui-result>
</div>
</template>
<style lang='css' scoped></style>

View File

@@ -1,14 +1,62 @@
<script lang='ts' setup>
import type { GenericObject } from "vee-validate";
import type { RwaIssuanceProductBody } from "@/api/types";
import { useRouteQuery } from "@vueuse/router";
import { useI18n } from "vue-i18n";
import { client, safeClient } from "@/api";
import Base from "./base.vue";
import Done from "./done.vue";
import IssuePeriod from "./issue-period.vue";
const { t } = useI18n();
const now = useNow();
const { data: categories = [] } = await safeClient(client.api.rwa.issuance.categories.get());
const step = useRouteQuery<number>("step", 1, { transform: v => Number(v), mode: "push" });
const initialData: RwaIssuanceProductBody = {
product: {
name: "",
code: "",
categoryId: categories!.length > 0 ? categories![0].id : "",
},
editions: [
{
editionName: "",
launchDate: useDateFormat(now.value, "YYYY/MM/DD").value,
perUserLimit: "",
subscriptionDeadline: useDateFormat(now.value, "YYYY/MM/DD").value,
totalSupply: "",
unitPrice: "",
dividendRate: "",
},
],
};
const form = useStorage<RwaIssuanceProductBody>("issuing-apply-form", { ...initialData });
function handleNext(values: GenericObject) {
form.value.product = { ...values as any };
step.value = 2;
}
async function handleSubmit(editions: RwaIssuanceProductBody["editions"]) {
form.value.editions = editions;
await client.api.rwa.issuance.products.bundle.post(form.value);
form.value = { ...initialData };
step.value = 3;
}
</script>
<template>
<IonPage>
<ion-header>
<ion-toolbar class="ui-toolbar">
<ion-title>Issuing Apply</ion-title>
<ion-title>{{ t('asset.issue.apply.title') }}</ion-title>
</ion-toolbar>
</ion-header>
<IonContent :fullscreen="true" class="ion-padding" />
<IonContent :fullscreen="true" class="ion-padding">
<Base v-if="step === 1" :initial-data="form.product" :categories="categories || []" @next="handleNext" />
<IssuePeriod v-else-if="step === 2" :initial-data="form.editions" @submit="handleSubmit" />
<Done v-else />
</IonContent>
</IonPage>
</template>

View File

@@ -0,0 +1,173 @@
<script lang='ts' setup>
import type { GenericObject } from "vee-validate";
import type { RwaIssuanceProductBody } from "@/api/types";
import { toTypedSchema } from "@vee-validate/yup";
import { addOutline, removeOutline } from "ionicons/icons";
import { ErrorMessage, Field, FieldArray, Form } from "vee-validate";
import { useI18n } from "vue-i18n";
import * as yup from "yup";
const props = defineProps<{
initialData: RwaIssuanceProductBody["editions"];
}>();
const emit = defineEmits<{
(e: "submit", values: RwaIssuanceProductBody["editions"]): void;
}>();
const { t } = useI18n();
const now = useNow();
const initialIssuePeriod: RwaIssuanceProductBody["editions"][0] = {
editionName: "",
launchDate: useDateFormat(now.value, "YYYY/MM/DD").value,
perUserLimit: "",
subscriptionDeadline: useDateFormat(now.value, "YYYY/MM/DD").value,
totalSupply: "",
unitPrice: "",
dividendRate: "",
};
const initialValues = ref({
editions: props.initialData || [{ ...initialIssuePeriod }],
});
const schema = toTypedSchema(yup.object({
editions: yup.array().of(
yup.object({
editionName: yup.string().required(t("asset.issue.apply.validation.editionNameRequired")),
launchDate: yup.string().required(t("asset.issue.apply.validation.launchDateRequired")),
perUserLimit: yup.string().required(t("asset.issue.apply.validation.perUserLimitRequired")),
totalSupply: yup.string().required(t("asset.issue.apply.validation.totalSupplyRequired")),
subscriptionDeadline: yup.string().required(t("asset.issue.apply.validation.subscriptionDeadlineRequired")),
unitPrice: yup.string().required(t("asset.issue.apply.validation.unitPriceRequired")),
dividendRate: yup.string().required(t("asset.issue.apply.validation.dividendRateRequired")),
}),
),
}));
function handleSubmit(values: GenericObject) {
emit("submit", values.editions);
}
</script>
<template>
<Form :validation-schema="schema" :initial-values="initialValues" class="space-y-5" @submit="handleSubmit">
<FieldArray v-slot="{ fields, push, remove }" name="editions">
<ui-collapse v-for="(entry, idx) in fields" :key="entry.key" :title="t('asset.issue.apply.issuePeriodIndex', { index: idx + 1 })" size="small">
<div>
<Field :name="`editions[${idx}].editionName`">
<template #default="{ field }">
<ui-input-label
v-bind="field"
:label="t('asset.issue.apply.editionName')"
:placeholder="t('asset.issue.apply.enterEditionName')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].editionName`" />
</div>
<div>
<Field :name="`editions[${idx}].launchDate`">
<template #default="{ field }">
<ui-datetime
v-bind="field"
:label="t('asset.issue.apply.launchDate')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].launchDate`" />
</div>
<div>
<Field :name="`editions[${idx}].perUserLimit`" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.perUserLimit')"
:placeholder="t('asset.issue.apply.enterPerUserLimit')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].perUserLimit`" />
</div>
<div>
<Field :name="`editions[${idx}].totalSupply`" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.totalSupply')"
:placeholder="t('asset.issue.apply.enterTotalSupply')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].totalSupply`" />
</div>
<div>
<Field :name="`editions[${idx}].subscriptionDeadline`">
<template #default="{ field }">
<ui-datetime
v-bind="field"
:label="t('asset.issue.apply.subscriptionDeadline')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].subscriptionDeadline`" />
</div>
<div>
<Field :name="`editions[${idx}].unitPrice`" type="text">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.unitPrice')"
:placeholder="t('asset.issue.apply.enterUnitPrice')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].unitPrice`" />
</div>
<div>
<Field :name="`editions[${idx}].dividendRate`" type="number">
<template #default="{ field }">
<ui-input-label
v-bind="field"
type="number"
inputmode="numeric"
:label="t('asset.issue.apply.dividendRate')"
:placeholder="t('asset.issue.apply.enterDividendRate')"
/>
</template>
</Field>
<ErrorMessage :name="`editions[${idx}].dividendRate`" />
</div>
<ion-button color="tertiary" size="small" class="ui-button" @click="remove(idx)">
<ion-icon slot="start" :icon="removeOutline" />
<span>{{ t('asset.issue.apply.removeItem') }}</span>
</ion-button>
</ui-collapse>
<ion-button expand="block" color="tertiary" fill="outline" @click="push({ ...initialIssuePeriod })">
<ion-icon slot="icon-only" :icon="addOutline" />
<span>{{ t('asset.issue.apply.addStep') }}</span>
</ion-button>
</FieldArray>
<ion-button expand="block" @click="$router.back()">
{{ t('asset.issue.apply.back') }}
</ion-button>
<ion-button expand="block" type="submit">
{{ t('asset.issue.apply.submit') }}
</ion-button>
</Form>
</template>
<style lang='css' scoped></style>