充值系统模块提交

This commit is contained in:
zzy 2022-07-25 17:29:24 +08:00
parent 53ca93a288
commit 2294e72882
23 changed files with 940 additions and 6 deletions

View File

@ -72,7 +72,8 @@
"react-dom": "^17.0.0",
"react-helmet-async": "^1.0.4",
"umi": "^3.5.0",
"umi-serve": "^1.9.10"
"umi-serve": "^1.9.10",
"bignumber.js": "^9.0.0"
},
"devDependencies": {
"@ant-design/pro-cli": "^2.0.2",

View File

@ -0,0 +1,13 @@
.delete-btn {
color: @error-color;
&:hover {
color: @error-color !important;
}
}
.disabled {
color: @disabled-color;
cursor: not-allowed;
&:hover {
color: @disabled-color !important;
}
}

View File

@ -0,0 +1,24 @@
import React from 'react';
import { Popconfirm } from 'antd';
import styles from './index.less';
import classNames from 'classnames';
interface PropsType {
onConfirm: () => void;
title?: string;
buttonName?: string;
disabled?: boolean;
}
const ConfirmButton: React.FC<PropsType> = ({
onConfirm,
disabled = false,
title = '',
buttonName = '',
}) => {
return (
<Popconfirm title={title} disabled={disabled} onConfirm={onConfirm} okText="是" cancelText="否">
<a className={classNames(styles.deleteBtn, disabled ? styles.disabled : '')}>{buttonName}</a>
</Popconfirm>
);
};
export default ConfirmButton;

View File

@ -172,7 +172,7 @@ const Table = <T extends Record<string, any>>(props: PropsType) => {
}}
>
<PlusOutlined />
{item.text || '新建'}
{item.text || '添加'}
</Button>,
);
} else if (item.type === 'batchDelete') {

View File

@ -35,7 +35,7 @@ const Login: React.FC = () => {
}}
>
<ProFormText
name="username"
name="name"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={styles.prefixIcon} />,

View File

@ -0,0 +1,62 @@
// 创建收款地址弹窗
import React, { useRef } from 'react';
import { createForm } from '@formily/core';
import { createSchemaField } from '@formily/react';
import Modal, { ModalProps } from '@/components/Modal';
import { Form, FormItem, Input, Select } from '@formily/antd';
interface AddAddressModalPropsType extends ModalProps {
onOk: (val: any) => void;
onCancel: () => void;
}
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
Select,
},
});
const form = createForm({});
const AddAddressModal = ({ onOk, onCancel, ...rest }: AddAddressModalPropsType) => {
const handleOk = () => {
const formState = form.getFormState();
onOk(formState.values);
};
const handleCancel = () => {
onCancel();
};
return (
<Modal title="创建收款地址" onOk={handleOk} onCancel={handleCancel} width={800} {...rest}>
<Form form={form} labelCol={4} wrapperCol={18}>
<SchemaField>
<SchemaField.String
name="bc_type"
title="游戏币类型"
required
x-decorator="FormItem"
x-component="Select"
x-component-props={{
placeholder: '',
}}
/>
<SchemaField.String
name="address"
title="收款地址"
x-decorator="FormItem"
x-component="Input"
x-component-props={{
placeholder: '不传则后台生成',
}}
/>
</SchemaField>
</Form>
</Modal>
);
};
export default AddAddressModal;

View File

@ -0,0 +1,72 @@
// 修改收款地址弹窗
import React, { useRef, useEffect } from 'react';
import { createForm } from '@formily/core';
import { createSchemaField } from '@formily/react';
import Modal, { ModalProps } from '@/components/Modal';
import { Form, FormItem, Input, Select } from '@formily/antd';
interface EditAddressModalPropsType extends ModalProps {
editModalData: any;
onOk: (val: any) => void;
onCancel: () => void;
}
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
Select,
},
});
const form = createForm({});
const EditAddressModal = ({
onOk,
onCancel,
editModalData,
...rest
}: EditAddressModalPropsType) => {
useEffect(() => {
form.setInitialValues(editModalData);
});
const handleOk = () => {
const formState = form.getFormState();
onOk(formState.values);
};
const handleCancel = () => {
onCancel();
};
return (
<Modal title="修改收款地址" onOk={handleOk} onCancel={handleCancel} width={800} {...rest}>
<Form form={form} labelCol={4} wrapperCol={18}>
<SchemaField>
<SchemaField.String
name="address"
title="旧收款地址"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="bc_type"
title="游戏币类型"
required
x-decorator="FormItem"
x-component="Select"
/>
<SchemaField.String
name="new_address"
title="新收款地址"
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
</Form>
</Modal>
);
};
export default EditAddressModal;

