feat: 实现发行申请提交
This commit is contained in:
127
src/views/issue/issuing-apply/base.vue
Normal file
127
src/views/issue/issuing-apply/base.vue
Normal 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>
|
||||
27
src/views/issue/issuing-apply/done.vue
Normal file
27
src/views/issue/issuing-apply/done.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
173
src/views/issue/issuing-apply/issue-period.vue
Normal file
173
src/views/issue/issuing-apply/issue-period.vue
Normal 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>
|
||||
Reference in New Issue
Block a user