feat: 新增代币化管理功能,包括代币化产品的添加、编辑和列表展示
This commit is contained in:
214
src/views/rwa/product/components/tokenization.vue
Normal file
214
src/views/rwa/product/components/tokenization.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import { type FormInst, type FormRules, NInput, useDialog } from 'naive-ui';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'RwaProductTokenization' });
|
||||
|
||||
type Data = Treaty.Data<typeof client.api.admin.rwa.issuance.products.get>['data'][number];
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.rwa.tokenization_schema.issue.post>;
|
||||
|
||||
const props = defineProps<{
|
||||
data: Data;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formRef = useTemplateRef<FormInst | null>('formRef');
|
||||
|
||||
const form = ref<Body>({
|
||||
productId: props.data.id,
|
||||
assetCode: props.data.code,
|
||||
totalSupply: '100',
|
||||
lockOptions: [
|
||||
{
|
||||
months: 6,
|
||||
rewardRate: '0.05' // 奖励率(如 0.05 = 5%)
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
assetCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择资产代码',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
totalSupply: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入发行总量',
|
||||
trigger: ['blur', 'change']
|
||||
},
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
if (Number(value) < 1) {
|
||||
return new Error('发行总量必须大于0');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function addLockOption() {
|
||||
(form.value.lockOptions ||= []).push({
|
||||
months: 12,
|
||||
rewardRate: '0.1'
|
||||
});
|
||||
}
|
||||
|
||||
function removeLockOption(index: number) {
|
||||
if (form.value.lockOptions && form.value.lockOptions.length > 1) {
|
||||
form.value.lockOptions.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
await safeClient(() =>
|
||||
client.api.admin.rwa.tokenization_schema.issue.post({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
window.$message?.success('代币化产品发行请求已提交。');
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="auto"
|
||||
label-placement="left"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NGrid :cols="1">
|
||||
<NFormItemGi label="资产代码" path="assetCode">
|
||||
<NInput v-model:value="form.assetCode" placeholder="请输入资产代码" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi label="发行总量" path="totalSupply">
|
||||
<NInputNumber
|
||||
:min="1"
|
||||
:step="100"
|
||||
:value="Number(form.totalSupply)"
|
||||
@update:value="val => (form.totalSupply = String(val))"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NDivider />
|
||||
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<span class="text-base font-medium">锁仓选项</span>
|
||||
<NButton secondary type="primary" @click="addLockOption">
|
||||
<template #icon>
|
||||
<icon-ic-round-plus class="text-icon" />
|
||||
</template>
|
||||
添加选项
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<NSpace vertical :size="16">
|
||||
<NCard
|
||||
v-for="(option, index) in form.lockOptions"
|
||||
:key="index"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
class="rounded-8px"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>选项 {{ index + 1 }}</span>
|
||||
<NButton
|
||||
v-if="form.lockOptions && form.lockOptions.length > 1"
|
||||
text
|
||||
type="error"
|
||||
@click="removeLockOption(index)"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-ic-round-delete class="text-icon" />
|
||||
</template>
|
||||
</NButton>
|
||||
</div>
|
||||
</template>
|
||||
<NGrid :cols="2" :x-gap="16">
|
||||
<NFormItemGi
|
||||
label="锁仓月数"
|
||||
:path="`lockOptions[${index}].months`"
|
||||
:rule="{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入锁仓月数',
|
||||
trigger: ['blur', 'change']
|
||||
}"
|
||||
>
|
||||
<NInputNumber
|
||||
v-model:value="option.months"
|
||||
:min="1"
|
||||
:max="120"
|
||||
:step="1"
|
||||
placeholder="请输入锁仓月数"
|
||||
class="w-full"
|
||||
>
|
||||
<template #suffix>月</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
label="奖励率"
|
||||
:path="`lockOptions[${index}].rewardRate`"
|
||||
:rule="{
|
||||
required: true,
|
||||
validator: (_rule: any, value: string) => {
|
||||
const num = Number(value);
|
||||
if (!value || value === '') {
|
||||
return new Error('请输入奖励率');
|
||||
}
|
||||
if (Number.isNaN(num)) {
|
||||
return new Error('请输入有效的数字');
|
||||
}
|
||||
if (num < 0 || num > 1) {
|
||||
return new Error('奖励率必须在0-1之间');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}"
|
||||
>
|
||||
<NInputNumber
|
||||
:value="Number(option.rewardRate)"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.01"
|
||||
:precision="4"
|
||||
placeholder="请输入奖励率"
|
||||
class="w-full"
|
||||
@update:value="val => (option.rewardRate = String(val))"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="text-12px">{{ (Number(option.rewardRate) * 100).toFixed(2) }}%</span>
|
||||
</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
|
||||
<NSpace justify="end" class="mt-4">
|
||||
<NButton @click="$emit('close')">取消</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">立即代币化</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -8,6 +8,7 @@ import { RwaStatusEnum } from '@/enum';
|
||||
import Add from './components/add.vue';
|
||||
import Edit from './components/edit.vue';
|
||||
import Editions from './components/editions.vue';
|
||||
import Tokenization from './components/tokenization.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
@@ -214,21 +215,18 @@ const filterColumns: TableFilterColumns = [
|
||||
];
|
||||
|
||||
function handleTokenization(row: any) {
|
||||
dialog.create({
|
||||
const dialogInstance = dialog.create({
|
||||
title: '代币化产品',
|
||||
content: '确认将该产品代币化吗?',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.rwa.tokenization.issue.post({
|
||||
assetCode: row.code,
|
||||
productId: row.id,
|
||||
totalSupply: row.estimatedValue
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
content: () =>
|
||||
h(Tokenization, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
70
src/views/tokenization/product/index.vue
Normal file
70
src/views/tokenization/product/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts" setup>
|
||||
import { useTemplateRef } from 'vue';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.rwa.tokenization_schema.configs.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
key: 'selection',
|
||||
title: '序号',
|
||||
type: 'selection',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '资产代码',
|
||||
key: 'assetCode'
|
||||
},
|
||||
{
|
||||
title: '发行总量',
|
||||
key: 'totalSupply'
|
||||
},
|
||||
{
|
||||
title: '锁定选项',
|
||||
key: 'lockOptions',
|
||||
render: (row: any) => {
|
||||
return row.lockOptions
|
||||
.map((option: any) => `锁定${option.months}个月,奖励率${(Number(option.rewardRate) * 100).toFixed(2)}%`)
|
||||
.join('; ');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '关联RWA产品',
|
||||
key: 'productId'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt'
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
:columns="columns"
|
||||
:filter-columns="filterColumns"
|
||||
:fetch-data="fetchData"
|
||||
:header-operations="{
|
||||
add: false,
|
||||
refresh: true,
|
||||
columns: true
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
Reference in New Issue
Block a user