View File

@ -0,0 +1,102 @@
import React, { useState, useRef } from 'react';
import Table, { ProColumns, ActionType } from '@/components/Table';
import { getAddressList, deleteAddress, createAddress, modifyAddress } from '@/services/address';
import { fetchTableData } from '@/utils/table';
import DeleteButton from '@/components/Table/DeleteButton';
import AddAddressModal from './components/addAddressModal';
import EditAddressModal from './components/editAddressModal';
const CollectionAddressList = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
const [modalData, setModalData] = useState({});
const handleEdit = (row: any) => {
setModalData(row);
setIsEditModalVisible(true);
};
const handleDelete = async (address: any) => {
await deleteAddress({ address: address });
};
const tableRef = useRef<ActionType>();
const columns: ProColumns<any>[] = [
{
title: '收款地址',
dataIndex: 'address',
hideInSearch: true,
ellipsis: true,
},
{
title: '收款游戏币类型',
dataIndex: 'bc_type',
hideInSearch: true,
ellipsis: true,
},
{
title: '操作',
valueType: 'option',
width: 150,
render: (_, row) => [
<a
key="edit"
onClick={() => {
handleEdit(row);
}}
>
</a>,
<DeleteButton
key="delete"
onDelete={() => {
handleDelete(row.address);
}}
/>,
],
},
];
return (
<div>
<Table
columns={columns}
rowKey="id"
toolBarActions={[
{
type: 'add',
onConfirm: () => {
setIsModalVisible(true);
},
},
]}
actionRef={tableRef}
request={async (params) => {
const res = await fetchTableData(getAddressList, params);
return res;
}}
/>
<AddAddressModal
visible={isModalVisible}
onCancel={function () {
setIsModalVisible(false);
}}
onOk={async function (val: any): Promise<void> {
await createAddress(val);
setIsModalVisible(false);
}}
/>
<EditAddressModal
visible={isEditModalVisible}
editModalData={modalData}
onCancel={function () {
setIsEditModalVisible(false);
}}
onOk={async function (val: any): Promise<void> {
await modifyAddress(val);
setIsEditModalVisible(false);
}}
/>
</div>
);
};
export default CollectionAddressList;

View File

@ -0,0 +1,68 @@
// 添加代币类型弹窗
import React, { useRef } from 'react';
import { createForm } from '@formily/core';
import { createSchemaField } from '@formily/react';
import Modal, { ModalProps } from '@/components/Modal';
import { Form, FormItem, Input, NumberPicker } from '@formily/antd';
interface AddCoinTypeModalPropsType extends ModalProps {
onOk: (val: any) => void;
onCancel: () => void;
}
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
NumberPicker,
},
});
const form = createForm({});
const AddCoinTypeModal = ({ onOk, onCancel, ...rest }: AddCoinTypeModalPropsType) => {
const handleOk = () => {
const formState = form.getFormState();
onOk(formState.values);
};
const handleCancel = () => {
onCancel();
};
return (
<Modal title="添加代币类型" onOk={handleOk} onCancel={handleCancel} width={800} {...rest}>
<Form form={form} labelCol={4} wrapperCol={18}>
<SchemaField>
<SchemaField.String
name="name"
title="代币名称"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="usdt_price"
title="usdt比例"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="eth_price"
title="eth比例"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.Number
name="num"
title="数量"
x-decorator="FormItem"
x-component="NumberPicker"
/>
</SchemaField>
</Form>
</Modal>
);
};
export default AddCoinTypeModal;

View File

