feat: init
This commit is contained in:
@@ -228,29 +228,7 @@ const local: App.I18n.Schema = {
|
||||
404: 'Page Not Found',
|
||||
500: 'Server Error',
|
||||
'iframe-page': 'Iframe',
|
||||
home: 'Home',
|
||||
deposit: 'Deposit',
|
||||
deposit_fiat: 'Fiat Deposit',
|
||||
withdraw: 'Withdraw',
|
||||
withdraw_fiat: 'Fiat Withdraw',
|
||||
rwa_producttype: 'Product Type',
|
||||
rwa: 'RWA Management',
|
||||
rwa_product: 'RWA Product',
|
||||
rwa_subscribe: 'RWA Subscribe',
|
||||
user: 'User Management',
|
||||
bank: 'User Bank',
|
||||
user_bankcard: 'User Bank Card',
|
||||
user_list: 'User List',
|
||||
transfer: 'Transfer',
|
||||
withdraw_approved: 'Approved Withdraw',
|
||||
asset: 'Asset Management',
|
||||
'tokenization_trading-pairs': 'Trading Pairs Management',
|
||||
tokenization: 'Tokenization Management',
|
||||
tokenization_product: 'Tokenization Product',
|
||||
notification: 'Notification Management',
|
||||
news: 'News Management',
|
||||
robot: 'Robot Management',
|
||||
robot_spot: 'Spot Robot'
|
||||
home: 'Home'
|
||||
},
|
||||
page: {
|
||||
login: {
|
||||
|
||||
@@ -224,29 +224,7 @@ const local: App.I18n.Schema = {
|
||||
404: '页面不存在',
|
||||
500: '服务器错误',
|
||||
'iframe-page': '外链页面',
|
||||
home: '首页',
|
||||
deposit: '充值管理',
|
||||
deposit_fiat: '法币充值',
|
||||
withdraw: '提现管理',
|
||||
withdraw_fiat: '法币提现',
|
||||
withdraw_approved: '已批准提现列表',
|
||||
rwa: 'RWA管理',
|
||||
rwa_product: 'RWA产品',
|
||||
rwa_producttype: 'RWA产品类型',
|
||||
rwa_subscribe: 'RWA申购记录',
|
||||
user: '用户管理',
|
||||
user_list: '用户列表',
|
||||
user_bankcard: '用户银行卡',
|
||||
bank: '银行管理',
|
||||
transfer: '转账记录',
|
||||
asset: '资产管理',
|
||||
'tokenization_trading-pairs': '交易对管理',
|
||||
tokenization: '代币化管理',
|
||||
tokenization_product: '代币化产品',
|
||||
notification: '通知管理',
|
||||
news: '新闻管理',
|
||||
robot: '机器人管理',
|
||||
robot_spot: '现货机器人'
|
||||
home: '首页'
|
||||
},
|
||||
page: {
|
||||
login: {
|
||||
|
||||
@@ -20,21 +20,5 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
||||
500: () => import("@/views/_builtin/500/index.vue"),
|
||||
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
||||
login: () => import("@/views/_builtin/login/index.vue"),
|
||||
asset: () => import("@/views/asset/index.vue"),
|
||||
bank: () => import("@/views/bank/index.vue"),
|
||||
deposit_fiat: () => import("@/views/deposit/fiat/index.vue"),
|
||||
home: () => import("@/views/home/index.vue"),
|
||||
news: () => import("@/views/news/index.vue"),
|
||||
notification: () => import("@/views/notification/index.vue"),
|
||||
robot_spot: () => import("@/views/robot/spot/index.vue"),
|
||||
rwa_product: () => import("@/views/rwa/product/index.vue"),
|
||||
rwa_producttype: () => import("@/views/rwa/productType/index.vue"),
|
||||
rwa_subscribe: () => import("@/views/rwa/subscribe/index.vue"),
|
||||
tokenization_product: () => import("@/views/tokenization/product/index.vue"),
|
||||
"tokenization_trading-pairs": () => import("@/views/tokenization/trading-pairs/index.vue"),
|
||||
transfer: () => import("@/views/transfer/index.vue"),
|
||||
user_bankcard: () => import("@/views/user/bankcard/index.vue"),
|
||||
user_list: () => import("@/views/user/list/index.vue"),
|
||||
withdraw_approved: () => import("@/views/withdraw/approved/index.vue"),
|
||||
withdraw_fiat: () => import("@/views/withdraw/fiat/index.vue"),
|
||||
};
|
||||
|
||||
@@ -39,56 +39,13 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
hideInMenu: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'asset',
|
||||
path: '/asset',
|
||||
component: 'layout.base$view.asset',
|
||||
meta: {
|
||||
title: 'asset',
|
||||
i18nKey: 'route.asset',
|
||||
order: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'bank',
|
||||
path: '/bank',
|
||||
component: 'layout.base$view.bank',
|
||||
meta: {
|
||||
title: 'bank',
|
||||
i18nKey: 'route.bank',
|
||||
order: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'deposit',
|
||||
path: '/deposit',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'deposit',
|
||||
i18nKey: 'route.deposit',
|
||||
order: 5
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'deposit_fiat',
|
||||
path: '/deposit/fiat',
|
||||
component: 'view.deposit_fiat',
|
||||
meta: {
|
||||
title: 'deposit_fiat',
|
||||
i18nKey: 'route.deposit_fiat'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'home',
|
||||
path: '/home',
|
||||
component: 'layout.base$view.home',
|
||||
meta: {
|
||||
title: 'home',
|
||||
i18nKey: 'route.home',
|
||||
icon: 'mdi:monitor-dashboard',
|
||||
order: 1
|
||||
i18nKey: 'route.home'
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -115,187 +72,5 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
constant: true,
|
||||
hideInMenu: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'news',
|
||||
path: '/news',
|
||||
component: 'layout.base$view.news',
|
||||
meta: {
|
||||
title: 'news',
|
||||
i18nKey: 'route.news',
|
||||
order: 8
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'notification',
|
||||
path: '/notification',
|
||||
component: 'layout.base$view.notification',
|
||||
meta: {
|
||||
title: 'notification',
|
||||
i18nKey: 'route.notification',
|
||||
order: 9
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'robot',
|
||||
path: '/robot',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'robot',
|
||||
i18nKey: 'route.robot',
|
||||
order: 7
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'robot_spot',
|
||||
path: '/robot/spot',
|
||||
component: 'view.robot_spot',
|
||||
meta: {
|
||||
title: 'robot_spot',
|
||||
i18nKey: 'route.robot_spot'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'rwa',
|
||||
path: '/rwa',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'rwa',
|
||||
i18nKey: 'route.rwa',
|
||||
order: 5
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'rwa_product',
|
||||
path: '/rwa/product',
|
||||
component: 'view.rwa_product',
|
||||
meta: {
|
||||
title: 'rwa_product',
|
||||
i18nKey: 'route.rwa_product'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'rwa_producttype',
|
||||
path: '/rwa/producttype',
|
||||
component: 'view.rwa_producttype',
|
||||
meta: {
|
||||
title: 'rwa_producttype',
|
||||
i18nKey: 'route.rwa_producttype'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'rwa_subscribe',
|
||||
path: '/rwa/subscribe',
|
||||
component: 'view.rwa_subscribe',
|
||||
meta: {
|
||||
title: 'rwa_subscribe',
|
||||
i18nKey: 'route.rwa_subscribe'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'tokenization',
|
||||
path: '/tokenization',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: '代币化管理',
|
||||
i18nKey: 'route.tokenization',
|
||||
order: 6
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'tokenization_product',
|
||||
path: '/tokenization/product',
|
||||
component: 'view.tokenization_product',
|
||||
meta: {
|
||||
title: '代币化产品',
|
||||
i18nKey: 'route.tokenization_product'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'tokenization_trading-pairs',
|
||||
path: '/tokenization/trading-pairs',
|
||||
component: 'view.tokenization_trading-pairs',
|
||||
meta: {
|
||||
title: 'tokenization_trading-pairs',
|
||||
i18nKey: 'route.tokenization_trading-pairs'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'transfer',
|
||||
path: '/transfer',
|
||||
component: 'layout.base$view.transfer',
|
||||
meta: {
|
||||
title: 'transfer',
|
||||
i18nKey: 'route.transfer',
|
||||
order: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'user',
|
||||
path: '/user',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'user',
|
||||
i18nKey: 'route.user',
|
||||
order: 4
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'user_bankcard',
|
||||
path: '/user/bankcard',
|
||||
component: 'view.user_bankcard',
|
||||
meta: {
|
||||
title: 'user_bankcard',
|
||||
i18nKey: 'route.user_bankcard'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'user_list',
|
||||
path: '/user/list',
|
||||
component: 'view.user_list',
|
||||
meta: {
|
||||
title: 'user_list',
|
||||
i18nKey: 'route.user_list'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'withdraw',
|
||||
path: '/withdraw',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'withdraw',
|
||||
i18nKey: 'route.withdraw',
|
||||
order: 3
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'withdraw_approved',
|
||||
path: '/withdraw/approved',
|
||||
component: 'view.withdraw_approved',
|
||||
meta: {
|
||||
title: 'withdraw_approved',
|
||||
i18nKey: 'route.withdraw_approved',
|
||||
order: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'withdraw_fiat',
|
||||
path: '/withdraw/fiat',
|
||||
component: 'view.withdraw_fiat',
|
||||
meta: {
|
||||
title: 'withdraw_fiat',
|
||||
i18nKey: 'route.withdraw_fiat',
|
||||
order: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -166,31 +166,9 @@ const routeMap: RouteMap = {
|
||||
"403": "/403",
|
||||
"404": "/404",
|
||||
"500": "/500",
|
||||
"asset": "/asset",
|
||||
"bank": "/bank",
|
||||
"deposit": "/deposit",
|
||||
"deposit_fiat": "/deposit/fiat",
|
||||
"home": "/home",
|
||||
"iframe-page": "/iframe-page/:url",
|
||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
|
||||
"news": "/news",
|
||||
"notification": "/notification",
|
||||
"robot": "/robot",
|
||||
"robot_spot": "/robot/spot",
|
||||
"rwa": "/rwa",
|
||||
"rwa_product": "/rwa/product",
|
||||
"rwa_producttype": "/rwa/producttype",
|
||||
"rwa_subscribe": "/rwa/subscribe",
|
||||
"tokenization": "/tokenization",
|
||||
"tokenization_product": "/tokenization/product",
|
||||
"tokenization_trading-pairs": "/tokenization/trading-pairs",
|
||||
"transfer": "/transfer",
|
||||
"user": "/user",
|
||||
"user_bankcard": "/user/bankcard",
|
||||
"user_list": "/user/list",
|
||||
"withdraw": "/withdraw",
|
||||
"withdraw_approved": "/withdraw/approved",
|
||||
"withdraw_fiat": "/withdraw/fiat"
|
||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
49
src/typings/elegant-router.d.ts
vendored
49
src/typings/elegant-router.d.ts
vendored
@@ -20,31 +20,9 @@ declare module "@elegant-router/types" {
|
||||
"403": "/403";
|
||||
"404": "/404";
|
||||
"500": "/500";
|
||||
"asset": "/asset";
|
||||
"bank": "/bank";
|
||||
"deposit": "/deposit";
|
||||
"deposit_fiat": "/deposit/fiat";
|
||||
"home": "/home";
|
||||
"iframe-page": "/iframe-page/:url";
|
||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
|
||||
"news": "/news";
|
||||
"notification": "/notification";
|
||||
"robot": "/robot";
|
||||
"robot_spot": "/robot/spot";
|
||||
"rwa": "/rwa";
|
||||
"rwa_product": "/rwa/product";
|
||||
"rwa_producttype": "/rwa/producttype";
|
||||
"rwa_subscribe": "/rwa/subscribe";
|
||||
"tokenization": "/tokenization";
|
||||
"tokenization_product": "/tokenization/product";
|
||||
"tokenization_trading-pairs": "/tokenization/trading-pairs";
|
||||
"transfer": "/transfer";
|
||||
"user": "/user";
|
||||
"user_bankcard": "/user/bankcard";
|
||||
"user_list": "/user/list";
|
||||
"withdraw": "/withdraw";
|
||||
"withdraw_approved": "/withdraw/approved";
|
||||
"withdraw_fiat": "/withdraw/fiat";
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -79,20 +57,9 @@ declare module "@elegant-router/types" {
|
||||
| "403"
|
||||
| "404"
|
||||
| "500"
|
||||
| "asset"
|
||||
| "bank"
|
||||
| "deposit"
|
||||
| "home"
|
||||
| "iframe-page"
|
||||
| "login"
|
||||
| "news"
|
||||
| "notification"
|
||||
| "robot"
|
||||
| "rwa"
|
||||
| "tokenization"
|
||||
| "transfer"
|
||||
| "user"
|
||||
| "withdraw"
|
||||
>;
|
||||
|
||||
/**
|
||||
@@ -114,23 +81,7 @@ declare module "@elegant-router/types" {
|
||||
| "500"
|
||||
| "iframe-page"
|
||||
| "login"
|
||||
| "asset"
|
||||
| "bank"
|
||||
| "deposit_fiat"
|
||||
| "home"
|
||||
| "news"
|
||||
| "notification"
|
||||
| "robot_spot"
|
||||
| "rwa_product"
|
||||
| "rwa_producttype"
|
||||
| "rwa_subscribe"
|
||||
| "tokenization_product"
|
||||
| "tokenization_trading-pairs"
|
||||
| "transfer"
|
||||
| "user_bankcard"
|
||||
| "user_list"
|
||||
| "withdraw_approved"
|
||||
| "withdraw_fiat"
|
||||
>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import type { FormInst, FormItemRule } from 'naive-ui';
|
||||
import { NButton, NForm, NFormItem, NFormItemGi, NGrid, NInput, NInputNumber, NSpace, NSwitch } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
type Asset = CommonType.TreatyBody<typeof client.api.admin.assets.post>;
|
||||
|
||||
defineOptions({
|
||||
name: 'AssetEdit'
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
}>();
|
||||
|
||||
const formRef = ref<FormInst | null>(null);
|
||||
const loading = ref(false);
|
||||
|
||||
const formModel = reactive<Asset>({
|
||||
code: '',
|
||||
name: '',
|
||||
iconUrl: '',
|
||||
maxWithdrawPerDay: '',
|
||||
maxWithdrawPerTx: '',
|
||||
minDeposit: '',
|
||||
minWithdraw: '',
|
||||
withdrawFeeFixed: '',
|
||||
withdrawFeeRate: '',
|
||||
tradeEnabled: false,
|
||||
transferEnabled: false,
|
||||
withdrawEnabled: false,
|
||||
depositEnabled: false,
|
||||
isActive: false,
|
||||
isRwaAsset: false,
|
||||
sortOrder: 1
|
||||
});
|
||||
|
||||
// 用于表单输入的临时数字值
|
||||
const tempValues = ref({
|
||||
maxWithdrawPerDay: 0,
|
||||
maxWithdrawPerTx: 0,
|
||||
minDeposit: 0,
|
||||
minWithdraw: 0,
|
||||
withdrawFeeFixed: 0,
|
||||
withdrawFeeRate: 0, // 转换为百分比
|
||||
sortOrder: 0
|
||||
});
|
||||
|
||||
const rules: Record<string, FormItemRule | FormItemRule[]> = {
|
||||
code: { required: true, message: '请输入资产代码', trigger: 'blur' },
|
||||
name: { required: true, message: '请输入资产名称', trigger: 'blur' },
|
||||
iconUrl: { required: true, message: '请输入图标地址', trigger: 'blur' },
|
||||
minDeposit: { required: true, message: '请输入最小充值金额', trigger: 'blur' },
|
||||
minWithdraw: { required: true, message: '请输入最小提现金额', trigger: 'blur' },
|
||||
withdrawFeeFixed: { required: true, message: '请输入提现固定手续费', trigger: 'blur' },
|
||||
withdrawFeeRate: { required: true, message: '请输入提现手续费率', trigger: 'blur' }
|
||||
};
|
||||
|
||||
// 同步数字输入到表单字符串
|
||||
function syncNumberToString() {
|
||||
formModel.maxWithdrawPerDay = tempValues.value.maxWithdrawPerDay.toString();
|
||||
formModel.maxWithdrawPerTx = tempValues.value.maxWithdrawPerTx.toString();
|
||||
formModel.minDeposit = tempValues.value.minDeposit.toString();
|
||||
formModel.minWithdraw = tempValues.value.minWithdraw.toString();
|
||||
formModel.withdrawFeeFixed = tempValues.value.withdrawFeeFixed.toString();
|
||||
formModel.withdrawFeeRate = (tempValues.value.withdrawFeeRate / 100).toString(); // 转回小数
|
||||
formModel.sortOrder = tempValues.value.sortOrder;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
syncNumberToString();
|
||||
await formRef.value?.validate();
|
||||
loading.value = true;
|
||||
await safeClient(() =>
|
||||
client.api.admin.assets.post({
|
||||
...formModel
|
||||
})
|
||||
);
|
||||
loading.value = false;
|
||||
window.$message?.success('更新成功');
|
||||
emit('close');
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm ref="formRef" :model="formModel" :rules="rules" label-placement="left" :label-width="140">
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="资产代码" path="code">
|
||||
<NInput v-model:value="formModel.code" placeholder="请输入资产代码" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="资产名称" path="name">
|
||||
<NInput v-model:value="formModel.name" placeholder="请输入资产名称" />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem label="图标地址" path="iconUrl">
|
||||
<IconPicker v-model="formModel.iconUrl" :collections="['cryptocurrency-color']" />
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="每日最大提现金额" path="maxWithdrawPerDay">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.maxWithdrawPerDay"
|
||||
placeholder="请输入每日最大提现金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="单笔最大提现金额" path="maxWithdrawPerTx">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.maxWithdrawPerTx"
|
||||
placeholder="请输入单笔最大提现金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="最小充值金额" path="minDeposit">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.minDeposit"
|
||||
placeholder="请输入最小充值金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="最小提现金额" path="minWithdraw">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.minWithdraw"
|
||||
placeholder="请输入最小提现金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="提现固定手续费" path="withdrawFeeFixed">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.withdrawFeeFixed"
|
||||
placeholder="请输入提现固定手续费"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="提现手续费率" path="withdrawFeeRate">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.withdrawFeeRate"
|
||||
placeholder="请输入提现手续费率"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="4"
|
||||
class="w-full"
|
||||
>
|
||||
<template #suffix>%</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem label="排序" path="sortOrder">
|
||||
<NInputNumber v-model:value="tempValues.sortOrder" placeholder="请输入排序" :min="0" class="w-full" />
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="是否可交易">
|
||||
<NSwitch v-model:value="formModel.tradeEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否可转账">
|
||||
<NSwitch v-model:value="formModel.transferEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否可提现">
|
||||
<NSwitch v-model:value="formModel.withdrawEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否可充值">
|
||||
<NSwitch v-model:value="formModel.depositEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否启用">
|
||||
<NSwitch v-model:value="formModel.isActive" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否为 RWA 资产">
|
||||
<NSwitch v-model:value="formModel.isRwaAsset" />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NSpace justify="end">
|
||||
<NButton type="primary" ghost @click="handleCancel">取消</NButton>
|
||||
<NButton type="primary" :loading="loading" @click="handleSubmit">创建</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,218 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import type { FormInst, FormItemRule } from 'naive-ui';
|
||||
import { NButton, NForm, NFormItem, NFormItemGi, NGrid, NInput, NInputNumber, NSpace, NSwitch } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
type Asset = CommonType.TreatyBody<typeof client.api.admin.assets.post>;
|
||||
|
||||
defineOptions({
|
||||
name: 'AssetEdit'
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
data: Asset;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
}>();
|
||||
|
||||
const formRef = ref<FormInst | null>(null);
|
||||
const loading = ref(false);
|
||||
|
||||
const formModel = reactive<Partial<Asset>>({
|
||||
code: props.data.code,
|
||||
name: props.data.name,
|
||||
iconUrl: props.data.iconUrl,
|
||||
maxWithdrawPerDay: props.data.maxWithdrawPerDay,
|
||||
maxWithdrawPerTx: props.data.maxWithdrawPerTx,
|
||||
minDeposit: props.data.minDeposit,
|
||||
minWithdraw: props.data.minWithdraw,
|
||||
withdrawFeeFixed: props.data.withdrawFeeFixed,
|
||||
withdrawFeeRate: props.data.withdrawFeeRate,
|
||||
tradeEnabled: props.data.tradeEnabled,
|
||||
transferEnabled: props.data.transferEnabled,
|
||||
withdrawEnabled: props.data.withdrawEnabled,
|
||||
depositEnabled: props.data.depositEnabled,
|
||||
isActive: props.data.isActive,
|
||||
isRwaAsset: props.data.isRwaAsset,
|
||||
sortOrder: props.data.sortOrder
|
||||
});
|
||||
|
||||
// 用于表单输入的临时数字值
|
||||
const tempValues = ref({
|
||||
maxWithdrawPerDay: Number(props.data.maxWithdrawPerDay) || 0,
|
||||
maxWithdrawPerTx: Number(props.data.maxWithdrawPerTx) || 0,
|
||||
minDeposit: Number(props.data.minDeposit) || 0,
|
||||
minWithdraw: Number(props.data.minWithdraw) || 0,
|
||||
withdrawFeeFixed: Number(props.data.withdrawFeeFixed) || 0,
|
||||
withdrawFeeRate: Number(props.data.withdrawFeeRate) * 100 || 0, // 转换为百分比
|
||||
sortOrder: Number(props.data.sortOrder) || 0
|
||||
});
|
||||
|
||||
const rules: Record<string, FormItemRule | FormItemRule[]> = {
|
||||
code: { required: true, message: '请输入资产代码', trigger: 'blur' },
|
||||
name: { required: true, message: '请输入资产名称', trigger: 'blur' },
|
||||
iconUrl: { required: true, message: '请输入图标地址', trigger: 'blur' },
|
||||
minDeposit: { required: true, message: '请输入最小充值金额', trigger: 'blur' },
|
||||
minWithdraw: { required: true, message: '请输入最小提现金额', trigger: 'blur' },
|
||||
withdrawFeeFixed: { required: true, message: '请输入提现固定手续费', trigger: 'blur' },
|
||||
withdrawFeeRate: { required: true, message: '请输入提现手续费率', trigger: 'blur' }
|
||||
};
|
||||
|
||||
// 同步数字输入到表单字符串
|
||||
function syncNumberToString() {
|
||||
formModel.maxWithdrawPerDay = tempValues.value.maxWithdrawPerDay.toString();
|
||||
formModel.maxWithdrawPerTx = tempValues.value.maxWithdrawPerTx.toString();
|
||||
formModel.minDeposit = tempValues.value.minDeposit.toString();
|
||||
formModel.minWithdraw = tempValues.value.minWithdraw.toString();
|
||||
formModel.withdrawFeeFixed = tempValues.value.withdrawFeeFixed.toString();
|
||||
formModel.withdrawFeeRate = (tempValues.value.withdrawFeeRate / 100).toString(); // 转回小数
|
||||
formModel.sortOrder = tempValues.value.sortOrder;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
syncNumberToString();
|
||||
await formRef.value?.validate();
|
||||
loading.value = true;
|
||||
await safeClient(() =>
|
||||
client.api.admin.assets({ code: props.data.code }).patch({
|
||||
...formModel
|
||||
})
|
||||
);
|
||||
loading.value = false;
|
||||
window.$message?.success('更新成功');
|
||||
emit('close');
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm ref="formRef" :model="formModel" :rules="rules" label-placement="left" :label-width="140">
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="资产代码" path="code">
|
||||
<NInput v-model:value="formModel.code" placeholder="请输入资产代码" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="资产名称" path="name">
|
||||
<NInput v-model:value="formModel.name" placeholder="请输入资产名称" />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem label="图标地址" path="iconUrl">
|
||||
<IconPicker v-model="formModel.iconUrl" :collections="['cryptocurrency-color']" />
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="每日最大提现金额" path="maxWithdrawPerDay">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.maxWithdrawPerDay"
|
||||
placeholder="请输入每日最大提现金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="单笔最大提现金额" path="maxWithdrawPerTx">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.maxWithdrawPerTx"
|
||||
placeholder="请输入单笔最大提现金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="最小充值金额" path="minDeposit">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.minDeposit"
|
||||
placeholder="请输入最小充值金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="最小提现金额" path="minWithdraw">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.minWithdraw"
|
||||
placeholder="请输入最小提现金额"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="提现固定手续费" path="withdrawFeeFixed">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.withdrawFeeFixed"
|
||||
placeholder="请输入提现固定手续费"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="提现手续费率" path="withdrawFeeRate">
|
||||
<NInputNumber
|
||||
v-model:value="tempValues.withdrawFeeRate"
|
||||
placeholder="请输入提现手续费率"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="4"
|
||||
class="w-full"
|
||||
>
|
||||
<template #suffix>%</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem label="排序" path="sortOrder">
|
||||
<NInputNumber v-model:value="tempValues.sortOrder" placeholder="请输入排序" :min="0" class="w-full" />
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi label="是否可交易">
|
||||
<NSwitch v-model:value="formModel.tradeEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否可转账">
|
||||
<NSwitch v-model:value="formModel.transferEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否可提现">
|
||||
<NSwitch v-model:value="formModel.withdrawEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否可充值">
|
||||
<NSwitch v-model:value="formModel.depositEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否启用">
|
||||
<NSwitch v-model:value="formModel.isActive" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="是否为 RWA 资产">
|
||||
<NSwitch v-model:value="formModel.isRwaAsset" />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NSpace justify="end">
|
||||
<NButton type="primary" ghost @click="handleCancel">取消</NButton>
|
||||
<NButton type="primary" :loading="loading" @click="handleSubmit">提交</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,242 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { NSelect, useDialog } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
import Edit from './components/edit.vue';
|
||||
import Add from './components/add.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.assets.get({
|
||||
query: { ...pagination, ...filter }
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: '代码',
|
||||
key: 'code',
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name'
|
||||
},
|
||||
|
||||
{
|
||||
title: '图标',
|
||||
key: 'iconUrl',
|
||||
width: 80,
|
||||
render: (row: any) => {
|
||||
return h(SvgIcon, { icon: row.iconUrl, class: 'w-6 h-6' });
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '每日最大提现金额',
|
||||
key: 'maxWithdrawPerDay',
|
||||
render: (row: any) => {
|
||||
return Number(row.maxWithdrawPerDay).toFixed(2) ?? '无限制';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '单笔最大提现金额',
|
||||
key: 'maxWithdrawPerTx',
|
||||
render: (row: any) => {
|
||||
return Number(row.maxWithdrawPerTx).toFixed(2) ?? '无限制';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '最小充值金额',
|
||||
key: 'minDeposit',
|
||||
render: (row: any) => {
|
||||
return Number(row.minDeposit).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '最小提现金额',
|
||||
key: 'minWithdraw',
|
||||
render: (row: any) => {
|
||||
return Number(row.minWithdraw).toFixed(2);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
title: '提现固定手续费',
|
||||
key: 'withdrawFeeFixed',
|
||||
render: (row: any) => {
|
||||
return Number(row.withdrawFeeFixed).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '提现手续费率',
|
||||
key: 'withdrawFeeRate',
|
||||
render: (row: any) => {
|
||||
return `${Number(row.withdrawFeeRate).toFixed(4)}%`;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否可交易',
|
||||
key: 'tradeEnabled',
|
||||
render: (row: any) => {
|
||||
return row.tradeEnabled ? '是' : '否';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否可转账',
|
||||
key: 'transferEnabled',
|
||||
render: (row: any) => {
|
||||
return row.transferEnabled ? '是' : '否';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否可提现',
|
||||
key: 'withdrawEnabled',
|
||||
render: (row: any) => {
|
||||
return row.withdrawEnabled ? '是' : '否';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否可充值',
|
||||
key: 'depositEnabled',
|
||||
render: (row: any) => {
|
||||
return row.depositEnabled ? '是' : '否';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否启用',
|
||||
key: 'isActive',
|
||||
render: (row: any) => {
|
||||
return row.isActive ? '是' : '否';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否为 RWA 资产',
|
||||
key: 'isRwaAsset',
|
||||
render: (row: any) => {
|
||||
return row.isRwaAsset ? '是' : '否';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sortOrder'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 140,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '编辑',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
handleEdit(row);
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '删除',
|
||||
ghost: true,
|
||||
type: 'error',
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
dialog.create({
|
||||
title: '删除确认',
|
||||
content: '确认删除该资产吗,删除后不可恢复。',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() => client.api.admin.assets({ code: row.code }).delete());
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: '资产代码',
|
||||
key: 'code'
|
||||
},
|
||||
{
|
||||
title: '资产名称',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '是否启用',
|
||||
key: 'isActive',
|
||||
component: NSelect,
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
clearable: true,
|
||||
options: [
|
||||
{ label: '启用', value: true },
|
||||
{ label: '未启用', value: false }
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
function handleAdd() {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '新建资产',
|
||||
showIcon: false,
|
||||
content: () =>
|
||||
h(Add, {
|
||||
onClose: () => {
|
||||
dialogInstance?.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '800px' },
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑资产',
|
||||
showIcon: false,
|
||||
content: () =>
|
||||
h(Edit, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance?.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '800px' },
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
:columns="columns"
|
||||
:fetch-data="fetchData"
|
||||
show-header-operation
|
||||
:filter-columns="filterColumns"
|
||||
:scroll-x="3000"
|
||||
@add="handleAdd"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,121 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'AddBank' });
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.bank_account.banks.post>;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formRef = useTemplateRef<FormInst | null>('formRef');
|
||||
|
||||
const form = ref<Body>({
|
||||
bankCode: '',
|
||||
nameCn: '',
|
||||
nameEn: '',
|
||||
isActive: true,
|
||||
sortOrder: 0
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
bankCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入银行代码',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
pattern: /^[A-Z0-9_]+$/,
|
||||
message: '银行代码只能包含大写字母、数字和下划线',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
nameCn: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入银行中文名称',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 100,
|
||||
message: '银行名称长度应在2-100个字符之间',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
sortOrder: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入排序顺序',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
async function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.bank_account.banks.post({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('银行创建成功');
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-placement="left"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem label="银行代码" path="bankCode">
|
||||
<NInput
|
||||
v-model:value="form.bankCode"
|
||||
placeholder="请输入银行代码(如:ICBC)"
|
||||
maxlength="50"
|
||||
:input-props="{ style: 'text-transform: uppercase' }"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="银行名称" path="nameCn">
|
||||
<NInput v-model:value="form.nameCn" placeholder="请输入银行中文名称" maxlength="100" show-count />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="排序顺序" path="sortOrder">
|
||||
<NInputNumber v-model:value="form.sortOrder" :min="0" :step="1" placeholder="请输入排序顺序" class="w-full">
|
||||
<template #suffix>
|
||||
<span class="text-12px text-gray-400">数字越小越靠前</span>
|
||||
</template>
|
||||
</NInputNumber>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="是否启用" path="isActive">
|
||||
<NSwitch :value="form.isActive || false" @update:value="val => (form.isActive = val)">
|
||||
<template #checked>启用</template>
|
||||
<template #unchecked>禁用</template>
|
||||
</NSwitch>
|
||||
</NFormItem>
|
||||
|
||||
<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>
|
||||
@@ -1,106 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'EditBank' });
|
||||
|
||||
type Data = Treaty.Data<typeof client.api.admin.bank_account.banks.get>['data'][number];
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.bank_account.banks.post>;
|
||||
|
||||
const props = defineProps<{
|
||||
data: Data;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formRef = useTemplateRef<FormInst | null>('formRef');
|
||||
|
||||
const form = ref<Body>({
|
||||
...props.data
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
nameCn: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入银行中文名称',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 100,
|
||||
message: '银行名称长度应在2-100个字符之间',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
sortOrder: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入排序顺序',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
async function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.bank_account.banks({ id: props.data.id }).patch({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('银行更新成功');
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-placement="left"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem label="银行代码">
|
||||
<NInput :value="data.bankCode" disabled placeholder="银行代码不可修改" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="银行名称" path="nameCn">
|
||||
<NInput v-model:value="form.nameCn" placeholder="请输入银行中文名称" maxlength="100" show-count />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="排序顺序" path="sortOrder">
|
||||
<NInputNumber v-model:value="form.sortOrder" :min="0" :step="1" placeholder="请输入排序顺序" class="w-full">
|
||||
<template #suffix>
|
||||
<span class="text-12px text-gray-400">数字越小越靠前</span>
|
||||
</template>
|
||||
</NInputNumber>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="是否启用" path="isActive">
|
||||
<NSwitch :value="form.isActive || false" @update:value="val => (form.isActive = val)">
|
||||
<template #checked>启用</template>
|
||||
<template #unchecked>禁用</template>
|
||||
</NSwitch>
|
||||
</NFormItem>
|
||||
|
||||
<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>
|
||||
@@ -1,146 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import Add from './components/add.vue';
|
||||
import Edit from './components/edit.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.bank_account.banks.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: '银行名称',
|
||||
key: 'nameCn'
|
||||
},
|
||||
{
|
||||
title: '银行编号',
|
||||
key: 'bankCode'
|
||||
},
|
||||
{
|
||||
title: '是否启用',
|
||||
key: 'isActive',
|
||||
width: 100,
|
||||
render(row: any) {
|
||||
return row.isActive ? '是' : '否';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sortOrder',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
render(row: any) {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm:ss').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 160,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '编辑',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
handleEdit(row);
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '删除',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '提示',
|
||||
positiveText: '是',
|
||||
negativeText: '否',
|
||||
content: '确认删除该银行信息?',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() => client.api.admin.bank_account.banks({ id: row.id }).delete());
|
||||
message.success('删除成功');
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: '银行名称',
|
||||
key: 'nameCn'
|
||||
},
|
||||
{
|
||||
title: '银行编号',
|
||||
key: 'bankCode'
|
||||
}
|
||||
];
|
||||
|
||||
function handleAdd() {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '创建银行',
|
||||
content: () =>
|
||||
h(Add, {
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
function handleEdit(row) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑银行',
|
||||
content: () =>
|
||||
h(Edit, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
show-header-operation
|
||||
:columns="columns"
|
||||
:filter-columns="filterColumns"
|
||||
:fetch-data="fetchData"
|
||||
:scroll-x="800"
|
||||
@add="handleAdd"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,140 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useTemplateRef } from 'vue';
|
||||
import { NSelect, useDialog, useMessage } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import { DepositTypeEnum } from '@/enum';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.deposit.pending.get({
|
||||
query: { ...pagination, ...filter }
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userId'
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
key: 'user.email'
|
||||
},
|
||||
{
|
||||
title: '资产代码',
|
||||
key: 'assetCode'
|
||||
},
|
||||
{
|
||||
title: '充值类型',
|
||||
key: 'depositType',
|
||||
render(row) {
|
||||
return DepositTypeEnum[row.depositType as keyof typeof DepositTypeEnum];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '金额',
|
||||
key: 'amount',
|
||||
render: row => {
|
||||
return Number(row.amount).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 160,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '通过',
|
||||
type: 'primary',
|
||||
strong: true,
|
||||
secondary: true,
|
||||
visible: row.status !== 'approved',
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '提示',
|
||||
positiveText: '是',
|
||||
negativeText: '否',
|
||||
content: '确认通过该充值请求吗?',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.deposit.approve({ orderId: row.id as string }).post({
|
||||
actualAmount: row.amount
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
message.success('充值通过成功');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '拒绝',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '提示',
|
||||
positiveText: '是',
|
||||
negativeText: '否',
|
||||
content: '确认拒绝该充值请求吗?',
|
||||
onPositiveClick: async () => {
|
||||
safeClient(() =>
|
||||
client.api.admin.deposit.reject({ orderId: row.id as string }).post({
|
||||
reviewNote: '管理员拒绝充值'
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
message.success('充值拒绝成功');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userId'
|
||||
},
|
||||
{
|
||||
title: '资产代码',
|
||||
key: 'assetCode'
|
||||
},
|
||||
{
|
||||
title: '充值类型',
|
||||
key: 'depositType',
|
||||
component: NSelect,
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '法币充值', value: 'fiat' },
|
||||
{ label: '加密货币充值', value: 'crypto' }
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
:columns="columns"
|
||||
:fetch-data="fetchData"
|
||||
show-header-operation
|
||||
:filter-columns="filterColumns"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,236 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import { NInput, useDialog } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import UploadS3 from '@/components/upload/index.vue';
|
||||
|
||||
defineOptions({ name: 'AddNews' });
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.news.post>;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formRef = useTemplateRef<FormInst | null>('formRef');
|
||||
const dialog = useDialog();
|
||||
|
||||
const form = ref<Body>({
|
||||
title: '',
|
||||
content: '',
|
||||
categoryId: '',
|
||||
attachmentIds: [],
|
||||
isPinned: false
|
||||
});
|
||||
|
||||
// 获取分类列表
|
||||
const { data: categories, execute: refreshCategories } = safeClient(() =>
|
||||
client.api.admin.news_categories.get({
|
||||
query: {
|
||||
pageIndex: 1,
|
||||
pageSize: 100
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const categoryOptions = computed(
|
||||
() =>
|
||||
categories.value?.data.map(cat => ({
|
||||
label: cat.name,
|
||||
value: cat.id
|
||||
})) || []
|
||||
);
|
||||
|
||||
// 新增分类
|
||||
function handleAddCategory() {
|
||||
const categoryName = ref('');
|
||||
|
||||
dialog.create({
|
||||
title: '新增分类',
|
||||
content: () =>
|
||||
h('div', { class: 'py-4' }, [
|
||||
h('div', { class: 'mb-2 text-14px' }, '分类名称'),
|
||||
h(NInput, {
|
||||
value: categoryName.value,
|
||||
placeholder: '请输入分类名称',
|
||||
'onUpdate:value': (val: string) => {
|
||||
categoryName.value = val;
|
||||
}
|
||||
})
|
||||
]),
|
||||
positiveText: '创建',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
if (!categoryName.value.trim()) {
|
||||
window.$message?.error('请输入分类名称');
|
||||
return false;
|
||||
}
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.news_categories.post({
|
||||
name: categoryName.value
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('分类创建成功');
|
||||
await refreshCategories();
|
||||
if (data.value?.id) {
|
||||
form.value.categoryId = data.value.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const rules: FormRules = {
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入新闻标题',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 200,
|
||||
message: '标题长度应在2-200个字符之间',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
summary: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入新闻摘要',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 10,
|
||||
max: 200,
|
||||
message: '摘要长度应在10-200个字符之间',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
content: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入新闻内容',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 10,
|
||||
message: '内容至少10个字符',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
categoryId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择新闻分类',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
async function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.news.post({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('新闻创建成功');
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-placement="left"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem label="新闻标题" path="title">
|
||||
<NInput v-model:value="form.title" placeholder="请输入新闻标题" maxlength="50" show-count />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="缩略图" path="thumbnailId">
|
||||
<UploadS3
|
||||
:model-value="form.thumbnailId ? [form.thumbnailId] : undefined"
|
||||
:max-size="20"
|
||||
:max-files="1"
|
||||
accept="image/*"
|
||||
placeholder="上传图片"
|
||||
:fetch-options="{ businessType: 'news_attachment' }"
|
||||
@update:model-value="evt => (form.thumbnailId = evt.length > 0 ? evt[0] : undefined)"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2">
|
||||
<NFormItemGi label="新闻分类" path="categoryId">
|
||||
<NSelect v-model:value="form.categoryId" :options="categoryOptions" placeholder="请选择新闻分类" class="flex-1">
|
||||
<template #action>
|
||||
<div class="px-3 py-2">
|
||||
<NButton size="small" type="primary" block @click.stop="handleAddCategory">
|
||||
<template #icon>
|
||||
<icon-ic-round-plus class="text-icon" />
|
||||
</template>
|
||||
新增分类
|
||||
</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</NSelect>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="新闻摘要" path="summary">
|
||||
<NInput v-model:value="form.summary" placeholder="请输入新闻摘要" maxlength="100" show-count />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem label="新闻内容" path="content">
|
||||
<MarkdownEditor v-model:value="form.content" placeholder="请输入新闻内容" :preview="false" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="附件上传" path="attachmentIds">
|
||||
<UploadS3
|
||||
:model-value="form.attachmentIds || []"
|
||||
:max-size="20"
|
||||
:max-files="10"
|
||||
accept="*/*"
|
||||
placeholder="上传附件"
|
||||
:fetch-options="{ businessType: 'other' }"
|
||||
@update:model-value="evt => (form.attachmentIds = evt)"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2">
|
||||
<NFormItemGi label="置顶显示" path="isPinned">
|
||||
<NSwitch v-model:value="form.isPinned">
|
||||
<template #checked>是</template>
|
||||
<template #unchecked>否</template>
|
||||
</NSwitch>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="排序顺序" path="sortOrder">
|
||||
<NInputNumber v-model:value="form.sortOrder" :min="0" :step="1" placeholder="请输入排序顺序" class="w-full">
|
||||
<template #suffix>
|
||||
<span class="text-12px text-gray-400">数字越小越靠前</span>
|
||||
</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<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>
|
||||
@@ -1,238 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import { NInput, useDialog } from 'naive-ui';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import UploadS3 from '@/components/upload/index.vue';
|
||||
|
||||
defineOptions({ name: 'EditNews' });
|
||||
|
||||
type Data = Treaty.Data<typeof client.api.admin.news.get>['data'][number];
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.news.post>;
|
||||
|
||||
const props = defineProps<{
|
||||
data: Data;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formRef = useTemplateRef<FormInst | null>('formRef');
|
||||
const dialog = useDialog();
|
||||
|
||||
const form = ref<Body>({
|
||||
...props.data
|
||||
});
|
||||
|
||||
// 获取分类列表
|
||||
const { data: categories, execute: refreshCategories } = safeClient(() =>
|
||||
client.api.admin.news_categories.get({
|
||||
query: {
|
||||
pageIndex: 1,
|
||||
pageSize: 100
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const categoryOptions = computed(
|
||||
() =>
|
||||
categories.value?.data.map(cat => ({
|
||||
label: cat.name,
|
||||
value: cat.id
|
||||
})) || []
|
||||
);
|
||||
|
||||
// 新增分类
|
||||
function handleAddCategory() {
|
||||
const categoryName = ref('');
|
||||
|
||||
dialog.create({
|
||||
title: '新增分类',
|
||||
content: () =>
|
||||
h('div', { class: 'py-4' }, [
|
||||
h('div', { class: 'mb-2 text-14px' }, '分类名称'),
|
||||
h(NInput, {
|
||||
value: categoryName.value,
|
||||
placeholder: '请输入分类名称',
|
||||
'onUpdate:value': (val: string) => {
|
||||
categoryName.value = val;
|
||||
}
|
||||
})
|
||||
]),
|
||||
positiveText: '创建',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
if (!categoryName.value.trim()) {
|
||||
window.$message?.error('请输入分类名称');
|
||||
return false;
|
||||
}
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.news_categories.post({
|
||||
name: categoryName.value
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('分类创建成功');
|
||||
await refreshCategories();
|
||||
if (data.value?.id) {
|
||||
form.value.categoryId = data.value.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const rules: FormRules = {
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入新闻标题',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 200,
|
||||
message: '标题长度应在2-200个字符之间',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
summary: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入新闻摘要',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 10,
|
||||
max: 200,
|
||||
message: '摘要长度应在10-200个字符之间',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
content: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入新闻内容',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 10,
|
||||
message: '内容至少10个字符',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
categoryId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择新闻分类',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
async function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.news({ id: props.data.id }).patch({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('新闻更新成功');
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-placement="left"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem label="新闻标题" path="title">
|
||||
<NInput v-model:value="form.title" placeholder="请输入新闻标题" maxlength="200" show-count />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="缩略图" path="thumbnailId">
|
||||
<UploadS3
|
||||
:model-value="[form.thumbnailId || '']"
|
||||
:max-size="20"
|
||||
:max-files="1"
|
||||
accept="image/*"
|
||||
placeholder="上传图片"
|
||||
:fetch-options="{ businessType: 'news_attachment' }"
|
||||
@update:model-value="evt => (form.thumbnailId = evt[0] || '')"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2">
|
||||
<NFormItemGi label="新闻分类" path="categoryId">
|
||||
<NSelect v-model:value="form.categoryId" :options="categoryOptions" placeholder="请选择新闻分类" class="flex-1">
|
||||
<template #action>
|
||||
<div class="px-3 py-2">
|
||||
<NButton size="small" type="primary" block @click.stop="handleAddCategory">
|
||||
<template #icon>
|
||||
<icon-ic-round-plus class="text-icon" />
|
||||
</template>
|
||||
新增分类
|
||||
</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</NSelect>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="新闻摘要" path="summary">
|
||||
<NInput v-model:value="form.summary" placeholder="请输入新闻摘要" maxlength="100" show-count />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem label="新闻内容" path="content">
|
||||
<MarkdownEditor v-model:value="form.content" placeholder="请输入新闻内容" :preview="false" class="h-400px!" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="附件上传" path="attachmentIds">
|
||||
<UploadS3
|
||||
:model-value="form.attachmentIds || []"
|
||||
:max-size="20"
|
||||
:max-files="10"
|
||||
accept="*/*"
|
||||
placeholder="上传附件"
|
||||
:fetch-options="{ businessType: 'news_attachment' }"
|
||||
@update:model-value="evt => (form.attachmentIds = evt)"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2">
|
||||
<NFormItemGi label="置顶显示" path="isPinned">
|
||||
<NSwitch v-model:value="form.isPinned">
|
||||
<template #checked>是</template>
|
||||
<template #unchecked>否</template>
|
||||
</NSwitch>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="排序顺序" path="sortOrder">
|
||||
<NInputNumber v-model:value="form.sortOrder" :min="0" :step="1" placeholder="请输入排序顺序" class="w-full">
|
||||
<template #suffix>
|
||||
<span class="text-12px text-gray-400">数字越小越靠前</span>
|
||||
</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<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>
|
||||
@@ -1,202 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { NButton, NSelect, NSpace, NTag, useDialog } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import Add from './components/add.vue';
|
||||
import Edit from './components/edit.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
const { data: categories } = safeClient(
|
||||
client.api.admin.news_categories.get({
|
||||
query: {
|
||||
pageIndex: 1,
|
||||
pageSize: 100
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.news.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: '新闻标题',
|
||||
key: 'title',
|
||||
width: 250,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
key: 'category.name',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '摘要',
|
||||
key: 'summary',
|
||||
width: 300,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: '内容预览',
|
||||
key: 'content',
|
||||
width: 300,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: '附件数',
|
||||
key: 'attachmentIds',
|
||||
width: 100,
|
||||
render: (row: any) => row.attachmentIds?.length || 0
|
||||
},
|
||||
{
|
||||
title: '置顶',
|
||||
key: 'isPinned',
|
||||
width: 80,
|
||||
render: row =>
|
||||
h(
|
||||
NTag,
|
||||
{ type: row.isPinned ? 'warning' : 'default', size: 'small' },
|
||||
{ default: () => (row.isPinned ? '是' : '否') }
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '排序顺序',
|
||||
key: 'sortOrder',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm:ss').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'operations',
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
render: (row: any) =>
|
||||
h(
|
||||
NSpace,
|
||||
{ size: 'small' },
|
||||
{
|
||||
default: () => [
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
text: true,
|
||||
onClick: () => handleEdit(row)
|
||||
},
|
||||
{ default: () => '编辑' }
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'error',
|
||||
text: true,
|
||||
onClick: () => handleDelete(row.id)
|
||||
},
|
||||
{ default: () => '删除' }
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
key: 'isPinned',
|
||||
title: '置顶状态',
|
||||
component: NSelect,
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '已置顶', value: 'true' },
|
||||
{ label: '未置顶', value: 'false' }
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function handleAdd() {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '创建新闻',
|
||||
content: () =>
|
||||
h(Add, {
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '1200px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑新闻',
|
||||
content: () =>
|
||||
h(Edit, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '1200px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(id: string) {
|
||||
dialog.warning({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这条新闻吗?此操作不可恢复。',
|
||||
positiveText: '删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.news({ id }).delete({
|
||||
params: { id }
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('删除成功');
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
show-header-operation
|
||||
:columns="columns"
|
||||
:filter-columns="filterColumns"
|
||||
:fetch-data="fetchData"
|
||||
:scroll-x="800"
|
||||
@add="handleAdd"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,215 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'AddNotification' });
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.notifications.post>;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formRef = useTemplateRef<FormInst | null>('formRef');
|
||||
|
||||
const form = ref<Body>({
|
||||
title: '',
|
||||
content: '',
|
||||
category: 'GENERAL',
|
||||
type: 'system',
|
||||
isBroadcast: true,
|
||||
priority: 'normal',
|
||||
userIds: []
|
||||
});
|
||||
|
||||
const categoryOptions = [
|
||||
{ label: '通用', value: 'GENERAL' },
|
||||
{ label: '系统', value: 'SYSTEM' },
|
||||
{ label: '交易', value: 'TRADING' },
|
||||
{ label: '安全', value: 'SECURITY' },
|
||||
{ label: '营销', value: 'MARKETING' }
|
||||
];
|
||||
|
||||
const typeOptions = [
|
||||
{ label: '系统通知', value: 'system' },
|
||||
{ label: '安全通知', value: 'security' },
|
||||
{ label: '交易通知', value: 'transaction' },
|
||||
{ label: '活动通知', value: 'activity' }
|
||||
];
|
||||
|
||||
const priorityOptions = [
|
||||
{ label: '低', value: 'low' },
|
||||
{ label: '普通', value: 'normal' },
|
||||
{ label: '高', value: 'high' },
|
||||
{ label: '紧急', value: 'urgent' }
|
||||
];
|
||||
|
||||
// 用户选择相关
|
||||
const { data: users } = safeClient(() =>
|
||||
client.api.admin.users.get({
|
||||
query: {
|
||||
pageIndex: 1,
|
||||
pageSize: 100
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const userOptions = computed(
|
||||
() =>
|
||||
users.value?.data.map(user => ({
|
||||
label: `${user.username} (${user.email})`,
|
||||
value: user.id
|
||||
})) || []
|
||||
);
|
||||
|
||||
const showUserSelect = computed(() => !form.value.isBroadcast);
|
||||
|
||||
const rules: FormRules = {
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入通知标题',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 1,
|
||||
max: 200,
|
||||
message: '标题长度应在1-200个字符之间',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
content: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入通知内容',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 1,
|
||||
message: '内容不能为空',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
category: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择通知分类',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择通知类型',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
priority: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择优先级',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
userIds: [
|
||||
{
|
||||
type: 'array',
|
||||
validator: (_rule, value: string[]) => {
|
||||
if (!form.value.isBroadcast && (!value || value.length === 0)) {
|
||||
return new Error('非广播通知必须选择接收用户');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function handleBroadcastChange(value: boolean) {
|
||||
if (value) {
|
||||
form.value.userIds = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.notifications.post({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('通知创建成功');
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-placement="left"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem label="通知标题" path="title">
|
||||
<NInput v-model:value="form.title" placeholder="请输入通知标题" maxlength="200" show-count />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="通知内容" path="content">
|
||||
<NInput
|
||||
v-model:value="form.content"
|
||||
type="textarea"
|
||||
placeholder="请输入通知内容"
|
||||
:rows="5"
|
||||
maxlength="2000"
|
||||
show-count
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2" :x-gap="16">
|
||||
<NFormItemGi label="通知类型" path="type">
|
||||
<NSelect v-model:value="form.type" :options="typeOptions" placeholder="请选择通知类型" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="通知分类" path="category">
|
||||
<NSelect v-model:value="form.category" :options="categoryOptions" placeholder="请选择通知分类" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="优先级" path="priority">
|
||||
<NSelect v-model:value="form.priority" :options="priorityOptions" placeholder="请选择优先级" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi label="广播通知" path="isBroadcast">
|
||||
<NSwitch v-model:value="form.isBroadcast" @update:value="handleBroadcastChange">
|
||||
<template #checked>是</template>
|
||||
<template #unchecked>否</template>
|
||||
</NSwitch>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem v-if="showUserSelect" label="接收用户" path="userIds">
|
||||
<NSelect
|
||||
v-model:value="form.userIds"
|
||||
:options="userOptions"
|
||||
placeholder="请选择接收用户"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<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>
|
||||
@@ -1,147 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { NTag, useDialog } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
|
||||
import Add from './components/add.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.notifications.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const typeMap: Record<string, { label: string; type: 'info' | 'success' | 'warning' | 'error' }> = {
|
||||
system: { label: '系统', type: 'info' },
|
||||
security: { label: '安全', type: 'error' },
|
||||
transaction: { label: '交易', type: 'success' },
|
||||
activity: { label: '活动', type: 'warning' }
|
||||
};
|
||||
|
||||
const categoryMap: Record<string, string> = {
|
||||
GENERAL: '通用',
|
||||
SYSTEM: '系统',
|
||||
TRADING: '交易',
|
||||
SECURITY: '安全',
|
||||
MARKETING: '营销'
|
||||
};
|
||||
|
||||
const priorityMap: Record<string, { label: string; type: 'default' | 'info' | 'success' | 'warning' | 'error' }> = {
|
||||
low: { label: '低', type: 'default' },
|
||||
normal: { label: '普通', type: 'info' },
|
||||
high: { label: '高', type: 'warning' },
|
||||
urgent: { label: '紧急', type: 'error' }
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
key: 'selection',
|
||||
title: '序号',
|
||||
type: 'selection',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '通知标题',
|
||||
key: 'title',
|
||||
width: 200,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: '通知类型',
|
||||
key: 'type',
|
||||
width: 100,
|
||||
render: row => {
|
||||
const type = typeMap[row.type as keyof typeof typeMap];
|
||||
return h(NTag, { type: type?.type || 'default', size: 'small' }, { default: () => type?.label || row.type });
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
key: 'category',
|
||||
width: 100,
|
||||
render: (row: any) => {
|
||||
return categoryMap[row.category] || row.category;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '优先级',
|
||||
key: 'priority',
|
||||
width: 100,
|
||||
render: row => {
|
||||
const priority = priorityMap[row.priority as keyof typeof priorityMap];
|
||||
return h(
|
||||
NTag,
|
||||
{ type: priority?.type || 'default', size: 'small' },
|
||||
{ default: () => priority?.label || row.priority }
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '广播',
|
||||
key: 'isBroadcast',
|
||||
width: 80,
|
||||
render: row => (row.isBroadcast ? '是' : '否')
|
||||
},
|
||||
{
|
||||
title: '通知内容',
|
||||
key: 'content',
|
||||
ellipsis: { tooltip: true },
|
||||
width: 250
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm:ss').value;
|
||||
}
|
||||
}
|
||||
// {
|
||||
// title: '操作',
|
||||
// key: 'operations',
|
||||
// fixed: 'right',
|
||||
// width: 120
|
||||
// }
|
||||
];
|
||||
|
||||
function handleAdd() {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '创建通知',
|
||||
content: () =>
|
||||
h(Add, {
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false,
|
||||
onPositiveClick: () => {
|
||||
window.$message?.success('创建通知成功');
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
show-header-operation
|
||||
:columns="columns"
|
||||
:fetch-data="fetchData"
|
||||
:scroll-x="1000"
|
||||
@add="handleAdd"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,230 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'EditSpotRobotConfig' });
|
||||
|
||||
type Data = Treaty.Data<typeof client.api.admin.spot_robot_trader.configs.get>['data'][number];
|
||||
|
||||
const props = defineProps<{
|
||||
data: Data;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formRef = useTemplateRef<FormInst | null>('formRef');
|
||||
const { data: assets } = safeClient(client.api.admin.assets.get({ query: { pageIndex: 1, pageSize: 100 } }));
|
||||
const { data: users } = safeClient(client.api.admin.users.get({ query: { pageIndex: 1, pageSize: 200 } }));
|
||||
const { data: tradingPairs } = safeClient(
|
||||
client.api.admin.trading_pairs.get({ query: { pageIndex: 1, pageSize: 100 } })
|
||||
);
|
||||
|
||||
const assetOptions = computed(
|
||||
() =>
|
||||
assets.value?.data.map(asset => ({
|
||||
label: `${asset.code} - ${asset.name}`,
|
||||
value: asset.code
|
||||
})) || []
|
||||
);
|
||||
|
||||
const form = ref({
|
||||
baseAsset: props.data.baseAsset,
|
||||
quoteAsset: props.data.quoteAsset,
|
||||
robotUserId: props.data.robotUserId,
|
||||
symbol: props.data.symbol,
|
||||
baseTopUpAmount: props.data.baseTopUpAmount ? Number(props.data.baseTopUpAmount) : 0,
|
||||
enabled: props.data.enabled,
|
||||
operatorUserId: props.data.operatorUserId || '',
|
||||
buyPriceMax: props.data.buyPriceMax ? Number(props.data.buyPriceMax) : 0,
|
||||
buyPriceMin: props.data.buyPriceMin ? Number(props.data.buyPriceMin) : 0,
|
||||
buyPriceStep: props.data.buyPriceStep ? Number(props.data.buyPriceStep) : 0
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
baseAsset: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入基础资产',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
quoteAsset: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入计价资产',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
robotUserId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入机器人用户ID',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
symbol: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入交易对',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
baseTopUpAmount: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入基础资产单次充值金额',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
buyPriceMax: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入买入价格上限',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
buyPriceMin: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入买入价格下限',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
buyPriceStep: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入买入价格步长',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
async function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.spot_robot_trader.config.put({
|
||||
id: props.data.id,
|
||||
baseAsset: form.value.baseAsset,
|
||||
operatorUserId: form.value.operatorUserId,
|
||||
quoteAsset: form.value.quoteAsset,
|
||||
robotUserId: form.value.robotUserId,
|
||||
symbol: form.value.symbol,
|
||||
baseTopUpAmount: String(form.value.baseTopUpAmount),
|
||||
enabled: form.value.enabled,
|
||||
buyPriceMax: String(form.value.buyPriceMax),
|
||||
buyPriceMin: String(form.value.buyPriceMin),
|
||||
buyPriceStep: String(form.value.buyPriceStep)
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('现货机器人配置更新成功');
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="140px"
|
||||
label-placement="left"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem label="基础资产" path="baseAsset">
|
||||
<NSelect v-model:value="form.baseAsset" :options="assetOptions" placeholder="请选择基础资产" filterable />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="计价资产" path="quoteAsset">
|
||||
<NSelect v-model:value="form.quoteAsset" :options="assetOptions" placeholder="请选择计价资产" filterable />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="机器人用户ID" path="robotUserId">
|
||||
<NSelect
|
||||
v-model:value="form.robotUserId"
|
||||
:options="users?.data.map(user => ({ label: `${user.nickname || user.user.name}`, value: user.userId })) || []"
|
||||
placeholder="请选择机器人用户"
|
||||
filterable
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="交易对" path="symbol">
|
||||
<NSelect
|
||||
v-model:value="form.symbol"
|
||||
:options="tradingPairs?.data.map(tp => ({ label: tp.symbol, value: tp.symbol })) || []"
|
||||
placeholder="请选择交易对"
|
||||
filterable
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="基础资产单次充值金额" path="baseTopUpAmount">
|
||||
<NInputNumber
|
||||
v-model:value="form.baseTopUpAmount"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:precision="8"
|
||||
placeholder="请输入充值金额"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="买入价格上限" path="buyPriceMax">
|
||||
<NInputNumber
|
||||
v-model:value="form.buyPriceMax"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:precision="8"
|
||||
placeholder="请输入买入价格上限"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="买入价格下限" path="buyPriceMin">
|
||||
<NInputNumber
|
||||
v-model:value="form.buyPriceMin"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:precision="8"
|
||||
placeholder="请输入买入价格下限"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="买入价格步长" path="buyPriceStep">
|
||||
<NInputNumber
|
||||
v-model:value="form.buyPriceStep"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:precision="8"
|
||||
placeholder="请输入买入价格步长"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="是否启用" path="enabled">
|
||||
<NSwitch v-model:value="form.enabled">
|
||||
<template #checked>启用</template>
|
||||
<template #unchecked>禁用</template>
|
||||
</NSwitch>
|
||||
</NFormItem>
|
||||
|
||||
<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>
|
||||
@@ -1,143 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { useDialog } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
|
||||
import Edit from './components/edit.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.spot_robot_trader.configs.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
key: 'selection',
|
||||
title: '序号',
|
||||
type: 'selection',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
key: 'id',
|
||||
title: 'ID',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
key: 'baseAsset',
|
||||
title: '基础资产',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
key: 'quoteAsset',
|
||||
title: '计价资产',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
key: 'robotUserId',
|
||||
title: '机器人用户ID',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
key: 'symbol',
|
||||
title: '交易对',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
key: 'baseTopUpAmount',
|
||||
title: '基础资产单次充值金额',
|
||||
width: 160
|
||||
},
|
||||
{
|
||||
key: 'buyPriceMax',
|
||||
title: '买入价格上限',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
key: 'buyPriceMin',
|
||||
title: '买入价格下限',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
key: 'buyPriceStep',
|
||||
title: '买入价格步长',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
title: '创建时间',
|
||||
width: 180,
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm:ss').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'enabled',
|
||||
title: '是否启用',
|
||||
width: 100,
|
||||
render: (row: any) => (row.enabled ? '是' : '否')
|
||||
},
|
||||
{
|
||||
key: 'operations',
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: row.enabled ? '禁用' : '启用',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: async () => {
|
||||
await safeClient(
|
||||
client.api.admin.spot_robot_trader.config.enable.post({
|
||||
id: row.id
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '编辑',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
handleEdit(row);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function handleEdit(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑现货机器人配置',
|
||||
content: () =>
|
||||
h(Edit, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase ref="tableInst" show-header-operation :columns="columns" :fetch-data="fetchData" :scroll-x="1000" />
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,192 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import dayjs from 'dayjs';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'RwaEditionAdd' });
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
const props = defineProps<{
|
||||
productId: string;
|
||||
}>();
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.rwa.issuance.editions.post>;
|
||||
|
||||
const formInst = useTemplateRef<FormInst>('formInst');
|
||||
const form = ref({
|
||||
productId: props.productId,
|
||||
editionName: '',
|
||||
perUserLimit: '1',
|
||||
totalSupply: '1',
|
||||
unitPrice: '1',
|
||||
dividendRate: '0.01',
|
||||
launchDate: dayjs().valueOf() as number | null,
|
||||
subscriptionStartDate: dayjs().valueOf() as number | null,
|
||||
subscriptionEndDate: dayjs().valueOf() as number | null
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
editionName: [{ required: true, message: '请输入发行期名称', trigger: ['blur', 'input'] }],
|
||||
perUserLimit: [{ required: true, message: '请输入个人申购上限', trigger: ['blur', 'input'] }],
|
||||
totalSupply: [{ required: true, message: '请输入发行总量', trigger: ['blur', 'input'] }],
|
||||
unitPrice: [{ required: true, message: '请输入单价', trigger: ['blur', 'input'] }],
|
||||
dividendRate: [{ required: true, message: '请输入分红率', trigger: ['blur', 'input'] }],
|
||||
launchDate: [{ required: true, type: 'number', message: '请选择预热时间', trigger: ['blur', 'change'] }],
|
||||
subscriptionStartDate: [
|
||||
{ required: true, type: 'number', message: '请选择申购开始时间', trigger: ['blur', 'change'] }
|
||||
],
|
||||
subscriptionEndDate: [{ required: true, type: 'number', message: '请选择申购结束时间', trigger: ['blur', 'change'] }]
|
||||
};
|
||||
|
||||
// 手动验证时间逻辑
|
||||
function validateTimes() {
|
||||
const now = dayjs();
|
||||
|
||||
if (form.value.launchDate && dayjs(form.value.launchDate).isBefore(now)) {
|
||||
return '预热时间必须在当前时间之后';
|
||||
}
|
||||
|
||||
if (form.value.subscriptionStartDate && form.value.launchDate) {
|
||||
if (dayjs(form.value.subscriptionStartDate).isBefore(dayjs(form.value.launchDate))) {
|
||||
return '申购开始时间必须在预热时间之后';
|
||||
}
|
||||
}
|
||||
|
||||
if (form.value.subscriptionEndDate && form.value.subscriptionStartDate) {
|
||||
if (dayjs(form.value.subscriptionEndDate).isBefore(dayjs(form.value.subscriptionStartDate))) {
|
||||
return '申购结束时间必须在开始时间之后';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleCreateDraft() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
await safeClient(
|
||||
client.api.admin.rwa.issuance.editions.post({
|
||||
...form.value,
|
||||
launchDate: new Date(form.value.launchDate!),
|
||||
subscriptionStartDate: new Date(form.value.subscriptionStartDate!),
|
||||
subscriptionEndDate: new Date(form.value.subscriptionEndDate!)
|
||||
} as Body)
|
||||
);
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleCreateDraftAndSubmit() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
const timeError = validateTimes();
|
||||
if (timeError) {
|
||||
window.$message?.error(timeError);
|
||||
return;
|
||||
}
|
||||
await safeClient(
|
||||
client.api.admin.rwa.issuance.editions.post({
|
||||
...form.value,
|
||||
launchDate: new Date(form.value.launchDate!),
|
||||
subscriptionStartDate: new Date(form.value.subscriptionStartDate!),
|
||||
subscriptionEndDate: new Date(form.value.subscriptionEndDate!)
|
||||
} as Body)
|
||||
);
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm
|
||||
ref="formInst"
|
||||
:model="form"
|
||||
label-width="150"
|
||||
label-placement="left"
|
||||
label-align="left"
|
||||
:rules="rules"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem path="editionName" label="发行期名称">
|
||||
<NInput v-model:value="form.editionName" />
|
||||
</NFormItem>
|
||||
<NFormItem path="perUserLimit" label="个人申购上限(份)">
|
||||
<NInputNumber
|
||||
:min="1"
|
||||
:step="10"
|
||||
:value="Number(form.perUserLimit)"
|
||||
@update:value="val => (form.perUserLimit = String(val))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="totalSupply" label="发行总量(份)">
|
||||
<NInputNumber
|
||||
:min="1"
|
||||
:step="100"
|
||||
:value="Number(form.totalSupply)"
|
||||
@update:value="val => (form.totalSupply = String(val))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="unitPrice" label="单价($)">
|
||||
<NInputNumber :min="0" :value="Number(form.unitPrice)" @update:value="val => (form.unitPrice = String(val))" />
|
||||
</NFormItem>
|
||||
<NFormItem path="dividendRate" label="分红率(%)">
|
||||
<NInputNumber
|
||||
:max="100"
|
||||
:min="0"
|
||||
:step="0.1"
|
||||
:precision="2"
|
||||
:value="Number(form.dividendRate) * 100"
|
||||
@update:value="val => (form.dividendRate = String((val || 0) / 100))"
|
||||
>
|
||||
<template #suffix>%</template>
|
||||
</NInputNumber>
|
||||
</NFormItem>
|
||||
<NFormItem path="launchDate" label="预热时间">
|
||||
<NDatePicker
|
||||
v-model:value="form.launchDate"
|
||||
type="datetime"
|
||||
format="yyyy-MM-dd HH:mm"
|
||||
:is-date-disabled="(ts: number) => ts < Date.now()"
|
||||
value-format="x"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem>
|
||||
<NSpace vertical class="w-full">
|
||||
<NFormItem path="subscriptionStartDate" label="申购开始时间" label-width="150">
|
||||
<NDatePicker
|
||||
v-model:value="form.subscriptionStartDate"
|
||||
type="datetime"
|
||||
format="yyyy-MM-dd HH:mm"
|
||||
:is-date-disabled="(ts: number) => (form.launchDate ? ts < Number(form.launchDate) : false)"
|
||||
value-format="x"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="subscriptionEndDate" label="申购结束时间" label-width="150">
|
||||
<NDatePicker
|
||||
v-model:value="form.subscriptionEndDate"
|
||||
type="datetime"
|
||||
format="yyyy-MM-dd HH:mm"
|
||||
:is-date-disabled="
|
||||
(ts: number) => (form.subscriptionStartDate ? ts < Number(form.subscriptionStartDate) : false)
|
||||
"
|
||||
value-format="x"
|
||||
class="w-full"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NSpace>
|
||||
</NFormItem>
|
||||
|
||||
<NSpace justify="end" class="mt-5">
|
||||
<NButton type="primary" ghost @click="handleCreateDraftAndSubmit">创建并提交审核</NButton>
|
||||
<NButton type="primary" @click="handleCreateDraft">创建</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,153 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'RwaProductAdd' });
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.rwa.issuance.products.post>;
|
||||
|
||||
const formInst = useTemplateRef<FormInst>('formInst');
|
||||
const { data } = safeClient(
|
||||
client.api.admin.rwa.category.categories.get({
|
||||
query: {
|
||||
pageIndex: 1,
|
||||
pageSize: 200
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const form = ref<Body>({
|
||||
iconifyIcon: '',
|
||||
name: '',
|
||||
code: '',
|
||||
categoryId: '',
|
||||
description: '',
|
||||
estimatedValue: '1',
|
||||
totalSupplyLimit: '100',
|
||||
proofDocumentIds: []
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
iconifyIcon: [{ required: true, message: '请选择产品图标', trigger: ['blur', 'input'] }],
|
||||
name: [{ required: true, message: '请输入产品名称', trigger: ['blur', 'input'] }],
|
||||
code: [
|
||||
{ required: true, message: '请输入产品编号', trigger: ['blur', 'input'] },
|
||||
{
|
||||
trigger: ['blur'],
|
||||
asyncValidator: async (rule, value, callback) => {
|
||||
if (!value) return;
|
||||
const { data: check } = await safeClient(() =>
|
||||
client.api.admin.rwa.issuance.products.check_code.get({
|
||||
query: {
|
||||
code: value
|
||||
}
|
||||
})
|
||||
);
|
||||
if (check.value?.exists) {
|
||||
callback(new Error('产品编号已存在'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
],
|
||||
categoryId: [{ required: true, message: '请输入产品类型', trigger: ['blur', 'input'] }],
|
||||
estimatedValue: [{ required: true, message: '请输入产品估值', trigger: ['blur', 'input'] }],
|
||||
totalSupplyLimit: [{ required: true, message: '请输入总发行量', trigger: ['blur', 'input'] }]
|
||||
};
|
||||
|
||||
function handleCreateDraft() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
await safeClient(
|
||||
client.api.admin.rwa.issuance.products.post({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleCreateDraftAndSubmit() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
await safeClient(
|
||||
client.api.admin.rwa.issuance.products.submit.post({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm
|
||||
ref="formInst"
|
||||
:model="form"
|
||||
label-width="auto"
|
||||
label-placement="left"
|
||||
:rules="rules"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem path="iconifyIcon" label="产品图标 ">
|
||||
<IconPicker v-model="form.iconifyIcon" />
|
||||
</NFormItem>
|
||||
<NFormItem path="name" label="产品名称">
|
||||
<NInput v-model:value="form.name" />
|
||||
</NFormItem>
|
||||
<NFormItem path="code" label="产品编号">
|
||||
<NInput v-model:value="form.code" />
|
||||
</NFormItem>
|
||||
<NFormItem path="categoryId" label="产品类型">
|
||||
<NSelect
|
||||
:value="form.categoryId || null"
|
||||
:options="data?.data?.map(item => ({ label: item.name, value: item.id }))"
|
||||
@update:value="val => (form.categoryId = val as string)"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="estimatedValue" label="产品估值">
|
||||
<NInputNumber
|
||||
:min="1"
|
||||
:step="100"
|
||||
:precision="2"
|
||||
:value="Number(form.estimatedValue)"
|
||||
@update:value="val => (form.estimatedValue = String(val || 0))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="totalSupplyLimit" label="总发行量">
|
||||
<NInputNumber
|
||||
:min="1"
|
||||
:step="100"
|
||||
:precision="0"
|
||||
:value="Number(form.totalSupplyLimit)"
|
||||
@update:value="val => (form.totalSupplyLimit = String(val || 0))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="description" label="产品描述">
|
||||
<NInput v-model:value="form.description" type="textarea" />
|
||||
</NFormItem>
|
||||
<NFormItem path="proofDocuments" label="资产证明 ">
|
||||
<Upload
|
||||
:model-value="form.proofDocumentIds || []"
|
||||
:fetch-options="{
|
||||
businessType: 'rwa_proof'
|
||||
}"
|
||||
accept="application/pdf,image/*,.doc,.docx"
|
||||
@update:model-value="val => (form.proofDocumentIds = val)"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NSpace justify="end">
|
||||
<NButton type="primary" @click="handleCreateDraftAndSubmit">创建草稿并提交审核</NButton>
|
||||
<NButton type="primary" @click="handleCreateDraft">创建草稿</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,93 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { useDialog } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
|
||||
|
||||
const props = defineProps<{
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
const dialog = useDialog();
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.rwa.subscription.orders.get({
|
||||
query: {
|
||||
editionId: props.id,
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: '分配用户ID',
|
||||
key: 'userId',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '分配数量',
|
||||
key: 'quantity',
|
||||
width: 120,
|
||||
render: (row: any) => {
|
||||
return Number(row.quantity).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '单价',
|
||||
key: 'unitPrice',
|
||||
width: 120,
|
||||
render: (row: any) => {
|
||||
return Number(row.unitPrice).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '分配时间',
|
||||
key: 'createdAt',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 'auto',
|
||||
operations: row => [
|
||||
{
|
||||
contentText: '执行分配',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
dialog.create({
|
||||
title: '执行分配',
|
||||
content: '确认执行该分配操作?',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.rwa.subscription.allocate.post({
|
||||
editionId: props.id
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase ref="tableInst" title="申购记录" :columns="columns" :fetch-data="fetchData" :scroll-x="undefined" />
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,175 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import dayjs from 'dayjs';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.rwa.issuance.editions.post>;
|
||||
|
||||
defineOptions({ name: 'RwaEditionAdd' });
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
data: Body;
|
||||
}>();
|
||||
|
||||
const formInst = useTemplateRef<FormInst>('formInst');
|
||||
const form = ref<Body>({
|
||||
productId: props.data.productId,
|
||||
editionName: props.data.editionName,
|
||||
perUserLimit: props.data.perUserLimit,
|
||||
totalSupply: props.data.totalSupply,
|
||||
unitPrice: props.data.unitPrice,
|
||||
dividendRate: props.data.dividendRate,
|
||||
launchDate: dayjs().toDate(),
|
||||
subscriptionStartDate: dayjs().toDate(),
|
||||
subscriptionEndDate: dayjs().toDate()
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
editionName: [{ required: true, message: '请输入发行期名称', trigger: ['blur', 'input'] }],
|
||||
perUserLimit: [{ required: true, message: '请输入个人申购上限', trigger: ['blur', 'input'] }],
|
||||
totalSupply: [{ required: true, message: '请输入发行总量', trigger: ['blur', 'input'] }],
|
||||
unitPrice: [{ required: true, message: '请输入单价', trigger: ['blur', 'input'] }],
|
||||
dividendRate: [{ required: true, message: '请输入分红率', trigger: ['blur', 'input'] }],
|
||||
launchDate: [{ required: true, type: 'number', message: '请选择预热时间', trigger: ['blur', 'change'] }],
|
||||
subscriptionStartDate: [
|
||||
{ required: true, type: 'number', message: '请选择申购开始时间', trigger: ['blur', 'change'] }
|
||||
],
|
||||
subscriptionEndDate: [{ required: true, type: 'number', message: '请选择申购结束时间', trigger: ['blur', 'change'] }]
|
||||
};
|
||||
|
||||
// 手动验证时间逻辑
|
||||
function validateTimes() {
|
||||
const now = dayjs();
|
||||
|
||||
if (form.value.launchDate && dayjs(form.value.launchDate).isBefore(now)) {
|
||||
return '预热时间必须在当前时间之后';
|
||||
}
|
||||
|
||||
if (form.value.subscriptionStartDate && form.value.launchDate) {
|
||||
if (dayjs(form.value.subscriptionStartDate).isBefore(dayjs(form.value.launchDate))) {
|
||||
return '申购开始时间必须在预热时间之后';
|
||||
}
|
||||
}
|
||||
|
||||
if (form.value.subscriptionEndDate && form.value.subscriptionStartDate) {
|
||||
if (dayjs(form.value.subscriptionEndDate).isBefore(dayjs(form.value.subscriptionStartDate))) {
|
||||
return '申购结束时间必须在开始时间之后';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
await safeClient(
|
||||
client.api.admin.rwa.issuance.editions.post({
|
||||
...form.value,
|
||||
launchDate: new Date(form.value.launchDate!),
|
||||
subscriptionStartDate: new Date(form.value.subscriptionStartDate!),
|
||||
subscriptionEndDate: new Date(form.value.subscriptionEndDate!)
|
||||
} as Body)
|
||||
);
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm
|
||||
ref="formInst"
|
||||
:model="form"
|
||||
label-width="150"
|
||||
label-placement="left"
|
||||
label-align="left"
|
||||
:rules="rules"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem path="editionName" label="发行期名称">
|
||||
<NInput v-model:value="form.editionName" />
|
||||
</NFormItem>
|
||||
<NFormItem path="perUserLimit" label="个人申购上限(份)">
|
||||
<NInputNumber
|
||||
:min="1"
|
||||
:step="10"
|
||||
:value="Number(form.perUserLimit)"
|
||||
@update:value="val => (form.perUserLimit = String(val))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="totalSupply" label="发行总量(份)">
|
||||
<NInputNumber
|
||||
:min="1"
|
||||
:step="100"
|
||||
:value="Number(form.totalSupply)"
|
||||
@update:value="val => (form.totalSupply = String(val))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="unitPrice" label="单价($)">
|
||||
<NInputNumber :min="0" :value="Number(form.unitPrice)" @update:value="val => (form.unitPrice = String(val))" />
|
||||
</NFormItem>
|
||||
<NFormItem path="dividendRate" label="分红率(%)">
|
||||
<NInputNumber
|
||||
:max="100"
|
||||
:min="0"
|
||||
:step="0.1"
|
||||
:precision="2"
|
||||
:value="Number(form.dividendRate) * 100"
|
||||
@update:value="val => (form.dividendRate = String((val || 0) / 100))"
|
||||
>
|
||||
<template #suffix>%</template>
|
||||
</NInputNumber>
|
||||
</NFormItem>
|
||||
<NFormItem path="launchDate" label="预热时间">
|
||||
<NDatePicker
|
||||
:value="dayjs(form.launchDate).valueOf()"
|
||||
type="datetime"
|
||||
format="yyyy-MM-dd HH:mm"
|
||||
:is-date-disabled="(ts: number) => ts < Date.now()"
|
||||
value-format="x"
|
||||
@update:value="val => (form.launchDate = new Date(val))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem>
|
||||
<NSpace vertical class="w-full">
|
||||
<NFormItem path="subscriptionStartDate" label="申购开始时间" label-width="150">
|
||||
<NDatePicker
|
||||
:value="dayjs(form.subscriptionStartDate).valueOf()"
|
||||
type="datetime"
|
||||
format="yyyy-MM-dd HH:mm"
|
||||
:is-date-disabled="(ts: number) => (form.launchDate ? ts < Number(form.launchDate) : false)"
|
||||
value-format="x"
|
||||
class="w-full"
|
||||
@update:value="val => (form.subscriptionStartDate = new Date(val))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="subscriptionEndDate" label="申购结束时间" label-width="150">
|
||||
<NDatePicker
|
||||
:value="dayjs(form.subscriptionEndDate).valueOf()"
|
||||
type="datetime"
|
||||
format="yyyy-MM-dd HH:mm"
|
||||
:is-date-disabled="
|
||||
(ts: number) => (form.subscriptionStartDate ? ts < Number(form.subscriptionStartDate) : false)
|
||||
"
|
||||
value-format="x"
|
||||
class="w-full"
|
||||
@update:value="val => (form.subscriptionEndDate = new Date(val))"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NSpace>
|
||||
</NFormItem>
|
||||
|
||||
<NSpace justify="end" class="mt-5">
|
||||
<NButton type="primary" @click="handleSubmit">修 改</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,148 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'RwaProductEdit' });
|
||||
|
||||
const props = defineProps<{
|
||||
data: Treaty.Data<typeof client.api.admin.rwa.issuance.products.post>;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.rwa.issuance.products.put>;
|
||||
|
||||
const formInst = useTemplateRef<FormInst>('formInst');
|
||||
const { data: categories } = safeClient(
|
||||
client.api.admin.rwa.category.categories.get({
|
||||
query: {
|
||||
pageIndex: 1,
|
||||
pageSize: 200
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const form = ref<Body>({
|
||||
id: props.data.id,
|
||||
iconifyIcon: props.data.iconifyIcon,
|
||||
name: props.data.name,
|
||||
code: props.data.code,
|
||||
categoryId: props.data.categoryId,
|
||||
description: props.data.description,
|
||||
estimatedValue: props.data.estimatedValue,
|
||||
totalSupplyLimit: props.data.totalSupplyLimit,
|
||||
proofDocumentIds: props.data.proofDocumentIds
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
iconifyIcon: [{ required: true, message: '请选择产品图标', trigger: ['blur', 'input'] }],
|
||||
name: [{ required: true, message: '请输入产品名称', trigger: ['blur', 'input'] }],
|
||||
code: [
|
||||
{ required: true, message: '请输入产品编号', trigger: ['blur', 'input'] },
|
||||
{
|
||||
trigger: ['blur'],
|
||||
asyncValidator: async (rule, value, callback) => {
|
||||
if (!value) return;
|
||||
const { data: check } = await safeClient(() =>
|
||||
client.api.admin.rwa.issuance.products.check_code.get({
|
||||
query: {
|
||||
code: value,
|
||||
excludeId: props.data.id
|
||||
}
|
||||
})
|
||||
);
|
||||
if (check.value?.exists) {
|
||||
callback(new Error('产品编号已存在'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
],
|
||||
categoryId: [{ required: true, message: '请输入产品类型', trigger: ['blur', 'input'] }],
|
||||
estimatedValue: [{ required: true, message: '请输入产品估值', trigger: ['blur', 'input'] }],
|
||||
totalSupplyLimit: [{ required: true, message: '请输入总发行量', trigger: ['blur', 'input'] }]
|
||||
};
|
||||
|
||||
function handleSubmit() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
await safeClient(
|
||||
client.api.admin.rwa.issuance.products.put({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm
|
||||
ref="formInst"
|
||||
:model="form"
|
||||
label-width="auto"
|
||||
label-placement="left"
|
||||
:rules="rules"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem path="iconifyIcon" label="产品图标 ">
|
||||
<IconPicker v-model="form.iconifyIcon" />
|
||||
</NFormItem>
|
||||
<NFormItem path="name" label="产品名称">
|
||||
<NInput v-model:value="form.name" />
|
||||
</NFormItem>
|
||||
<NFormItem path="code" label="产品编号">
|
||||
<NInput v-model:value="form.code" />
|
||||
</NFormItem>
|
||||
<NFormItem path="categoryId" label="产品类型">
|
||||
<NSelect
|
||||
:value="form.categoryId || null"
|
||||
:options="categories?.data?.map(item => ({ label: item.name, value: item.id }))"
|
||||
@update:value="val => (form.categoryId = val as string)"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="estimatedValue" label="产品估值">
|
||||
<NInputNumber
|
||||
:min="1"
|
||||
:step="100"
|
||||
:precision="2"
|
||||
:value="Number(form.estimatedValue)"
|
||||
@update:value="val => (form.estimatedValue = String(val || 0))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="totalSupplyLimit" label="总发行量">
|
||||
<NInputNumber
|
||||
:min="1"
|
||||
:step="100"
|
||||
:precision="0"
|
||||
:value="Number(form.totalSupplyLimit)"
|
||||
@update:value="val => (form.totalSupplyLimit = String(val || 0))"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem path="description" label="产品描述">
|
||||
<NInput v-model:value="form.description" type="textarea" />
|
||||
</NFormItem>
|
||||
<NFormItem path="proofDocumentIds" label="资产证明 ">
|
||||
<Upload
|
||||
:model-value="form.proofDocumentIds || []"
|
||||
:fetch-options="{
|
||||
businessType: 'rwa_proof'
|
||||
}"
|
||||
accept="application/pdf,image/*,.doc,.docx"
|
||||
@update:model-value="val => (form.proofDocumentIds = val)"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NSpace justify="end">
|
||||
<NButton @click="$emit('close')">取 消</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">确 认</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,257 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { useDialog } from 'naive-ui';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
|
||||
import { RwaEditionStatusEnum } from '@/enum';
|
||||
import AddEdition from './add-edition.vue';
|
||||
import EditEdition from './edit-edition.vue';
|
||||
import Allocations from './allocations.vue';
|
||||
|
||||
defineOptions({ name: 'RwaProductEditions' });
|
||||
|
||||
const props = defineProps<{
|
||||
data: Treaty.Data<typeof client.api.admin.rwa.issuance.products.get>['data'][number];
|
||||
}>();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
const dialog = useDialog();
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.rwa.issuance.editions.get({
|
||||
query: {
|
||||
productId: props.data.id,
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: '发行期名称',
|
||||
key: 'editionName',
|
||||
editComponent: 'NInput'
|
||||
},
|
||||
{
|
||||
title: '个人申购上限',
|
||||
key: 'perUserLimit',
|
||||
render: (row: any) => {
|
||||
return Number(row.perUserLimit).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '发行总量',
|
||||
key: 'totalSupply',
|
||||
render: (row: any) => {
|
||||
return Number(row.totalSupply).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '单价',
|
||||
key: 'unitPrice',
|
||||
render: (row: any) => {
|
||||
return Number(row.unitPrice).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '分红率',
|
||||
key: 'dividendRate',
|
||||
render: (row: any) => {
|
||||
return `${(Number(row.dividendRate) * 100).toFixed(2)}%`;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '预热时间',
|
||||
key: 'launchDate',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.launchDate, 'YYYY-MM-DD HH:mm').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '订阅开始时间',
|
||||
key: 'subscriptionStartDate',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.subscriptionStartDate, 'YYYY-MM-DD HH:mm').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '认购截止时间',
|
||||
key: 'subscriptionEndDate',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.subscriptionEndDate, 'YYYY-MM-DD HH:mm').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render: row => {
|
||||
return RwaEditionStatusEnum[row.status as keyof typeof RwaEditionStatusEnum];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 300,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '排期',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
visible: row.status === 'draft' || row.status === 'cancelled',
|
||||
text: true,
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '确认排期吗?',
|
||||
content: '排期后将对投资者可见,且不可修改。',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(client.api.admin.rwa.issuance.editions({ id: row.id }).publish.post());
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '取消排期',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
visible: row.status === 'scheduled',
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '确认取消发布该排期吗?',
|
||||
content: '取消排期将对投资者不可见,且不可修改。',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(client.api.admin.rwa.issuance.editions({ id: row.id }).cancel.post());
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '执行分红',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
// visible: row.status === 'scheduled',
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '确认执行分红吗?',
|
||||
content: '执行分红后将按照分红率向持有者分配收益。',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(
|
||||
client.api.admin.rwa.issuance.dividend.distribute.post({
|
||||
editionId: row.id as string
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '申购记录',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
visible: row.status === 'scheduled',
|
||||
onClick: () => handleAllocations(row)
|
||||
},
|
||||
{
|
||||
contentText: '编辑',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
// visible: row.status === 'draft',
|
||||
onClick: () => handleEdit(row)
|
||||
},
|
||||
{
|
||||
contentText: '删除',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
// visible: row.status === 'draft' || row.status === 'cancelled',
|
||||
onClick: () => {
|
||||
dialog.create({
|
||||
title: '确认删除该排期吗?',
|
||||
content: '删除后该排期将不可恢复,请谨慎操作。',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(client.api.admin.rwa.issuance.editions({ id: row.id }).delete());
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function handleAdd() {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '添加阶段',
|
||||
content: () =>
|
||||
h(AddEdition, {
|
||||
productId: props.data.id,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false,
|
||||
onPositiveClick: () => {
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleEdit(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑阶段',
|
||||
content: () =>
|
||||
h(EditEdition, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false,
|
||||
onPositiveClick: () => {
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleAllocations(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '分配记录',
|
||||
content: () => h(Allocations, { id: row.id }),
|
||||
style: { width: '800px', height: '600px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase ref="tableInst" title="RWA阶段" :columns="columns" :fetch-data="fetchData" @add="handleAdd" />
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,214 +0,0 @@
|
||||
<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>
|
||||
@@ -1,311 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { NDatePicker, useDialog, useMessage } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import { RwaStatusEnum } from '@/enum';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
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();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.rwa.issuance.products.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
key: 'selection',
|
||||
title: '序号',
|
||||
type: 'selection',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '产品图标',
|
||||
key: 'iconifyIcon',
|
||||
render: (row: any) => {
|
||||
return h(SvgIcon, { icon: row.iconifyIcon });
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '产品代码',
|
||||
key: 'code'
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '产品估值',
|
||||
key: 'estimatedValue',
|
||||
render: (row: any) => {
|
||||
return Number(row.estimatedValue).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '产品分类',
|
||||
key: 'category.name'
|
||||
},
|
||||
{
|
||||
title: '创建人ID',
|
||||
key: 'creator.username'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render: row => {
|
||||
return RwaStatusEnum[row.status as keyof typeof RwaStatusEnum];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 300,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '提交到审核',
|
||||
ghost: true,
|
||||
visible: row.status === 'draft',
|
||||
size: 'small',
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '提交到审核流程',
|
||||
content: '确认将该产品提交到审核流程吗?',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.rwa.issuance.products({ id: row.id }).submit.post({
|
||||
submissionNote: '提交审核'
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '批准',
|
||||
ghost: true,
|
||||
visible: row.status === 'under_review',
|
||||
size: 'small',
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '批准产品',
|
||||
content: '确认批准该产品吗?',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.rwa.issuance.approve.post({
|
||||
productId: row.id as string,
|
||||
publishFirstEdition: true
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '拒绝',
|
||||
ghost: true,
|
||||
visible: row.status === 'under_review',
|
||||
size: 'small',
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '批准产品',
|
||||
content: '确认批准该产品吗?',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.rwa.issuance.reject.post({
|
||||
productId: row.id as string,
|
||||
rejectionReason: '不符合要求'
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '编辑',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
// visible: row.status === 'draft' || row.status === 'rejected',
|
||||
onClick: () => {
|
||||
handleEdit(row);
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '删除',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
// visible: row.status === 'draft' || row.status === 'rejected',
|
||||
onClick: () => {
|
||||
dialog.create({
|
||||
title: '删除产品',
|
||||
content: '确认删除该产品吗,删除后不可恢复。',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() => client.api.admin.rwa.issuance.products({ id: row.id }).delete());
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '发行期',
|
||||
type: 'tertiary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
handleViewEditions(row);
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '代币化',
|
||||
type: 'tertiary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
handleTokenization(row);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: '产品代码',
|
||||
key: 'code'
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
component: NDatePicker
|
||||
}
|
||||
];
|
||||
|
||||
function handleTokenization(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '代币化产品',
|
||||
content: () =>
|
||||
h(Tokenization, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '添加产品',
|
||||
content: () =>
|
||||
h(Add, {
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false,
|
||||
onPositiveClick: () => {
|
||||
message.success('添加成功');
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑产品',
|
||||
content: () =>
|
||||
h(Edit, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
showIcon: false,
|
||||
onPositiveClick: () => {
|
||||
message.success('更新成功');
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleViewEditions(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '发行期列表',
|
||||
content: () =>
|
||||
h(Editions, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '80vw', minWidth: '1000px', height: '80vh' },
|
||||
showIcon: false,
|
||||
onPositiveClick: () => {
|
||||
message.success('添加成功');
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
show-header-operation
|
||||
:columns="columns"
|
||||
:filter-columns="filterColumns"
|
||||
:fetch-data="fetchData"
|
||||
@add="handleAdd"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,68 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.rwa.category.categories.post>;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
const formInst = useTemplateRef<FormInst>('formInst');
|
||||
const form = ref<Body>({
|
||||
name: '',
|
||||
code: '',
|
||||
description: '',
|
||||
displayOrder: 0
|
||||
});
|
||||
const rules: FormRules = {
|
||||
name: [{ required: true, message: '请输入分类名称', trigger: ['blur', 'input'] }],
|
||||
code: [{ required: true, message: '请输入分类编号', trigger: ['blur', 'input'] }]
|
||||
};
|
||||
|
||||
function handleSubmit() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
await safeClient(() =>
|
||||
client.api.admin.rwa.category.categories.post({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm
|
||||
ref="formInst"
|
||||
:model="form"
|
||||
label-width="auto"
|
||||
label-placement="left"
|
||||
:rules="rules"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem path="name" label="分类名称">
|
||||
<NInput v-model:value="form.name" />
|
||||
</NFormItem>
|
||||
<NFormItem path="code" label="分类编号">
|
||||
<NInput v-model:value="form.code" />
|
||||
</NFormItem>
|
||||
<NFormItem path="description" label="描述">
|
||||
<NInput v-model:value="form.description" type="textarea" />
|
||||
</NFormItem>
|
||||
<NFormItem path="displayOrder" label="排序">
|
||||
<NInputNumber v-model:value="form.displayOrder" />
|
||||
</NFormItem>
|
||||
|
||||
<NSpace justify="end">
|
||||
<NButton type="primary" ghost @click="$emit('close')">取 消</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">创 建</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,73 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.rwa.category.categories.post>;
|
||||
|
||||
const props = defineProps<{
|
||||
data: Treaty.Data<typeof client.api.admin.rwa.category.categories.post>;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
const formInst = useTemplateRef<FormInst>('formInst');
|
||||
const form = ref<Body>({
|
||||
id: props.data.id,
|
||||
name: props.data.name,
|
||||
code: props.data.code,
|
||||
description: props.data.description,
|
||||
displayOrder: props.data.displayOrder
|
||||
});
|
||||
const rules: FormRules = {
|
||||
name: [{ required: true, message: '请输入分类名称', trigger: ['blur', 'input'] }],
|
||||
code: [{ required: true, message: '请输入分类编号', trigger: ['blur', 'input'] }]
|
||||
};
|
||||
|
||||
function handleSubmit() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
await safeClient(() =>
|
||||
client.api.admin.rwa.category.categories.put({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm
|
||||
ref="formInst"
|
||||
:model="form"
|
||||
label-width="auto"
|
||||
label-placement="left"
|
||||
:rules="rules"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem path="name" label="分类名称">
|
||||
<NInput v-model:value="form.name" />
|
||||
</NFormItem>
|
||||
<NFormItem path="code" label="分类编号">
|
||||
<NInput v-model:value="form.code" />
|
||||
</NFormItem>
|
||||
<NFormItem path="description" label="描述">
|
||||
<NInput v-model:value="form.description" type="textarea" />
|
||||
</NFormItem>
|
||||
<NFormItem path="displayOrder" label="排序">
|
||||
<NInputNumber v-model:value="form.displayOrder" />
|
||||
</NFormItem>
|
||||
|
||||
<NSpace justify="end">
|
||||
<NButton type="primary" ghost @click="$emit('close')">取 消</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">更 新</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,121 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import Add from './components/add.vue';
|
||||
import Edit from './components/edit.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.rwa.category.categories.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '编号',
|
||||
key: 'code'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 160,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '编辑',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => handleEdit(row)
|
||||
},
|
||||
{
|
||||
contentText: '删除',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '提示',
|
||||
positiveText: '是',
|
||||
negativeText: '否',
|
||||
content: '确认删除该信息?',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() => client.api.admin.rwa.category.categories({ id: row.id as string }).delete());
|
||||
message.success('删除成功');
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function handleAdd() {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '添加产品类型',
|
||||
content: () =>
|
||||
h(Add, {
|
||||
onClose: () => {
|
||||
dialogInstance?.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(data: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑产品类型',
|
||||
content: () =>
|
||||
h(Edit, {
|
||||
data,
|
||||
onClose: () => {
|
||||
dialogInstance?.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' },
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase ref="tableInst" :columns="columns" :fetch-data="fetchData" @add="handleAdd" />
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,95 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
|
||||
import { RwaSubscribeStatusEnum } from '@/enum';
|
||||
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.rwa.subscription.orders.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
key: 'selection',
|
||||
title: '序号',
|
||||
type: 'selection',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userId'
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
key: 'product.name'
|
||||
},
|
||||
{
|
||||
title: '申购数量',
|
||||
key: 'quantity',
|
||||
render: (row: any) => {
|
||||
return Number(row.quantity).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '单价',
|
||||
key: 'unitPrice',
|
||||
render: (row: any) => {
|
||||
return Number(row.unitPrice).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '总价',
|
||||
key: 'totalAmount',
|
||||
render: (row: any) => {
|
||||
return Number(row.totalAmount).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '申购时间',
|
||||
key: 'createdAt',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(new Date(row.createdAt), 'YYYY-MM-DD HH:mm:ss').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render: (row: any) => {
|
||||
return RwaSubscribeStatusEnum[row.status as keyof typeof RwaSubscribeStatusEnum];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 200,
|
||||
operations: (row: any) => []
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
show-header-operation
|
||||
:header-operations="{
|
||||
add: false,
|
||||
refresh: true,
|
||||
columns: true
|
||||
}"
|
||||
:columns="columns"
|
||||
:fetch-data="fetchData"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,166 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'LockOptions' });
|
||||
|
||||
interface LockOption {
|
||||
months: number;
|
||||
rewardRate: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
options: LockOption[];
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formRef = useTemplateRef<FormInst | null>('formRef');
|
||||
const lockOptions = ref<LockOption[]>(cloneDeep(props.options));
|
||||
|
||||
const rules: FormRules = {};
|
||||
|
||||
function addLockOption() {
|
||||
const newOptions = [...lockOptions.value, { months: 12, rewardRate: '0.1' }];
|
||||
lockOptions.value = newOptions;
|
||||
}
|
||||
|
||||
function removeLockOption(index: number) {
|
||||
if (lockOptions.value.length > 1) {
|
||||
const newOptions = lockOptions.value.filter((_, i) => i !== index);
|
||||
lockOptions.value = newOptions;
|
||||
}
|
||||
}
|
||||
|
||||
function updateOption<K extends keyof LockOption>(index: number, field: K, value: LockOption[K]) {
|
||||
lockOptions.value[index][field] = value;
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
await safeClient(() =>
|
||||
client.api.admin.rwa.tokenization_schema({ id: props.id }).lock_options.patch({
|
||||
lockOptions: lockOptions.value
|
||||
})
|
||||
);
|
||||
window.$message?.success('锁仓选项更新成功');
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="{ lockOptions }"
|
||||
:rules="rules"
|
||||
label-width="auto"
|
||||
label-placement="left"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<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 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="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
|
||||
:value="option.months"
|
||||
:min="1"
|
||||
:max="120"
|
||||
:step="1"
|
||||
placeholder="请输入锁仓月数"
|
||||
class="w-full"
|
||||
@update:value="val => updateOption(index, 'months', val!)"
|
||||
>
|
||||
<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 => updateOption(index, '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>
|
||||
@@ -1,162 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { useDialog } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import { TokenizationStatusEnum } from '@/enum';
|
||||
import LockOptions from './components/lock-options.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.rwa.tokenization_schema.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: '链类型',
|
||||
key: 'chainType'
|
||||
},
|
||||
{
|
||||
title: '合约地址',
|
||||
key: 'contractAddress'
|
||||
},
|
||||
{
|
||||
title: '交易哈希',
|
||||
key: 'deployTxHash'
|
||||
},
|
||||
{
|
||||
title: '关联RWA产品',
|
||||
key: 'productId'
|
||||
},
|
||||
{
|
||||
title: '发行状态',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
render: (row: any) => {
|
||||
return TokenizationStatusEnum[row.status as keyof typeof TokenizationStatusEnum];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm:ss').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 300,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '启用资产化',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
visible: row.enabled === false,
|
||||
onClick: async () => {
|
||||
await safeClient(
|
||||
client.api.admin.rwa.tokenization_schema.enable.post({
|
||||
productId: row.productId,
|
||||
createTradingPair: true
|
||||
})
|
||||
);
|
||||
window.$message?.success('资产化已启用。');
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '关闭资产化',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
visible: row.enabled === true,
|
||||
onClick: async () => {
|
||||
await safeClient(client.api.admin.rwa.tokenization_schema({ id: row.id }).disable.patch());
|
||||
window.$message?.success('资产化已关闭。');
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '更新锁仓选项',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: async () => {
|
||||
handleUpdateLockOptions(row);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: '资产代码',
|
||||
key: 'assetCode'
|
||||
}
|
||||
];
|
||||
|
||||
function handleUpdateLockOptions(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '更新锁仓选项',
|
||||
content: () =>
|
||||
h(LockOptions, {
|
||||
options: row.lockOptions,
|
||||
id: row.id,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '600px' }
|
||||
});
|
||||
}
|
||||
</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>
|
||||
@@ -1,178 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.trading_pairs.post>;
|
||||
|
||||
defineOptions({
|
||||
name: 'TradingPairAdd'
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formInst = useTemplateRef<FormInst>('formInst');
|
||||
const form = ref<Body>({
|
||||
symbol: '',
|
||||
name: '',
|
||||
baseAsset: '',
|
||||
quoteAsset: 'USDT',
|
||||
description: '',
|
||||
minOrderAmount: '0',
|
||||
minOrderQuantity: '0',
|
||||
makerFeeRate: '0',
|
||||
takerFeeRate: '0',
|
||||
iconUrl: '',
|
||||
isActive: true,
|
||||
marginEnabled: false,
|
||||
maxLeverage: null,
|
||||
spotEnabled: true
|
||||
});
|
||||
|
||||
// 用于表单输入的临时数字值
|
||||
const tempValues = ref({
|
||||
minOrderAmount: 0,
|
||||
minOrderQuantity: 0,
|
||||
makerFeeRate: 0,
|
||||
takerFeeRate: 0,
|
||||
maxLeverage: 1
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
symbol: [{ required: true, message: '请输入交易对标识', trigger: ['blur', 'input'] }],
|
||||
name: [{ required: true, message: '请输入交易对显示名称', trigger: ['blur', 'input'] }],
|
||||
baseAsset: [{ required: true, message: '请输入基础资产代码', trigger: ['blur', 'input'] }],
|
||||
quoteAsset: [{ required: true, message: '请输入计价资产代码', trigger: ['blur', 'input'] }],
|
||||
minOrderAmount: [{ required: true, message: '请输入最小下单金额', trigger: ['blur', 'change'] }],
|
||||
minOrderQuantity: [{ required: true, message: '请输入最小下单数量', trigger: ['blur', 'change'] }],
|
||||
makerFeeRate: [{ required: true, message: '请输入挂单手续费率', trigger: ['blur', 'change'] }],
|
||||
takerFeeRate: [{ required: true, message: '请输入成交手续费率', trigger: ['blur', 'change'] }]
|
||||
};
|
||||
|
||||
const showMaxLeverage = computed(() => form.value.marginEnabled);
|
||||
const { data: assets } = safeClient(client.api.admin.assets.get());
|
||||
const assetsOption = computed(() => {
|
||||
return assets.value?.data
|
||||
.filter(item => item.code !== 'USDT')
|
||||
.map(item => ({
|
||||
label: item.code,
|
||||
value: item.code
|
||||
}));
|
||||
});
|
||||
|
||||
// 同步数字输入到表单字符串
|
||||
function syncNumberToString() {
|
||||
form.value.minOrderAmount = tempValues.value.minOrderAmount.toString();
|
||||
form.value.minOrderQuantity = tempValues.value.minOrderQuantity.toString();
|
||||
form.value.makerFeeRate = (tempValues.value.makerFeeRate / 100).toString();
|
||||
form.value.takerFeeRate = (tempValues.value.takerFeeRate / 100).toString();
|
||||
if (form.value.marginEnabled) {
|
||||
form.value.maxLeverage = tempValues.value.maxLeverage;
|
||||
} else {
|
||||
form.value.maxLeverage = null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
syncNumberToString();
|
||||
await safeClient(() => client.api.admin.trading_pairs.post(form.value));
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm
|
||||
ref="formInst"
|
||||
:model="form"
|
||||
label-width="140px"
|
||||
label-placement="left"
|
||||
:rules="rules"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem path="name" label="交易对显示名称">
|
||||
<NInput v-model:value="form.name" placeholder="如:比特币/泰达币" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem path="iconUrl" label="交易对图标">
|
||||
<IconPicker v-model="form.iconUrl" :collections="['cryptocurrency-color']" />
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi path="baseAsset" label="基础资产代码">
|
||||
<NSelect
|
||||
:value="form.baseAsset || undefined"
|
||||
:options="assetsOption"
|
||||
placeholder="如:BTC"
|
||||
@update:value="val => (form.baseAsset = val)"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="quoteAsset" label="计价资产代码">
|
||||
<NInput v-model:value="form.quoteAsset" placeholder="如:USDT" readonly disabled />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi path="minOrderAmount" label="最小下单金额(USDT)">
|
||||
<NInputNumber v-model:value="tempValues.minOrderAmount" :min="0" :precision="2" class="w-full" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="minOrderQuantity" label="最小下单数量">
|
||||
<NInputNumber v-model:value="tempValues.minOrderQuantity" :min="0" :precision="8" class="w-full" />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi path="makerFeeRate" label="挂单手续费率">
|
||||
<NInputNumber v-model:value="tempValues.makerFeeRate" :min="0" :max="100" :step="0.01" class="w-full">
|
||||
<template #suffix>%</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="takerFeeRate" label="成交手续费率">
|
||||
<NInputNumber v-model:value="tempValues.takerFeeRate" :min="0" :max="100" :step="0.01" class="w-full">
|
||||
<template #suffix>%</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem path="description" label="交易对描述">
|
||||
<NInput v-model:value="form.description" type="textarea" placeholder="请输入交易对描述" />
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi path="spotEnabled" label="是否支持现货交易">
|
||||
<NSwitch v-model:value="form.spotEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="isActive" label="是否启用">
|
||||
<NSwitch v-model:value="form.isActive" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="marginEnabled" label="是否支持杠杆交易">
|
||||
<NSwitch v-model:value="form.marginEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi v-if="showMaxLeverage" path="maxLeverage" label="最大杠杆倍数">
|
||||
<NInputNumber v-model:value="tempValues.maxLeverage" :min="1" :max="100" :precision="0" class="w-full">
|
||||
<template #suffix>倍</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NSpace justify="end">
|
||||
<NButton type="primary" ghost @click="$emit('close')">取 消</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">创 建</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,193 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
type Body = CommonType.TreatyBody<typeof client.api.admin.trading_pairs.post>;
|
||||
|
||||
defineOptions({
|
||||
name: 'TradingPairEdit'
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
data: Body;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formInst = useTemplateRef<FormInst>('formInst');
|
||||
const form = ref<Body>({
|
||||
symbol: props.data.symbol,
|
||||
name: props.data.name,
|
||||
baseAsset: props.data.baseAsset,
|
||||
quoteAsset: props.data.quoteAsset,
|
||||
description: props.data.description,
|
||||
minOrderAmount: props.data.minOrderAmount,
|
||||
minOrderQuantity: props.data.minOrderQuantity,
|
||||
makerFeeRate: props.data.makerFeeRate,
|
||||
takerFeeRate: props.data.takerFeeRate,
|
||||
pricePrecision: props.data.pricePrecision,
|
||||
iconUrl: props.data.iconUrl,
|
||||
isActive: props.data.isActive,
|
||||
marginEnabled: props.data.marginEnabled,
|
||||
maxLeverage: props.data.maxLeverage,
|
||||
spotEnabled: props.data.spotEnabled
|
||||
});
|
||||
|
||||
// 用于表单输入的临时数字值
|
||||
const tempValues = ref({
|
||||
minOrderAmount: 0,
|
||||
minOrderQuantity: 0,
|
||||
makerFeeRate: 0,
|
||||
takerFeeRate: 0,
|
||||
maxLeverage: 1
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
symbol: [{ required: true, message: '请输入交易对标识', trigger: ['blur', 'input'] }],
|
||||
name: [{ required: true, message: '请输入交易对显示名称', trigger: ['blur', 'input'] }],
|
||||
baseAsset: [{ required: true, message: '请输入基础资产代码', trigger: ['blur', 'input'] }],
|
||||
quoteAsset: [{ required: true, message: '请输入计价资产代码', trigger: ['blur', 'input'] }],
|
||||
minOrderAmount: [{ required: true, message: '请输入最小下单金额', trigger: ['blur', 'change'] }],
|
||||
minOrderQuantity: [{ required: true, message: '请输入最小下单数量', trigger: ['blur', 'change'] }],
|
||||
makerFeeRate: [{ required: true, message: '请输入挂单手续费率', trigger: ['blur', 'change'] }],
|
||||
takerFeeRate: [{ required: true, message: '请输入成交手续费率', trigger: ['blur', 'change'] }]
|
||||
};
|
||||
|
||||
const showMaxLeverage = computed(() => form.value.marginEnabled);
|
||||
const { data: assets } = safeClient(client.api.admin.assets.get());
|
||||
const assetsOption = computed(() => {
|
||||
return assets.value?.data
|
||||
.filter(item => item.code !== 'USDT')
|
||||
.map(item => ({
|
||||
label: item.code,
|
||||
value: item.code
|
||||
}));
|
||||
});
|
||||
|
||||
// 同步数字输入到表单字符串
|
||||
function syncNumberToString() {
|
||||
form.value.minOrderAmount = tempValues.value.minOrderAmount.toString();
|
||||
form.value.minOrderQuantity = tempValues.value.minOrderQuantity.toString();
|
||||
form.value.makerFeeRate = (tempValues.value.makerFeeRate / 100).toString();
|
||||
form.value.takerFeeRate = (tempValues.value.takerFeeRate / 100).toString();
|
||||
if (form.value.marginEnabled) {
|
||||
form.value.maxLeverage = tempValues.value.maxLeverage;
|
||||
} else {
|
||||
form.value.maxLeverage = null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formInst.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
syncNumberToString();
|
||||
await safeClient(() =>
|
||||
client.api.admin.trading_pairs.patch(form.value, {
|
||||
query: { symbol: props.data.symbol }
|
||||
})
|
||||
);
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm
|
||||
ref="formInst"
|
||||
:model="form"
|
||||
label-width="140px"
|
||||
label-placement="left"
|
||||
:rules="rules"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem path="name" label="交易对显示名称">
|
||||
<NInput v-model:value="form.name" placeholder="如:比特币/泰达币" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem path="iconUrl" label="交易对图标">
|
||||
<IconPicker v-model="form.iconUrl" :collections="['cryptocurrency-color']" />
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi path="baseAsset" label="基础资产代码">
|
||||
<NSelect
|
||||
:value="form.baseAsset || undefined"
|
||||
:options="assetsOption"
|
||||
placeholder="如:BTC"
|
||||
@update:value="val => (form.baseAsset = val)"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="quoteAsset" label="计价资产代码">
|
||||
<NInput v-model:value="form.quoteAsset" placeholder="如:USDT" readonly disabled />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi path="minOrderAmount" label="最小下单金额(USDT)">
|
||||
<NInputNumber v-model:value="tempValues.minOrderAmount" :min="0" :precision="2" class="w-full" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="minOrderQuantity" label="最小下单数量">
|
||||
<NInputNumber v-model:value="tempValues.minOrderQuantity" :min="0" :precision="8" class="w-full" />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi path="makerFeeRate" label="挂单手续费率">
|
||||
<NInputNumber v-model:value="tempValues.makerFeeRate" :min="0" :max="100" :step="0.01" class="w-full">
|
||||
<template #suffix>%</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="takerFeeRate" label="成交手续费率">
|
||||
<NInputNumber v-model:value="tempValues.takerFeeRate" :min="0" :max="100" :step="0.01" class="w-full">
|
||||
<template #suffix>%</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NFormItem path="pricePrecision" label="价格小数位数">
|
||||
<NInputNumber v-model:value="form.pricePrecision" :min="0" :max="8" :precision="0" class="w-full">
|
||||
<template #suffix>位</template>
|
||||
</NInputNumber>
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem path="description" label="交易对描述">
|
||||
<NInput v-model:value="form.description" type="textarea" placeholder="请输入交易对描述" />
|
||||
</NFormItem>
|
||||
|
||||
<NGrid :cols="2" :x-gap="12">
|
||||
<NFormItemGi path="spotEnabled" label="是否支持现货交易">
|
||||
<NSwitch v-model:value="form.spotEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="isActive" label="是否启用">
|
||||
<NSwitch v-model:value="form.isActive" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi path="marginEnabled" label="是否支持杠杆交易">
|
||||
<NSwitch v-model:value="form.marginEnabled" />
|
||||
</NFormItemGi>
|
||||
|
||||
<NFormItemGi v-if="showMaxLeverage" path="maxLeverage" label="最大杠杆倍数">
|
||||
<NInputNumber v-model:value="tempValues.maxLeverage" :min="1" :max="100" :precision="0" class="w-full">
|
||||
<template #suffix>倍</template>
|
||||
</NInputNumber>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
|
||||
<NSpace justify="end">
|
||||
<NButton type="primary" ghost @click="$emit('close')">取 消</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">保 存</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,251 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { NInput, NSelect, NTag, useDialog } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import Add from './components/add.vue';
|
||||
import Edit from './components/edit.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.trading_pairs.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: '交易对标识',
|
||||
key: 'symbol',
|
||||
width: 150,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '基础资产代码',
|
||||
key: 'baseAsset',
|
||||
width: 130
|
||||
},
|
||||
{
|
||||
title: '计价资产代码',
|
||||
key: 'quoteAsset',
|
||||
width: 130
|
||||
},
|
||||
|
||||
{
|
||||
title: '挂单手续费率',
|
||||
key: 'makerFeeRate',
|
||||
width: 140,
|
||||
render: row => {
|
||||
return `${(Number(row.makerFeeRate) * 100).toFixed(2)}%`;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '成交手续费率',
|
||||
key: 'takerFeeRate',
|
||||
width: 140,
|
||||
render: row => {
|
||||
return `${(Number(row.takerFeeRate) * 100).toFixed(2)}%`;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '最小下单金额(USDT)',
|
||||
key: 'minOrderAmount',
|
||||
width: 170,
|
||||
render: row => {
|
||||
return Number(row.minOrderAmount).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '最大下单金额(USDT)',
|
||||
key: 'maxOrderAmount',
|
||||
width: 170,
|
||||
render: row => {
|
||||
return Number(row.maxOrderAmount).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '最小下单数量',
|
||||
key: 'minOrderQuantity',
|
||||
width: 130,
|
||||
render: row => {
|
||||
return Number(row.minOrderQuantity).toFixed(8);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '最大下单数量',
|
||||
key: 'maxOrderQuantity',
|
||||
width: 130,
|
||||
render: row => {
|
||||
return Number(row.maxOrderQuantity).toFixed(8);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '价格小数位数',
|
||||
key: 'pricePrecision',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '数量小数位数',
|
||||
key: 'quantityPrecision',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '排序权重',
|
||||
key: 'sortOrder',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '是否启用',
|
||||
key: 'isActive',
|
||||
width: 140,
|
||||
align: 'center',
|
||||
render: row => {
|
||||
return row.isActive ? '是' : '否';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 200,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '编辑',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
handleEdit(row);
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '删除',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
dialog.create({
|
||||
title: '删除确认',
|
||||
content: '确认删除该交易对吗,删除后不可恢复。',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.trading_pairs.delete(undefined, { query: { symbol: row.symbol } })
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: '关键词',
|
||||
key: 'keyword',
|
||||
component: NInput,
|
||||
componentProps: {
|
||||
placeholder: '请输入交易对名称或标识',
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '基础资产',
|
||||
key: 'baseAsset',
|
||||
component: NInput,
|
||||
componentProps: {
|
||||
placeholder: '请输入基础资产代码',
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '计价资产',
|
||||
key: 'quoteAsset',
|
||||
component: NInput,
|
||||
componentProps: {
|
||||
placeholder: '请输入计价资产代码',
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否启用',
|
||||
key: 'isActive',
|
||||
component: NSelect,
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
clearable: true,
|
||||
options: [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false }
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function handleAdd() {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '添加交易对',
|
||||
showIcon: false,
|
||||
content: () =>
|
||||
h(Add, {
|
||||
onClose: () => {
|
||||
dialogInstance?.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '800px' },
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
function handleEdit(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑交易对',
|
||||
showIcon: false,
|
||||
content: () =>
|
||||
h(Edit, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance?.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '800px' },
|
||||
closable: true
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
:columns="columns"
|
||||
:filter-columns="filterColumns"
|
||||
:fetch-data="fetchData"
|
||||
:scroll-x="3000"
|
||||
@add="handleAdd"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,140 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { NInput, NSelect, NTag } from 'naive-ui';
|
||||
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.transfer.get({
|
||||
query: {
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// 状态枚举映射
|
||||
const TransferStatusEnum = {
|
||||
pending: '待完成',
|
||||
completed: '已完成',
|
||||
failed: '失败'
|
||||
};
|
||||
|
||||
// 状态标签类型映射
|
||||
const getStatusTagType = (status: string) => {
|
||||
const typeMap: Record<string, 'warning' | 'success' | 'error'> = {
|
||||
pending: 'warning',
|
||||
completed: 'success',
|
||||
failed: 'error'
|
||||
};
|
||||
return typeMap[status] || 'default';
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: '订单ID',
|
||||
key: 'id',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '订单号',
|
||||
key: 'orderNo',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '转出账户ID',
|
||||
key: 'fromUserId',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '资产代码',
|
||||
key: 'assetCode',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '金额',
|
||||
key: 'amount',
|
||||
width: 150,
|
||||
render: row => {
|
||||
return Number(row.amount).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '手续费',
|
||||
key: 'fee',
|
||||
width: 120,
|
||||
render: row => {
|
||||
return Number(row.fee || 0).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 120,
|
||||
render: (row: any) => {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: getStatusTagType(row.status)
|
||||
},
|
||||
{
|
||||
default: () => TransferStatusEnum[row.status as keyof typeof TransferStatusEnum] || row.status
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm:ss').value;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userId',
|
||||
component: NInput,
|
||||
componentProps: {
|
||||
placeholder: '请输入用户ID',
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '资产代码',
|
||||
key: 'assetCode',
|
||||
component: NInput,
|
||||
componentProps: {
|
||||
placeholder: '请输入资产代码',
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
component: NSelect,
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
clearable: true,
|
||||
options: [
|
||||
{ label: '待完成', value: 'pending' },
|
||||
{ label: '已完成', value: 'completed' },
|
||||
{ label: '失败', value: 'failed' }
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase ref="tableInst" :columns="columns" :filter-columns="filterColumns" :fetch-data="fetchData" />
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,134 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'EditBankCard' });
|
||||
|
||||
type Data = Treaty.Data<typeof client.api.admin.bank_account.get>['data'][number];
|
||||
|
||||
const props = defineProps<{
|
||||
data: Data;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formRef = useTemplateRef<FormInst | null>('formRef');
|
||||
|
||||
const form = ref({
|
||||
accountName: props.data.accountName,
|
||||
bankName: props.data.bankName,
|
||||
bankCode: props.data.bankCode,
|
||||
riskStatus: props.data.riskStatus,
|
||||
isVerified: props.data.isVerified || false
|
||||
});
|
||||
|
||||
const riskStatusOptions = [
|
||||
{ label: '低风险', value: 'low' },
|
||||
{ label: '中风险', value: 'medium' },
|
||||
{ label: '高风险', value: 'high' }
|
||||
];
|
||||
|
||||
const rules: FormRules = {
|
||||
accountName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入持卡人姓名',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 50,
|
||||
message: '姓名长度应在2-50个字符之间',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
bankName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入银行名称',
|
||||
trigger: ['blur', 'input']
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 100,
|
||||
message: '银行名称长度应在2-100个字符之间',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
bankCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入银行编号',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
riskStatus: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择风险等级',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
async function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (!errors) {
|
||||
const { data } = await safeClient(() =>
|
||||
client.api.admin.bank_account({ id: props.data.id }).patch({
|
||||
...form.value
|
||||
})
|
||||
);
|
||||
if (data) {
|
||||
window.$message?.success('银行卡信息更新成功');
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-placement="left"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem label="持卡人姓名" path="accountName">
|
||||
<NInput v-model:value="form.accountName" placeholder="请输入持卡人姓名" maxlength="50" show-count />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="银行名称" path="bankName">
|
||||
<NInput v-model:value="form.bankName" placeholder="请输入银行名称" maxlength="100" show-count />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="银行编号" path="bankCode">
|
||||
<NInput v-model:value="form.bankCode" placeholder="请输入银行编号" maxlength="50" show-count />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="风险等级" path="riskStatus">
|
||||
<NSelect v-model:value="form.riskStatus" :options="riskStatusOptions" placeholder="请选择风险等级" />
|
||||
</NFormItem>
|
||||
|
||||
<NFormItem label="是否认证" path="isVerified">
|
||||
<NSwitch v-model:value="form.isVerified">
|
||||
<template #checked>已认证</template>
|
||||
<template #unchecked>未认证</template>
|
||||
</NSwitch>
|
||||
</NFormItem>
|
||||
|
||||
<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>
|
||||
@@ -1,135 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import Edit from './components/edit.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.bank_account.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'userId'
|
||||
},
|
||||
{
|
||||
title: '持卡人姓名',
|
||||
key: 'accountName'
|
||||
},
|
||||
{
|
||||
title: '银行卡名称',
|
||||
key: 'bankName'
|
||||
},
|
||||
{
|
||||
title: '银行编号',
|
||||
key: 'bankCode'
|
||||
},
|
||||
{
|
||||
title: '风险等级',
|
||||
key: 'riskStatus'
|
||||
},
|
||||
{
|
||||
title: '是否认证',
|
||||
key: 'isVerified',
|
||||
render: (row: any) => (row.isVerified ? '是' : '否')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 160,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '编辑',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
handleEdit(row);
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '删除',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '提示',
|
||||
positiveText: '是',
|
||||
negativeText: '否',
|
||||
content: '确认删除该银行信息?',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.deposit.reject({ orderId: row.id as string }).post({
|
||||
reviewNote: '管理员拒绝充值'
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
message.success('删除成功');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: '银行名称',
|
||||
key: 'bankName'
|
||||
},
|
||||
{
|
||||
title: '银行编号',
|
||||
key: 'bankCode'
|
||||
}
|
||||
];
|
||||
|
||||
function handleEdit(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑银行卡信息',
|
||||
content: () =>
|
||||
h(Edit, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '800px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
show-header-operation
|
||||
:columns="columns"
|
||||
:filter-columns="filterColumns"
|
||||
:fetch-data="fetchData"
|
||||
:scroll-x="800"
|
||||
:header-operations="{
|
||||
add: false,
|
||||
refresh: true,
|
||||
columns: true
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,120 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import type { FormInst, FormRules } from 'naive-ui';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'UserEditForm' });
|
||||
|
||||
type Data = Treaty.Data<typeof client.api.admin.users.get>['data'][number];
|
||||
|
||||
const props = defineProps<{
|
||||
data: Data;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const formInst = useTemplateRef<FormInst>('formInst');
|
||||
|
||||
const form = ref({
|
||||
userId: props.data.userId,
|
||||
nickname: props.data.nickname,
|
||||
email: props.data.user?.email || '',
|
||||
phoneNumber: props.data.user?.phoneNumber || '',
|
||||
gender: props.data.gender,
|
||||
language: props.data.language
|
||||
});
|
||||
|
||||
const genderOptions = [
|
||||
{ label: '男', value: 'male' },
|
||||
{ label: '女', value: 'female' },
|
||||
{ label: '其他', value: 'other' }
|
||||
];
|
||||
|
||||
const languageOptions = [
|
||||
{ label: '简体中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en-US' },
|
||||
{ label: '繁體中文', value: 'zh-TW' }
|
||||
];
|
||||
|
||||
const rules: FormRules = {
|
||||
nickname: [
|
||||
{ required: true, message: '请输入用户名', trigger: ['blur', 'input'] },
|
||||
{ min: 2, max: 20, message: '用户名长度为2-20个字符', trigger: ['blur', 'input'] }
|
||||
],
|
||||
email: [
|
||||
{ required: false, message: '请输入邮箱', trigger: ['blur', 'input'] },
|
||||
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'input'] }
|
||||
],
|
||||
phoneNumber: [{ required: false, message: '请输入手机号', trigger: ['blur', 'input'] }]
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
async function handleSubmit() {
|
||||
await formInst.value?.validate(async errors => {
|
||||
if (errors) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
await safeClient(() =>
|
||||
client.api.admin.users({ userId: form.value.userId }).profile.put({
|
||||
nickname: form.value.nickname,
|
||||
gender: form.value.gender,
|
||||
language: form.value.language
|
||||
})
|
||||
);
|
||||
window.$message?.success('更新成功');
|
||||
emit('close');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-10">
|
||||
<NForm
|
||||
ref="formInst"
|
||||
:model="form"
|
||||
label-width="auto"
|
||||
label-placement="left"
|
||||
:rules="rules"
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<NFormItem path="userId" label="用户ID">
|
||||
<NInput :value="data.userId" disabled />
|
||||
</NFormItem>
|
||||
<NFormItem path="uid" label="UID">
|
||||
<NInput :value="data.uid" disabled />
|
||||
</NFormItem>
|
||||
<NFormItem path="nickname" label="用户名">
|
||||
<NInput v-model:value="form.nickname" placeholder="请输入用户名" />
|
||||
</NFormItem>
|
||||
<NFormItem path="email" label="邮箱">
|
||||
<NInput :value="form.email" disabled placeholder="邮箱不可修改" />
|
||||
</NFormItem>
|
||||
<NFormItem path="phoneNumber" label="手机号">
|
||||
<NInput :value="form.phoneNumber" disabled placeholder="手机号不可修改" />
|
||||
</NFormItem>
|
||||
<NFormItem path="referralCode" label="推荐人ID">
|
||||
<NInput :value="data.referralCode || '无'" disabled />
|
||||
</NFormItem>
|
||||
<NFormItem path="gender" label="性别">
|
||||
<NSelect v-model:value="form.gender" :options="genderOptions" placeholder="请选择性别" clearable />
|
||||
</NFormItem>
|
||||
<NFormItem path="language" label="语言">
|
||||
<NSelect v-model:value="form.language" :options="languageOptions" placeholder="请选择语言" />
|
||||
</NFormItem>
|
||||
<NSpace justify="end">
|
||||
<NButton @click="$emit('close')">取 消</NButton>
|
||||
<NButton type="primary" :loading="loading" @click="handleSubmit">确 认</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,132 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { NInputNumber, useDialog, useMessage } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import { DepositTypeEnum } from '@/enum';
|
||||
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
|
||||
import EditForm from './components/edit.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.users.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userId'
|
||||
},
|
||||
{
|
||||
title: 'UID',
|
||||
key: 'uid'
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
key: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
key: 'user.email'
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
key: 'user.phoneNumber'
|
||||
},
|
||||
{
|
||||
title: '推荐人ID',
|
||||
key: 'referralCode'
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
key: 'gender'
|
||||
},
|
||||
{
|
||||
title: '语言',
|
||||
key: 'language'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 100,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '编辑',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
handleEdit(row);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableBaseColumns = [
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userId'
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
key: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
key: 'email'
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
key: 'phoneNumber'
|
||||
},
|
||||
{
|
||||
title: '推荐人ID',
|
||||
key: 'referralCode'
|
||||
}
|
||||
];
|
||||
|
||||
function handleEdit(row: any) {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '编辑用户',
|
||||
content: () =>
|
||||
h(EditForm, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
}),
|
||||
style: { width: '700px' },
|
||||
showIcon: false
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
show-header-operation
|
||||
:columns="columns"
|
||||
:fetch-data="fetchData"
|
||||
:filter-columns="filterColumns"
|
||||
:scroll-x="1400"
|
||||
:header-operations="{
|
||||
add: false,
|
||||
refresh: true,
|
||||
columns: true
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,70 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
|
||||
defineOptions({ name: 'WithdrawApprovedComplete' });
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
data: Treaty.Data<typeof client.api.admin.withdraw.approved.get>['data'][number];
|
||||
}>();
|
||||
|
||||
const withdrawType = computed(() => props.data.withdrawMethod);
|
||||
|
||||
const input = ref('');
|
||||
const loading = ref(false);
|
||||
|
||||
async function handleSubmit() {
|
||||
loading.value = true;
|
||||
await safeClient(
|
||||
client.api.admin.withdraw({ orderId: props.data.id }).complete.post({
|
||||
bankTransferProof: withdrawType.value === 'bank' ? input.value : undefined,
|
||||
txHash: withdrawType.value === 'crypto' ? input.value : undefined,
|
||||
cashPickupCode: withdrawType.value === 'cash' ? input.value : undefined
|
||||
})
|
||||
).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
window.$message?.success('操作成功');
|
||||
setTimeout(() => {
|
||||
emit('close');
|
||||
}, 300);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical size="large" class="my-5">
|
||||
<div>
|
||||
<div v-if="withdrawType === 'bank'">
|
||||
<div class="text-md">转账人卡号</div>
|
||||
<div class="text-2xl">{{ data.bankAccountId }}</div>
|
||||
</div>
|
||||
<div v-else-if="withdrawType === 'crypto'">
|
||||
<div class="text-md">转账人链上地址</div>
|
||||
<div class="text-2xl">{{ data.toAddress }}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="text-md">现金提取码</div>
|
||||
<div class="text-2xl">{{ data.cashPickupCode }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<NForm>
|
||||
<NFormItem v-if="withdrawType === 'bank'" label="银行转账凭证">
|
||||
<NInput v-model:value="input" placeholder="银行转账凭证(可选)" />
|
||||
</NFormItem>
|
||||
<NFormItem v-else-if="withdrawType === 'crypto'" label="链上交易哈希">
|
||||
<NInput v-model:value="input" placeholder="链上交易哈希(可选)" />
|
||||
</NFormItem>
|
||||
|
||||
<NSpace justify="end">
|
||||
<NButton @click="emit('close')">取 消</NButton>
|
||||
<NButton type="primary" :loading="loading" @click="handleSubmit">通 过</NButton>
|
||||
</NSpace>
|
||||
</NForm>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,53 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import type { Treaty } from '@elysiajs/eden';
|
||||
import type { client } from '@/service/api';
|
||||
import { WithdrawMethodEnum, WithdrawStatusEnum } from '@/enum';
|
||||
|
||||
const props = defineProps<{
|
||||
data: Treaty.Data<typeof client.api.admin.withdraw.approved.get>['data'][number];
|
||||
}>();
|
||||
const withdrawToWhere = computed(() => {
|
||||
if (props.data.withdrawMethod === 'bank') {
|
||||
return `银行账户: ${props.data.bankAccountId}`;
|
||||
} else if (props.data.withdrawMethod === 'crypto') {
|
||||
return `加密地址: ${props.data.toAddress}`;
|
||||
}
|
||||
return `现金提取码: ${props.data.cashPickupCode}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDescriptions label-placement="top" bordered :column="2" class="my-4">
|
||||
<NDescriptionsItem label="ID">
|
||||
{{ data.id }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="资产账户">
|
||||
{{ data.assetCode }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="提现金额">
|
||||
{{ Number(data.amount).toFixed(2) }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="实际到账金额">
|
||||
{{ Number(data.actualAmount).toFixed(2) }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="提现方式">
|
||||
{{ WithdrawMethodEnum[data.withdrawMethod as keyof typeof WithdrawMethodEnum] }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="手续费">
|
||||
{{ Number(data.fee).toFixed(2) }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="提现去向">
|
||||
{{ withdrawToWhere }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="状态">
|
||||
{{ WithdrawStatusEnum[data.status as keyof typeof WithdrawStatusEnum] }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="创建时间">
|
||||
{{ useDateFormat(data.createdAt, 'YYYY-MM-DD HH:mm') }}
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,163 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import { WithdrawMethodEnum, WithdrawStatusEnum } from '@/enum';
|
||||
import Complete from './components/complete.vue';
|
||||
import Info from './components/info.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.withdraw.approved.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '资产账户',
|
||||
key: 'assetCode'
|
||||
},
|
||||
{
|
||||
title: '提现金额',
|
||||
key: 'amount',
|
||||
render: row => {
|
||||
return Number(row.amount).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '实际到账金额',
|
||||
key: 'actualAmount',
|
||||
render: row => {
|
||||
return Number(row.actualAmount).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '提现方式',
|
||||
key: 'withdrawMethod',
|
||||
render: row => {
|
||||
return WithdrawMethodEnum[row.withdrawMethod as keyof typeof WithdrawMethodEnum];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '手续费',
|
||||
key: 'fee',
|
||||
render: row => {
|
||||
return Number(row.fee).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render: row => {
|
||||
return WithdrawStatusEnum[row.status as keyof typeof WithdrawStatusEnum];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
key: 'operation',
|
||||
width: 240,
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '完成',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
onClick: () => {
|
||||
const dialogInstance = dialog.create({
|
||||
title: '提示',
|
||||
style: { width: '600px' },
|
||||
content: () =>
|
||||
h(Complete, {
|
||||
data: row,
|
||||
onClose: () => {
|
||||
dialogInstance.destroy();
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '拒绝',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
onClick: async () => {
|
||||
dialog.create({
|
||||
title: '提示',
|
||||
positiveText: '是',
|
||||
negativeText: '否',
|
||||
content: '确定拒绝该提现申请吗?',
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.deposit.reject({ orderId: row.id as string }).post({
|
||||
reviewNote: '管理员拒绝充值'
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
message.success('拒绝成功');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '详情',
|
||||
type: 'tertiary',
|
||||
ghost: true,
|
||||
onClick: () => {
|
||||
dialog.create({
|
||||
title: '提现详情',
|
||||
style: { width: '800px' },
|
||||
content: () => h(Info, { data: row })
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: '提现金额',
|
||||
key: 'amount'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
show-header-operation
|
||||
:columns="columns"
|
||||
:filter-columns="filterColumns"
|
||||
:fetch-data="fetchData"
|
||||
:header-operations="{
|
||||
add: false,
|
||||
refresh: true,
|
||||
columns: true
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped></style>
|
||||
@@ -1,190 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { h, ref, useTemplateRef } from 'vue';
|
||||
import { useDateFormat } from '@vueuse/core';
|
||||
import { NDatePicker, NInput, useDialog } from 'naive-ui';
|
||||
import { client, safeClient } from '@/service/api';
|
||||
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
|
||||
import TableBase from '@/components/table/table-base.vue';
|
||||
import { WithdrawMethodEnum, WithdrawStatusEnum } from '@/enum';
|
||||
|
||||
const tableInst = useTemplateRef<TableInst>('tableInst');
|
||||
const dialog = useDialog();
|
||||
|
||||
const fetchData: TableFetchData = ({ pagination, filter }) => {
|
||||
return safeClient(() =>
|
||||
client.api.admin.withdraw.pending.get({
|
||||
query: {
|
||||
...pagination,
|
||||
...filter
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const columns: TableBaseColumns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '资产账户',
|
||||
key: 'assetCode'
|
||||
},
|
||||
{
|
||||
title: '提现金额',
|
||||
key: 'amount',
|
||||
render: row => {
|
||||
return Number(row.amount).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '实际到账金额',
|
||||
key: 'actualAmount',
|
||||
render: row => {
|
||||
return Number(row.actualAmount).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '提现方式',
|
||||
key: 'withdrawMethod',
|
||||
render: row => {
|
||||
return WithdrawMethodEnum[row.withdrawMethod as keyof typeof WithdrawMethodEnum];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '银行卡ID',
|
||||
key: 'bankAccountId'
|
||||
},
|
||||
{
|
||||
title: '手续费',
|
||||
key: 'fee',
|
||||
render: row => {
|
||||
return Number(row.fee).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render: row => {
|
||||
return WithdrawStatusEnum[row.status as keyof typeof WithdrawStatusEnum];
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
render: (row: any) => {
|
||||
return useDateFormat(row.createdAt, 'YYYY-MM-DD HH:mm').value;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
width: 180,
|
||||
key: 'operation',
|
||||
operations: (row: any) => [
|
||||
{
|
||||
contentText: '通过',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
visible: row.status === 'pending',
|
||||
onClick: () => {
|
||||
const reason = ref<string>('');
|
||||
dialog.create({
|
||||
title: '通过提现',
|
||||
positiveText: '是',
|
||||
negativeText: '否',
|
||||
showIcon: false,
|
||||
content: () =>
|
||||
h(NInput, {
|
||||
type: 'textarea',
|
||||
rows: 4,
|
||||
placeholder: '请输入通过理由(可选)',
|
||||
'onUpdate:value': value => {
|
||||
reason.value = value;
|
||||
}
|
||||
}),
|
||||
onPositiveClick: async () => {
|
||||
await safeClient(() =>
|
||||
client.api.admin.withdraw({ orderId: row.id }).approve.post({
|
||||
reviewNote: reason.value
|
||||
})
|
||||
);
|
||||
tableInst.value?.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
contentText: '拒绝',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
visible: row.status === 'pending',
|
||||
onClick: async () => {
|
||||
const reason = ref<string>('');
|
||||
dialog.create({
|
||||
title: '拒绝提现',
|
||||
positiveText: '是',
|
||||
negativeText: '否',
|
||||
showIcon: false,
|
||||
content: () =>
|
||||
h(NInput, {
|
||||
type: 'textarea',
|
||||
rows: 4,
|
||||
placeholder: '请输入拒绝理由',
|
||||
'onUpdate:value': value => {
|
||||
reason.value = value;
|
||||
}
|
||||
}),
|
||||
onPositiveClick: () => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!reason.value) {
|
||||
window.$message?.error('请输入拒绝理由');
|
||||
reject(new Error('请输入拒绝理由'));
|
||||
} else {
|
||||
safeClient(() =>
|
||||
client.api.admin.withdraw({ orderId: row.id }).reject.post({
|
||||
rejectReason: reason.value
|
||||
})
|
||||
).then(() => {
|
||||
tableInst.value?.reload();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const filterColumns: TableFilterColumns = [
|
||||
{
|
||||
title: 'id',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '提现金额',
|
||||
key: 'amount',
|
||||
component: NDatePicker
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableBase
|
||||
ref="tableInst"
|
||||
show-header-operation
|
||||
: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