feat: init
This commit is contained in:
2
.env
2
.env
@@ -17,7 +17,7 @@ VITE_ICON_LOCAL_PREFIX=icon-local
|
|||||||
VITE_AUTH_ROUTE_MODE=static
|
VITE_AUTH_ROUTE_MODE=static
|
||||||
|
|
||||||
# static auth route home
|
# static auth route home
|
||||||
VITE_ROUTE_HOME=asset
|
VITE_ROUTE_HOME=/home
|
||||||
|
|
||||||
# default menu icon
|
# default menu icon
|
||||||
VITE_MENU_ICON=mdi:menu
|
VITE_MENU_ICON=mdi:menu
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# backend service base url, test environment
|
# backend service base url, test environment
|
||||||
VITE_SERVICE_BASE_URL=https://riwa-api.riwsan1.com
|
VITE_SERVICE_BASE_URL=http://192.168.1.2:9538
|
||||||
|
|
||||||
# other backend service base url, test environment
|
# other backend service base url, test environment
|
||||||
VITE_OTHER_SERVICE_BASE_URL= `{}`
|
VITE_OTHER_SERVICE_BASE_URL= `{}`
|
||||||
|
|||||||
@@ -228,29 +228,7 @@ const local: App.I18n.Schema = {
|
|||||||
404: 'Page Not Found',
|
404: 'Page Not Found',
|
||||||
500: 'Server Error',
|
500: 'Server Error',
|
||||||
'iframe-page': 'Iframe',
|
'iframe-page': 'Iframe',
|
||||||
home: 'Home',
|
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'
|
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
login: {
|
login: {
|
||||||
|
|||||||
@@ -224,29 +224,7 @@ const local: App.I18n.Schema = {
|
|||||||
404: '页面不存在',
|
404: '页面不存在',
|
||||||
500: '服务器错误',
|
500: '服务器错误',
|
||||||
'iframe-page': '外链页面',
|
'iframe-page': '外链页面',
|
||||||
home: '首页',
|
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: '现货机器人'
|
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
login: {
|
login: {
|
||||||
|
|||||||
@@ -20,21 +20,5 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
|||||||
500: () => import("@/views/_builtin/500/index.vue"),
|
500: () => import("@/views/_builtin/500/index.vue"),
|
||||||
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
||||||
login: () => import("@/views/_builtin/login/index.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"),
|
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
|
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',
|
name: 'home',
|
||||||
path: '/home',
|
path: '/home',
|
||||||
component: 'layout.base$view.home',
|
component: 'layout.base$view.home',
|
||||||
meta: {
|
meta: {
|
||||||
title: 'home',
|
title: 'home',
|
||||||
i18nKey: 'route.home',
|
i18nKey: 'route.home'
|
||||||
icon: 'mdi:monitor-dashboard',
|
|
||||||
order: 1
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -115,187 +72,5 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||||||
constant: true,
|
constant: true,
|
||||||
hideInMenu: 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",
|
"403": "/403",
|
||||||
"404": "/404",
|
"404": "/404",
|
||||||
"500": "/500",
|
"500": "/500",
|
||||||
"asset": "/asset",
|
|
||||||
"bank": "/bank",
|
|
||||||
"deposit": "/deposit",
|
|
||||||
"deposit_fiat": "/deposit/fiat",
|
|
||||||
"home": "/home",
|
"home": "/home",
|
||||||
"iframe-page": "/iframe-page/:url",
|
"iframe-page": "/iframe-page/:url",
|
||||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
|
"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"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
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";
|
"403": "/403";
|
||||||
"404": "/404";
|
"404": "/404";
|
||||||
"500": "/500";
|
"500": "/500";
|
||||||
"asset": "/asset";
|
|
||||||
"bank": "/bank";
|
|
||||||
"deposit": "/deposit";
|
|
||||||
"deposit_fiat": "/deposit/fiat";
|
|
||||||
"home": "/home";
|
"home": "/home";
|
||||||
"iframe-page": "/iframe-page/:url";
|
"iframe-page": "/iframe-page/:url";
|
||||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
|
"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"
|
| "403"
|
||||||
| "404"
|
| "404"
|
||||||
| "500"
|
| "500"
|
||||||
| "asset"
|
|
||||||
| "bank"
|
|
||||||
| "deposit"
|
|
||||||
| "home"
|
| "home"
|
||||||
| "iframe-page"
|
| "iframe-page"
|
||||||
| "login"
|
| "login"
|
||||||
| "news"
|
|
||||||
| "notification"
|
|
||||||
| "robot"
|
|
||||||
| "rwa"
|
|
||||||
| "tokenization"
|
|
||||||
| "transfer"
|
|
||||||
| "user"
|
|
||||||
| "withdraw"
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,23 +81,7 @@ declare module "@elegant-router/types" {
|
|||||||
| "500"
|
| "500"
|
||||||
| "iframe-page"
|
| "iframe-page"
|
||||||
| "login"
|
| "login"
|
||||||
| "asset"
|
|
||||||
| "bank"
|
|
||||||
| "deposit_fiat"
|
|
||||||
| "home"
|
| "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