@ -0,0 +1,78 @@
// 修改收款地址弹窗
import React, { useRef, useEffect } from 'react';
import { createForm } from '@formily/core';
import { createSchemaField } from '@formily/react';
import Modal, { ModalProps } from '@/components/Modal';
import { Form, FormItem, Input, Select } from '@formily/antd';
interface EditCoinTypeModalPropsType extends ModalProps {
editModalData: any;
onOk: (val: any) => void;
onCancel: () => void;
}
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
Select,
},
});
const form = createForm({});
const EditCoinTypeModal = ({
onOk,
onCancel,
editModalData,
...rest
}: EditCoinTypeModalPropsType) => {
useEffect(() => {
form.setInitialValues(editModalData);
});
const handleOk = () => {
const formState = form.getFormState();
onOk(formState.values);
};
const handleCancel = () => {
onCancel();
};
return (
<Modal title="修改代币类型" onOk={handleOk} onCancel={handleCancel} width={800} {...rest}>
<Form form={form} labelCol={4} wrapperCol={18}>
<SchemaField>
<SchemaField.String
name="bc_name"
title="游戏币名称"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="bc_type"
title="游戏币类型"
required
x-decorator="FormItem"
x-component="Select"
/>
<SchemaField.String
name="usdt_price"
title="usdt比例"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="eth_price"
title="eth比例"
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
</Form>
</Modal>
);
};
export default EditCoinTypeModal;

View File

@ -0,0 +1,104 @@
import React, { useState, useRef } from 'react';
import Table, { ProColumns, ActionType } from '@/components/Table';
import { addCoinType, getCoinTypeList, modifyCoinType } from '@/services/coinType';
import { fetchTableData } from '@/utils/table';
import AddCoinTypeModal from './components/addCoinTypeModal';
import EditCoinTypeModal from './components/editCoinTypeModal';
const CoinTypeList = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
const [modalData, setModalData] = useState({});
const handleEdit = (row: any) => {
setModalData(row);
setIsEditModalVisible(true);
};
const tableRef = useRef<ActionType>();
const columns: ProColumns<any>[] = [
{
title: '游戏币名称',
dataIndex: 'bc_name',
hideInSearch: true,
ellipsis: true,
},
{
title: '游戏币类型',
dataIndex: 'bc_type',
width: '10%',
hideInSearch: true,
},
{
title: 'usdt比例',
dataIndex: 'usdt_price',
hideInSearch: true,
ellipsis: true,
},
{
title: 'eth比例',
dataIndex: 'eth_price',
hideInSearch: true,
ellipsis: true,
},
{
title: '操作',
valueType: 'option',
width: 150,
render: (_, row) => [
<a
key="edit"
onClick={() => {
handleEdit(row);
}}
>
</a>,
],
},
];
return (
<div>
<Table
columns={columns}
rowKey="id"
toolBarActions={[
{
type: 'add',
onConfirm: () => {
setIsModalVisible(true);
},
},
]}
actionRef={tableRef}
request={async (params) => {
const res = await fetchTableData(getCoinTypeList, params);
return res;
}}
/>
<AddCoinTypeModal
visible={isModalVisible}
onCancel={function () {
setIsModalVisible(false);
}}
onOk={async function (val: any): Promise<void> {
await addCoinType(val);
setIsModalVisible(false);
}}
/>
<EditCoinTypeModal
visible={isEditModalVisible}
editModalData={modalData}
onCancel={function () {
setIsEditModalVisible(false);
}}
onOk={async function (val: any): Promise<void> {
await modifyCoinType(val);
setIsEditModalVisible(false);
}}
/>
</div>
);
};
export default CoinTypeList;

View File

@ -0,0 +1,89 @@
import React, { useRef } from 'react';
import Table, { ProColumns, ActionType } from '@/components/Table';
import { getRecordList } from '@/services/record';
import { fetchTableData } from '@/utils/table';
import { getBalanceAmount } from '@/utils/formatBalance';
import BigNumber from 'bignumber.js';
const RecordList = () => {
const tableRef = useRef<ActionType>();
const columns: ProColumns<any>[] = [
{
title: '时间',
dataIndex: 'time',
hideInTable: true,
valueType: 'dateRange',
ellipsis: true,
},
{
title: '交易哈希',
dataIndex: 'tx_hash',
hideInSearch: true,
ellipsis: true,
},
{
title: '金额',
dataIndex: 'price',
width: '10%',
hideInSearch: true,
},
{
title: '位数',
dataIndex: 'decimals',
width: '10%',
hideInSearch: true,
},
{
title: '游戏币类型',
dataIndex: 'bc_type',
width: '10%',
hideInSearch: true,
},
{
title: '发送地址',
dataIndex: 'form_address',
hideInSearch: true,
ellipsis: true,
},
{
title: '接受地址',
dataIndex: 'to_address',
hideInSearch: true,
ellipsis: true,
},
{
title: '充值时间',
dataIndex: 'time',
hideInSearch: true,
ellipsis: true,
},
];
return (
<div>
<Table
columns={columns}
rowKey="id"
actionRef={tableRef}
request={async (params) => {
if ((params.time ?? '') !== '') {
params.start_time = params.time[0];
params.end_time = params.time[1];
}
const res = await fetchTableData(getRecordList, params);
for (const key in res.data) {
if (Object.prototype.hasOwnProperty.call(res.data, key)) {
const element = res.data[key];
element.price = getBalanceAmount(
new BigNumber(element.price),
element.decimals,
).toNumber();
}
}
return res;
}}
/>
</div>
);
};
export default RecordList;

View File

@ -0,0 +1,97 @@
import React, { useRef } from 'react';
import Table, { ProColumns, ActionType } from '@/components/Table';
import { getWithdrawList, solveWithdraw } from '@/services/withdraw';
import { fetchTableData } from '@/utils/table';
import { getBalanceAmount } from '@/utils/formatBalance';
import BigNumber from 'bignumber.js';
import ConfirmButton from '@/components/Table/ConfirmButton';
const WithdrawList = () => {
const handleConfirm = async (uuid) => {
await solveWithdraw({ uuid: uuid });
};
const tableRef = useRef<ActionType>();
const columns: ProColumns<any>[] = [
{
title: '提现订单号',
dataIndex: 'uuid',
ellipsis: true,
},
{
title: '金额',
dataIndex: 'price',
width: '10%',
hideInSearch: true,
},
{
title: '位数',
dataIndex: 'decimals',
width: '10%',
hideInSearch: true,
},
{
title: '发送地址',
dataIndex: 'form_address',
hideInSearch: true,
ellipsis: true,
},
{
title: '接受地址',
dataIndex: 'to_address',
hideInSearch: true,
ellipsis: true,
},
{
title: '游戏币类型',
dataIndex: 'bc_type',
width: '10%',
},
{
title: '时间',
dataIndex: 'time',
valueType: 'dateRange',
ellipsis: true,
},
{
title: '操作',
valueType: 'option',
width: 150,
render: (_, row) => [
<ConfirmButton
key="confirm"
title="确认通知已提现?"
buttonName="通知"
onConfirm={() => {
handleConfirm(row.uuid);
}}
/>,
],
},
];
return (
<div>
<Table
columns={columns}
rowKey="id"
actionRef={tableRef}
request={async (params) => {
console.log('params = ', params);
const res = await fetchTableData(getWithdrawList, params);
for (const key in res.data) {
if (Object.prototype.hasOwnProperty.call(res.data, key)) {
const element = res.data[key];
element.price = getBalanceAmount(
new BigNumber(element.price),
element.decimals,
).toNumber();
}
}
return res;
}}
/>
</div>
);
};
export default WithdrawList;

View File

@ -10,6 +10,37 @@ export default [
layout: false,
component: './Login',
},
{
name: '充值系统',
path: RoutePath.RECHARGE,
routes: [
{
path: RoutePath.RECHARGE,
redirect: RoutePath.RECORD.LIST,
hideInMenu: true,
},
{
name: '充值订单',
path: RoutePath.RECORD.LIST,
component: './Recharge/Record/List',
},
{
name: '收款地址',
path: RoutePath.ADDRESS.LIST,
component: './Recharge/Address/List',
},
{
name: '代币种类',
path: RoutePath.COIN_TYPE.LIST,
component: './Recharge/CoinType/List',
},
{
name: '提现管理',
path: RoutePath.WITHDRAW.LIST,
component: './Recharge/Withdraw/List',
},
],
},
{
name: '设置',
path: RoutePath.SETTING,
@ -19,7 +50,6 @@ export default [
redirect: RoutePath.WORK.LIST,
hideInMenu: true,
},
{
name: '作品',
path: RoutePath.WORK.LIST,

View File

@ -1,6 +1,20 @@
const SETTING = '/setting';
const RECHARGE = '/recharge';
const RoutePath = {
LOGIN: '/login',
RECHARGE: RECHARGE,
RECORD: {
LIST: `${RECHARGE}/record`,
},
ADDRESS: {
LIST: `${RECHARGE}/address`,
},
COIN_TYPE: {
LIST: `${RECHARGE}/coin_type`,
},
WITHDRAW: {
LIST: `${RECHARGE}/withdraw`,
},
SETTING: SETTING,
WORK: {
LIST: `${SETTING}/work`,

61
src/services/address.ts Normal file
View File

@ -0,0 +1,61 @@
import request from '@/utils/request';
/**
*
* @param {object} params
* @returns {array} data
* bc_type
* address
*/
export const getAddressList = (params) => {
return request.request({
url: '/api/v1/address/get',
method: 'get',
params,
});
};
/**
*
* @param {Object} data
* bc_type
* address ()
* @returns
*/
export const createAddress = (data) => {
return request.request({
url: '/api/v1/address/make',
method: 'post',
data,
});
};
/**
*
* @param {Object} data
* address
* @returns
*/
export const deleteAddress = (data) => {
return request.request({
url: '/api/v1/address/del',
method: 'post',
data,
});
};
/**
*
* @param {Object} data
* address
* bc_type
* new_address
* @returns
*/
export const modifyAddress = (data) => {
return request.request({
url: '/api/v1/address/del',
method: 'post',
data,
});
};

53
src/services/coinType.ts Normal file
View File

@ -0,0 +1,53 @@
import request from '@/utils/request';
/**
*
* @param {object} params
* type
* @returns {array} data
* bc_name
* bc_type
* usdt_price usdt的比例
* eth_price eth的比例
*/
export const getCoinTypeList = (params) => {
return request.request({
url: '/api/v1/token/get',
method: 'get',
params,
});
};
/**
*
* @param {object} params
* bc_name
* usdt_price usdt的比例
* eth_price eth的比例
* num
* @returns {object} data
*/
export const addCoinType = (data) => {
return request.request({
url: '/api/v1/token/make',
method: 'post',
data,
});
};
/**
*
* @param {object} params
* bc_name
* bc_type
* usdt_price usdt的比例
* eth_price eth的比例
* @returns {object} data
*/
export const modifyCoinType = (data) => {
return request.request({
url: '/api/v1/token/chg',
method: 'post',
data,
});
};

View File

@ -2,7 +2,7 @@ import request from '@/utils/request';
export const login = (data) => {
return request.request({
url: '/admin/login',
url: '/tbg/api/v1/admin/login',
method: 'post',
data,
});

20
src/services/record.ts Normal file
View File

@ -0,0 +1,20 @@
import request from '@/utils/request';
/**
*
* @param {object} data
* page
* pageSize
* start_time
* end_time
* @returns {array} data
* bc_type
* address
*/
export const getRecordList = (data) => {
return request.request({
url: '/tgb/api/v1/recharge/record',
method: 'post',
data,
});
};

34
src/services/withdraw.ts Normal file
View File

@ -0,0 +1,34 @@
import request from '@/utils/request';
/**
*
* @param {object} params
*
* @returns {array} data
* uuid id
* to_addrss
* form_address
* time
* price
*/
export const getWithdrawList = (params) => {
return request.request({
url: '/tgb/api/v1/withdrawal/get',
method: 'get',
params,
});
};
/**
*
* @param {object} params
* uuid id
* @returns {object} data
*/
export const solveWithdraw = (params) => {
return request.request({
url: '/tgb/api/v1/withdrawal/solve',
method: 'get',
params,
});
};

View File

@ -176,7 +176,7 @@
.ant-pro-table-list-toolbar-container {
flex-direction: column;
justify-content: flex-start;
padding: 0 0 20px 0;
padding: 20px 0 20px 0;
.ant-pro-table-list-toolbar-left {
.ant-space-item {
margin-bottom: 16px;

6
src/utils/bigNumber.ts Normal file
View File

@ -0,0 +1,6 @@
import BigNumber from 'bignumber.js';
export const BIG_ZERO = new BigNumber(0);
export const BIG_ONE = new BigNumber(1);
export const BIG_NINE = new BigNumber(9);
export const BIG_TEN = new BigNumber(10);

View File

@ -0,0 +1,6 @@
import BigNumber from 'bignumber.js';
import { BIG_TEN } from './bigNumber';
export const getBalanceAmount = (amount: BigNumber, decimals = 18) => {
return new BigNumber(amount).dividedBy(BIG_TEN.pow(decimals));
};