Browse Source

commit

main
mac 6 months ago
parent
commit
27bf63e606
  1. 4
      .env
  2. 5
      env-temp
  3. 11
      package.json
  4. BIN
      public/favicon.ico
  5. 2
      public/index.html
  6. BIN
      public/login192.png
  7. BIN
      public/login512.png
  8. BIN
      public/logo192.png
  9. BIN
      public/logo512.png
  10. 15
      src/App.tsx
  11. BIN
      src/assets/_line2.png
  12. BIN
      src/assets/login.jpg
  13. BIN
      src/assets/login.png
  14. BIN
      src/assets/root.png
  15. 27
      src/components/Button.tsx
  16. 71
      src/components/CountdownTimer.tsx
  17. 302
      src/components/MyTable/index.tsx
  18. 49
      src/components/MyTable/tableHook.tsx
  19. 78
      src/components/SearchForm/index.tsx
  20. 151
      src/components/layout/Header.tsx
  21. 42
      src/components/layout/Slider.tsx
  22. 111
      src/components/profitChart.tsx
  23. 49
      src/http/api.ts
  24. 31
      src/http/axios_config.ts
  25. 65
      src/http/service.ts
  26. 20
      src/http/sign.ts
  27. 38
      src/http/sort.js
  28. 66
      src/http/types.ts
  29. 113
      src/pages/account/index.tsx
  30. 97
      src/pages/create-proxy/index.tsx
  31. 203
      src/pages/deposit/index.tsx
  32. 50
      src/pages/forget/index.tsx
  33. 103
      src/pages/home/index.tsx
  34. 51
      src/pages/link/index.tsx
  35. 93
      src/pages/login/index.tsx
  36. 53
      src/pages/personal/index.tsx
  37. 216
      src/pages/record/assets/index.tsx
  38. 111
      src/pages/record/bonus/index.tsx
  39. 75
      src/pages/record/deposit/index.tsx
  40. 67
      src/pages/record/escrow/index.tsx
  41. 81
      src/pages/record/withdraw/index.tsx
  42. 75
      src/pages/security/index.tsx
  43. 78
      src/pages/submit/index.tsx
  44. 110
      src/pages/team/index.tsx
  45. 73
      src/pages/transfer/index.tsx
  46. 95
      src/pages/withdraw/index.tsx
  47. 43
      src/router/index.tsx
  48. 3
      src/router/renderRouter.tsx
  49. 46
      src/router/routes.tsx
  50. 38
      src/store/index.ts
  51. 12
      src/styles/app.scss
  52. 4
      src/styles/components.scss
  53. 23
      src/styles/global.scss
  54. 114
      src/styles/home.scss
  55. 10
      src/styles/login.scss
  56. 27
      src/types/index.ts
  57. 47
      src/utils/index.ts
  58. 68
      yarn.lock

4
.env

@ -0,0 +1,4 @@
SKIP_PREFLIGHT_CHECK=true
GENERATE_SOURCEMAP=false
REACT_APP_BASEURL='https://matontrading.com'
REACT_APP_ORIGIN='https://matontrading.com'

5
env-temp

@ -0,0 +1,5 @@
REACT_APP_BASEURL='https://matontrading.com'
REACT_APP_ORIGIN='https://matontrading.com'
REACT_APP_BASEURL='http://203.161.61.234:8082'
REACT_APP_ORIGIN='http://162.254.37.253:8086'

11
package.json

@ -11,11 +11,16 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"antd": "^5.18.1",
"axios": "^1.7.2",
"echarts": "^5.5.0",
"echarts-for-react": "^3.0.2",
"js-md5": "^0.8.3",
"mobx": "^6.12.4",
"mobx-react": "^9.1.1",
"qrcode": "^1.5.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.2.1",
"react-router-dom": "^6.23.1",
"react-scripts": "5.0.1",
"sass": "^1.77.5",
@ -26,7 +31,9 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"deploy": "npm run build && scp -r ./build/* dcfilefast_prod:/data/wwwroot/tarder",
"deploy:prod": "npm run build && scp -r ./build/* metatrader_prod:/data/wwwroot/matontrading.com"
},
"eslintConfig": {
"extends": [
@ -49,4 +56,4 @@
"devDependencies": {
"@types/qrcode": "^1.5.5"
}
}
}

BIN
public/favicon.ico

2
public/index.html

@ -22,7 +22,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>Maton Trading</title>
</head>
<body>

BIN
public/login192.png

After

Width: 564  |  Height: 442  |  Size: 100 KiB

BIN
public/login512.png

After

Width: 564  |  Height: 442  |  Size: 100 KiB

BIN
public/logo192.png

Before

Width: 192  |  Height: 192  |  Size: 5.2 KiB

BIN
public/logo512.png

Before

Width: 512  |  Height: 512  |  Size: 9.4 KiB

15
src/App.tsx

@ -1,9 +1,22 @@
import { Suspense, lazy } from "react";
import RenderRouter from "./router";
import { HashRouter } from 'react-router-dom';
import { HashRouter, Route, Routes } from 'react-router-dom';
const Submit = lazy(() => import("./pages/submit"));
function App() {
return (
<HashRouter>
<Suspense fallback={<div className='row-center' style={{ height: '80vh' }}>
<div className='tac'>
{/* <Loading color='#1BEDFF' size={30} /> */}
<div className='fz-16 sub-text mt-1'>...</div>
</div>
</div>}>
<Routes>
<Route path="/submit" element={<Submit />} />
</Routes>
</Suspense>
<RenderRouter />
</HashRouter>
);

BIN
src/assets/_line2.png

After

Width: 41  |  Height: 41  |  Size: 178 B

BIN
src/assets/login.jpg

Before

Width: 200  |  Height: 200  |  Size: 5.8 KiB

BIN
src/assets/login.png

After

Width: 564  |  Height: 442  |  Size: 100 KiB

BIN
src/assets/root.png

After

Width: 24  |  Height: 22  |  Size: 1.7 KiB

27
src/components/Button.tsx

@ -1,19 +1,28 @@
import { forwardRef } from 'react';
import '../styles/components.scss'
interface ButtonProps {
children?: string | JSX.Element;
className?: string;
style?: React.CSSProperties,
onClick?: Function
style?: React.CSSProperties;
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
htmlType?: "button" | "submit" | "reset" | undefined
}
const Button = (props: ButtonProps) => {
const { children, style, className, onClick } = props
const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
const { children, style, className, onClick, htmlType = "button" } = props;
return (
<div onClick={() => onClick && onClick()} className={`default-button ${className}`} style={style}>{children}</div>
)
}
<button
ref={ref}
type={htmlType}
onClick={onClick}
className={`default-button ${className}`}
style={style}
>
{children}
</button>
);
});
export default Button
export default Button;

71
src/components/CountdownTimer.tsx

@ -0,0 +1,71 @@
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
interface CountdownTimerProps {
initialSeconds: number,
onComplete?: Function
}
export interface CountdownTimerRef {
handleStop: Function,
handleStart: Function,
handleReset: Function,
isActive: boolean
}
const CountdownTimer = forwardRef<CountdownTimerRef, CountdownTimerProps>((props, ref) => {
const { initialSeconds, onComplete } = props
const [seconds, setSeconds] = useState(initialSeconds);
const [isActive, setIsActive] = useState(false);
const [title, setTitle] = useState('发送验证码')
useImperativeHandle(ref, () => ({
handleStop,
handleReset,
handleStart,
isActive
}))
const handleStop = () => {
setIsActive(false);
setTitle('发送验证码')
};
const handleStart = () => {
if (isActive) return
setSeconds(initialSeconds)
setIsActive(true);
};
const handleReset = () => {
setSeconds(initialSeconds);
setIsActive(true);
};
useEffect(() => {
let interval: any = null;
console.log(seconds);
console.log(isActive);
if (isActive && seconds > 0) {
interval = setInterval(() => {
setSeconds(seconds => seconds - 1);
}, 1000);
} else {
clearInterval(interval)
seconds <= 0 && setTitle('重新发送')
setIsActive(false)
// onComplete && onComplete(seconds)
}
console.log(1);
return () => clearInterval(interval);
}, [isActive, seconds, onComplete]);
return (
<div>{isActive ? <span className='fz-18 fw550'>{seconds}s</span> : title}</div>
);
});
export default CountdownTimer;

302
src/components/MyTable/index.tsx

@ -0,0 +1,302 @@
import React, {
useState,
forwardRef,
useImperativeHandle,
useRef,
ReactNode,
FC
} from 'react'
import { Table } from 'antd'
import useService from './tableHook'
import SearchView from '../SearchForm'
/**
*
* @param {RefType} ref
* @param {object[]} columns
* @param {function} apiFun
* @param {object[]} searchConfigList
* @param {function} beforeSearch
* @param {function} onFieldsChange
* @param {object} extraProps
* @param {function} onSelectRow
* @param {string} rowKey key
* @param {function} sortConfig
* @param {function} expandedRowRender
* @param {function} onExpand
* @param {string} rowClassName
* @param {boolean} small
* @param {string[]} extraPagation
*/
interface TableProps {
columns: object[];
apiFun: (arg0?: unknown[]) => Promise<{}>;
ref?: any;
searchConfigList?: object[];
extraProps?: object;
rowKey?: string;
rowClassName?: string;
small?: boolean;
showHeader?: boolean;
extraPagation?: string[];
beforeSearch?: (arg0?: unknown) => void;
onSelectRow?: (arg0?: string[], arg1?: string[]) => void;
onFieldsChange?: (arg0?: unknown, arg1?: unknown) => void;
sortConfig?: (arg0?: object) => any;
expandedRowRender?: () => ReactNode;
onExpand?: () => void;
header?: JSX.Element
}
const MyTable: FC<TableProps> = forwardRef(
(props: TableProps, ref: any) => {
/**
* @forwardRef
* ref实例
* ref绑定到子组件自身的节点上.
*/
const searchForm: any = useRef(null)
const {
columns,
apiFun,
searchConfigList,
extraProps,
rowKey,
rowClassName,
small,
showHeader,
extraPagation,
beforeSearch,
onSelectRow,
onFieldsChange,
sortConfig,
expandedRowRender,
onExpand,
header
} = props
// 搜索参数,如果有特殊需要处理的参数,就处理
const searchObj = searchConfigList && searchConfigList.reduce(
(prev: any, next: any) => {
return Object.assign(prev, {
[next.key]: next.fn ? next.fn(next.initialValue) : next.initialValue
})
},
{}
)
// 初始参数
const initParams = {
...searchObj,
...extraProps,
page: 1,
page_size: 20
}
// 多选框的选择值
const [selectedKeys, setSelectedKeys] = useState([])
// 列表所有的筛选参数(包括搜索、分页、排序等)
const [tableParams, setTableParams] = useState(initParams)
// 列表搜索参数
const [searchParams, setSearchParams] = useState(searchObj)
// 列表排序参数
const [sortParams, setSortParams] = useState({})
// 列表分页参数
const [curPageNo, setCurPageNo] = useState(initParams.page)
const [curPageSize, setCurPageSize] = useState(initParams.page_size)
const { loading = false, response = {} }: any = useService(
apiFun,
tableParams
);
let tableData = [] as any[];
let total = 0;
const validData = response?.data ? response.data : [];
if (Array.isArray(validData) && validData.length >= 0) {
tableData = validData;
total = validData.length;
}
if (validData.rows) {
tableData = validData.rows;
total = validData.total;
}
// const validData = response?.data ? response.data : {}
// const { rows: tableData = [], total } = validData
// 执行搜索操作
const handleSearch = (val: any): void => {
if (val.login) {
val.login = Number(val.login)
}
const obj = {
...val
}
if (Array.isArray(obj.chain_id)) {
obj.chain_id = Number(obj.chain_id[0])
}
setSearchParams(obj)
setTableParams({ ...tableParams, ...obj, page: 1 })
}
// 重置列表部分状态
const resetAction = (page?: number): void => {
setSelectedKeys([])
const nextPage = page || curPageNo
const nextParmas = page === 1 ? {} : { ...searchParams, ...sortParams }
setCurPageNo(nextPage)
setTableParams({
...initParams,
...nextParmas,
page: nextPage,
page_size: curPageSize
})
}
// 列表复选框选中变化
const onSelectChange = (
selectedRowKeys: any[],
selectedRows: any[]
): void => {
setSelectedKeys(selectedRowKeys as any)
onSelectRow && onSelectRow(selectedRowKeys, selectedRows)
}
// 复选框配置
const rowSelection = {
selectedRowKeys: selectedKeys,
onChange: onSelectChange
}
// 判断是否有复选框显示
const showCheckbox = onSelectRow ? { rowSelection } : {}
// 展开配置
const expendConfig = {
expandedRowRender,
onExpand,
rowClassName
}
// 判断是否有展开行
const showExpend = expandedRowRender ? expendConfig : {}
// 表格和分页的大小
const tableSize = small ? 'small' : 'middle'
const pagationSize = small ? 'small' : 'default'
// 分页、筛选、排序变化时触发
const onTableChange = (
pagination: any,
filters: any,
sorter: object
): void => {
// 如果有sort排序并且sort参数改变时,优先排序
const sortObj = sortConfig ? sortConfig(sorter) : {}
setSortParams(sortObj)
const { current: page, pageSize } = pagination
setCurPageNo(page)
setCurPageSize(pageSize)
setTableParams({
...initParams,
...searchParams,
...sortObj,
page,
page_size: pageSize
})
}
/**
* @useImperativeHandle
* forwardRef引用父组件的ref实例
* ,
*/
useImperativeHandle(ref, () => ({
// 更新列表
update(page?: number): void {
resetAction(page)
},
// 更新列表,并重置搜索字段
resetForm(page?: number): void {
if (searchForm.current) searchForm.current.resetFields()
setSearchParams({})
resetAction(page)
},
// 仅重置搜索字段
resetField(field?: string[]): void {
return field
? searchForm.current.resetFields([...field])
: searchForm.current.resetFields()
},
// 获取当前列表数据
getTableData(): any[] {
return tableData
}
}))
return (
<div>
{/* 搜索栏 */}
{searchConfigList && searchConfigList.length > 0 && (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<SearchView
ref={searchForm}
config={searchConfigList}
beforeSearch={beforeSearch}
handleSearch={handleSearch}
onFieldsChange={onFieldsChange}
/>
{header}
</div>
)}
{/* 列表 */}
<Table
// {...showCheckbox}
{...showExpend}
rowKey={rowKey}
loading={loading}
dataSource={tableData}
columns={columns}
onChange={onTableChange}
size={tableSize}
showHeader={showHeader}
style={{ minWidth: 1000 }}
pagination={{
size: pagationSize,
total,
pageSize: tableParams.page_size,
current: tableParams.page,
showQuickJumper: true,
showSizeChanger: true,
pageSizeOptions: ['20', '50', '100', '200', ...extraPagation as any],
showTotal: (all) => `${all}`,
}}
/>
</div>
)
}
)
MyTable.defaultProps = {
searchConfigList: [],
ref: null,
extraProps: {},
rowKey: 'id',
rowClassName: '',
small: false,
showHeader: true,
extraPagation: [],
beforeSearch: () => { },
onSelectRow: () => { },
onFieldsChange: () => { },
sortConfig: () => { },
expandedRowRender: null as any,
onExpand: () => { }
}
export default MyTable

49
src/components/MyTable/tableHook.tsx

@ -0,0 +1,49 @@
/**
* tableHook.js Table组件的分页事件
* server hook hook ajax { loading, error, response }
* hook
*/
import { useEffect, useState, useCallback } from 'react'
export const useServiceCallback = (
service: (arg0?: any) => Promise<{}>
): any[] => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [response, setResponse] = useState(null as any)
// 使用 useCallback,来判断 service 是否改变
const callback = useCallback(
(params: any) => {
setLoading(true)
setError(null)
service(params)
.then((res) => {
setLoading(false)
setResponse(res)
})
.catch(() => {
setLoading(false)
})
},
[service]
)
return [callback, { loading, error, response }]
}
const useService = (
service: (arg0?: any) => Promise<{}>,
params?: any
): object => {
const [callback, { loading, error, response }]: any[] = useServiceCallback(
service
)
useEffect(() => {
callback(params)
return () => { }
}, [callback, params])
return { loading, error, response }
}
export default useService

78
src/components/SearchForm/index.tsx

@ -0,0 +1,78 @@
import React, { forwardRef, useImperativeHandle, FC } from 'react'
import { Form, Button } from 'antd'
interface SearchProps {
config: object[];
handleSearch: (arg0?: object) => void;
ref: any;
beforeSearch?: (arg0?: object) => void;
onFieldsChange?: (arg0?: unknown, arg1?: unknown) => void;
}
const SearchForm: FC<SearchProps> = forwardRef(
(props: SearchProps, ref) => {
const { config, handleSearch, beforeSearch, onFieldsChange } = props
const [form] = Form.useForm()
const getFields = (): JSX.Element[] => {
return config.map((item: any) => {
return (
<Form.Item
label={item.label}
key={item.key}
name={item.key}
rules={item.rules}
style={{ marginBottom: '6px' }}
>
{item.slot}
</Form.Item>
)
})
}
const emitSearch = (values: object): void => {
// beforeSearch用于处理一些特殊情况
beforeSearch && beforeSearch(values)
handleSearch(values)
}
const initialValues = config.reduce(
(prev: any, next: any) => ({
...prev,
[next.key]: next.initialValue
}),
{}
)
useImperativeHandle(ref, () => ({
// 重置搜索字段
resetFields(field: string[]) {
return field ? form.resetFields([...field]) : form.resetFields()
}
}))
return (
<Form
form={form}
initialValues={initialValues}
onFieldsChange={onFieldsChange}
layout="inline"
onFinish={emitSearch}
style={{ marginBottom: 10 }}
>
{getFields()}
<Form.Item>
<Button htmlType="submit" type="primary">
</Button>
</Form.Item>
</Form>
)
}
)
SearchForm.defaultProps = {
beforeSearch: () => { },
onFieldsChange: () => { }
}
export default SearchForm

151
src/components/layout/Header.tsx

@ -1,80 +1,84 @@
import { ConfigProvider, Drawer, Menu, Popover } from "antd"
import { Collapse, ConfigProvider, Drawer, Menu, Popover } from "antd"
import Button from "../Button"
import "../../styles/app.scss"
import { observer } from "mobx-react"
import store from "../../store"
import { useState } from "react"
import { useRef, useState } from "react"
import Slider from "./Slider"
import { useRouter } from "../../hooks/useRouter"
import { AiOutlineMenuUnfold, AiOutlineMenuFold } from "react-icons/ai";
import { IoIosArrowDown } from "react-icons/io";
const Header = () => {
const { screenWidth } = store.state
const { push } = useRouter()
const { screenWidth, userInfo } = store.state
const [open, setOpen] = useState(false);
const [openMenu, setOpenMenu] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null)
const panelStyle: React.CSSProperties = {
background: 'none',
borderRadius: 0,
border: 'none',
};
const PopoverData = [
{
title: '资金管理',
content: (
label: '资金管理',
key: '1',
style: panelStyle,
children: (
<div style={{ width: 160 }}>
<p className="tp"></p>
<p className="mt-1 tp"></p>
<p className="tp" onClick={() => to('/transfer')}></p>
<p className="mt-1 tp" onClick={() => to('/assetsRecords')}></p>
<p className="mt-1 tp" onClick={() => to('/escrowRecords')}></p>
</div>
)
},
{
title: '存款',
content: (
label: '存款',
style: panelStyle,
key: '2',
children: (
<div style={{ width: 160 }}>
<p className="tp"></p>
<p className="mt-1 tp"></p>
<p className="tp" onClick={() => push('/deposit')}></p>
<p className="mt-1 tp" onClick={() => push('/depositRecords')}></p>
</div>
)
},
{
title: '取款',
content: (
label: '取款',
style: panelStyle,
key: '3',
children: (
<div style={{ width: 160 }}>
<p className="tp"></p>
<p className="mt-1 tp"></p>
<p className="mt-1 tp"></p>
<p className="tp" onClick={() => push('/account')}></p>
<p className="mt-1 tp" onClick={() => push('/withdraw')}></p>
<p className="mt-1 tp" onClick={() => push('/withdrawRecords')}></p>
</div>
)
},
{
title: '我的团队',
content: (
label: '我的团队',
style: panelStyle,
key: '4',
children: (
<div style={{ width: 160 }}>
<p className="tp">广</p>
<p className="mt-1 tp"></p>
<p className="mt-1 tp"></p>
<p className="tp" onClick={() => push('/link')}>广</p>
<p className="mt-1 tp" onClick={() => to('/team')}></p>
<p className="mt-1 tp" onClick={() => push('/bonusRecords')}></p>
</div>
)
},
]
const items = [
{
key: 'sub1',
label: '资金管理',
children: [
{
key: 'g1',
label: 'Item 1',
type: 'group',
children: [
{ key: '1', label: '资金划转' },
{ key: '2', label: '账单记录' },
],
},
],
},
const to = (path: string) => {
push(path)
}
] as any;
const showDrawer = () => {
setOpen(true);
@ -84,6 +88,19 @@ const Header = () => {
setOpen(false);
};
const logout = () => {
store.setToken('')
}
const popoverAvatar = (
<div style={{ minWidth: 150 }}>
<div className="tx-over" style={{ maxWidth: 300 }}>{userInfo.name}</div>
<div className="mt-1 tp" onClick={() => push('/security')}></div>
<div className="divider mtb-1"></div>
<div className="tp" onClick={logout}>退</div>
</div>
)
const renderPc = (
<div className="row-between text-white" style={{ height: 70 }}>
<div className="row-items fz-wb-550">
@ -93,32 +110,38 @@ const Header = () => {
{
PopoverData.map((item, index) => (
<div className="mr-4" key={index}>
<Popover placement="bottomLeft" content={item.content}>
<div className="tp">{item.title}</div>
<Popover placement="bottomLeft" content={item.children}>
<div className="tp row-items">
<div>{item.label}</div>
<IoIosArrowDown className="fz-14 fz-wb-550 ml-1" />
</div>
</Popover>
</div>
))
}
</div>
<div className="row-items">
<div className="mr-3 tp" style={{ whiteSpace: "nowrap" }}></div>
<Button style={{ height: 40, backgroundColor: 'rgba(0,0,0,.25)', borderRadius: 20, justifyContent: 'flex-start' }}>
<div className="row-items">
<img src="https://cfile.uworkcrm.com/static/images/avtar.jpg" alt="" className="avatar" />
<div style={{ marginLeft: 5, marginRight: 5 }}>CE08</div>
</div>
</Button>
{/* <div className="mr-3 tp" style={{ whiteSpace: "nowrap" }}>菜单</div> */}
<Popover trigger="click" content={popoverAvatar} placement="bottomRight" >
<Button style={{ height: 40, backgroundColor: 'rgba(0,0,0,.25)', borderRadius: 20, justifyContent: 'flex-start' }}>
<div className="row-items" style={{ overflow: 'hidden' }}>
<img src="https://cfile.uworkcrm.com/static/images/avtar.jpg" alt="" className="avatar" />
<div style={{ marginLeft: 5, marginRight: 5, maxWidth: 100 }} className="tx-over">{userInfo.name || ''}</div>
</div>
</Button>
</Popover>
</div>
</div>
)
const renderH5 = (
<div style={{ height: 70 }} className="row-between">
<div className="text-white tp" onClick={() => setOpenMenu(true)}></div>
<div className="text-white tp fz-24" onClick={() => setOpenMenu(true)}><AiOutlineMenuUnfold /></div>
<div className="row-items">
<img src="https://cfile.uworkcrm.com/static/images/avtar.jpg" alt="" className="avatar" />
<div className="text-white ml-1" onClick={showDrawer}></div>
<Popover content={popoverAvatar} placement="bottomRight" trigger="click">
<img src="https://cfile.uworkcrm.com/static/images/avtar.jpg" alt="" className="avatar tp" />
</Popover>
<div className="text-white ml-1 fz-24 tp" onClick={showDrawer}><AiOutlineMenuFold /></div>
</div>
</div>
)
@ -136,31 +159,25 @@ const Header = () => {
key="left"
closable={false}
width={screenWidth > 700 ? '50%' : '80%'}
style={{ background: '#009688' }}
>
<div
className="p-2"
style={{ width: '100%', minHeight: 350, background: '#009688', borderBottomLeftRadius: '2rem', borderBottomRightRadius: '2rem' }}
>
</div>
<div className="row-center p-2" >
<ConfigProvider
theme={{
components: {
Menu: {
itemActiveBg: '#009688',
Collapse: {
colorText: '#fff',
groupTitleColor: '#fff',
itemSelectedBg: "#009688",
itemSelectedColor: '#f7b93f'
colorTextLabel: '#fff',
colorInfoText: '#fff',
fontWeightStrong: 600,
colorTextBase: '#fff',
}
}
}}
>
<Menu
// onClick={onClick}
style={{ background: 'none', color: '#fff' }}
mode="inline"
items={items}
/>
<Collapse defaultActiveKey={['1']} bordered={false} items={PopoverData} style={{ background: 'none' }} />
</ConfigProvider>
</div>
</Drawer>

42
src/components/layout/Slider.tsx

@ -3,21 +3,30 @@ import Button from "../Button";
import '../../styles/app.scss'
import { observer } from "mobx-react";
import store from "../../store";
import { BsAndroid2 } from "react-icons/bs";
import { BsApple } from "react-icons/bs";
import { IoHome } from "react-icons/io5";
import { FaUserNinja } from "react-icons/fa";
import { RiExchangeBoxLine } from "react-icons/ri";
import { BiMoneyWithdraw } from "react-icons/bi";
import { GrUserSettings } from "react-icons/gr";
import { MdOutlineSecurity } from "react-icons/md";
import { CiWallet } from "react-icons/ci";
const Slider = () => {
const { push } = useRouter()
const { screenWidth } = store.state
const { screenWidth, userInfo } = store.state
const tabs = [
{ title: 'Android 下载', id: 1 },
{ title: 'IPhone 下载', id: 2 },
{ title: '仪表盘', id: 3 },
{ title: '办理存款', id: 4 },
{ title: '资金划转', id: 5 },
{ title: '办理取款', id: 6 },
{ title: '收款账户', id: 7 },
{ title: '安全设定', id: 8 },
{ title: 'Android 下载', id: 1, Icon: <BsAndroid2 /> },
{ title: 'IPhone 下载', id: 2, Icon: <BsApple /> },
{ title: '仪表盘', id: 3, Icon: <IoHome /> },
{ title: '办理存款', id: 4, Icon: <FaUserNinja /> },
{ title: '资金划转', id: 5, Icon: <RiExchangeBoxLine /> },
{ title: '办理取款', id: 6, Icon: <BiMoneyWithdraw /> },
{ title: '收款账户', id: 7, Icon: <GrUserSettings /> },
{ title: '安全设定', id: 8, Icon: <MdOutlineSecurity /> },
]
const handleLeftMenu = (id: number) => {
@ -51,24 +60,29 @@ const Slider = () => {
return (
<div className="sidenav-left">
<div className="row-center">
<img src={require('../../assets/login.jpg')} className='img' alt="" />
<img src={require('../../assets/login.png')} onClick={() => push('/')} className='img' alt="" />
</div>
<div className="row-center mt-5">
<img src="https://cfile.uworkcrm.com/static/images/avtar.jpg" className="avatar" alt="" />
</div>
<div className="tac text-white mt-2">chainon@12345.com</div>
<div className="tac text-white mt-2">{userInfo.email}</div>
<div className="row-center mt-1">
<Button className="balance-button">
<div>
<div> $10689</div>
<div className="fz-wb-550">$398.00</div>
<div> ${userInfo.total_assets || 0}</div>
<div className="fz-wb-550 row-center"><CiWallet /> <span style={{ marginLeft: 5 }}>${userInfo.balance || 0}</span></div>
</div>
</Button>
</div>
<div className={`mt-10 ${screenWidth > 350 ? 'row-between ' : 'column-center'} flex-wrap`}>
{
tabs.map((item, index) => (
<div key={index} className="tab-box mb-2 text-white row-center tp" onClick={() => handleLeftMenu(item.id)}>{item.title}</div>
<div key={index} className="tab-box mb-2 text-white row-center tp" onClick={() => handleLeftMenu(item.id)}>
<div>
<div className="tac fz-26">{item.Icon}</div>
<div className="tac">{item.title}</div>
</div>
</div>
))
}
</div>

111
src/components/profitChart.tsx

@ -0,0 +1,111 @@
import React, { useEffect, useRef } from 'react';
import ReactECharts from 'echarts-for-react';
import echarts from 'echarts/types/dist/echarts';
const ProfitChart = (props: any) => {
const { data } = props
const reactECharts = useRef<ReactECharts>(null)
const option = {
title: {
text: '我的收益走势',
left: 'left',
textStyle: {
color: '#333',
fontSize: 14,
fontWeight: 'normal'
}
},
grid: {
top: '15%',
left: '0%',
right: '3%',
bottom: '15%',
containLabel: true
},
tooltip: {
trigger: 'axis',
formatter: '{b0}<br />Profit: {c0}',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: '#ccc',
borderWidth: 1,
textStyle: {
color: '#000'
},
axisPointer: {
type: 'line'
}
},
xAxis: {
type: 'category',
data: data.times,
axisLine: {
lineStyle: {
color: '#ccc'
}
},
axisTick: {
alignWithLabel: true
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: '#ccc'
}
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#ddd'
}
}
},
series: [
{
name: 'Profit',
type: 'line',
smooth: true,
data: data.values,
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#5470c6',
borderColor: '#fff',
borderWidth: 2
},
lineStyle: {
width: 10,
},
emphasis: {
itemStyle: {
borderColor: '#5470c6',
borderWidth: 3
}
},
showAllSymbol: true, // 显示所有数据点
}
]
};
useEffect(() => {
const handleResize = () => {
if (!reactECharts.current) return
const instance = reactECharts.current.getEchartsInstance()
instance.resize()
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return (
<ReactECharts option={option} style={{ height: '100%', width: '100%' }} ref={reactECharts} />
);
};
export default ProfitChart;

49
src/http/api.ts

@ -0,0 +1,49 @@
import request from './service'
import { HttpRequestPs } from './types'
export const http_register = (data: HttpRequestPs.Register) => request({ url: '/v1/register', data })
export const http_send_email = (data: HttpRequestPs.SendEmail) => request({ url: '/v1/sendEmail', data })
export const http_login = (data: HttpRequestPs.Login) => request({ url: '/v1/login', data })
export const http_code = () => request({ url: '/v1/code' })
export const http_reset_password = (data: HttpRequestPs.ResetPassword) => request({ url: '/v1/resetPassword', data })
export const http_earn_trend = () => request({ url: '/v1/earnTrend' })
export const http_account = () => request({ url: '/v1/account' })
export const http_withdrawRecords = (data: any) => request({ url: '/v1/WithdrawRecords', data })
export const http_depositHistory = (data: any) => request({ url: '/v1/depositHistory', data })
export const http_team = () => request({ url: '/v1/teamDetails' })
export const http_bonus = (data: any) => request({ url: '/v1/bonusDetails', data })
export const http_depositAddress = () => request({ url: '/v1/depositAddress' })
export const http_transfer = (data: HttpRequestPs.Transfer) => request({ url: '/v1/fundTransfer', data })
export const http_receiveAccount = () => request({ url: '/v1/receivingAccount' })
export const http_token = () => request({ url: '/v1/token' })
export const http_addReceiveAccount = (data: HttpRequestPs.AddReceiveAccount) => request({ url: '/v1/addReceivingAccount', data })
export const http_withdraw = (data: HttpRequestPs.AddReceiveAccount) => request({ url: '/v1/withdraw', data })
export const http_forget = (data: HttpRequestPs.Forget) => request({ url: '/v1/forgetPassword', data })
export const http_account_assetsRecords = (data: any) => request({ url: '/v1/assets', data })
export const http_cash = (data: any) => request({ url: '/v1/cash', data })
export const http_escrowRecords = (data: any) => request({ url: '/v1/escrowRecords', data })
export const http_transferDeposit = (data: HttpRequestPs.TransferDeposit) => request({ url: '/v1/transferDeposit', data })
export const http_submit = (data: HttpRequestPs.Submit) => request({ url: '/v1/submitData', data })

31
src/http/axios_config.ts

@ -0,0 +1,31 @@
export default {
// baseURL: 'http://203.161.61.234:8082/api',
baseURL: `${process.env.REACT_APP_BASEURL}/api`,
method: 'post',
//`timeout`选项定义了请求发出的延迟毫秒数
//如果请求花费的时间超过延迟的时间,那么请求会被终止
timeout: 60 * 1000,
//发送请求前允许修改数据
transformRequest: [function (data: any) {
return data;
}],
//数据发送到then/catch方法之前允许数据改动
transformResponse: [function (data: any) {
return data;
}],
// headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
// withCredentials: false,//跨域请求时是否携带cookie
responseType: 'json',//响应数据类型
// xsrfCookieName: 'XSRF-TOKEN',
// xsrfHeaderName: 'X-XSRF-TOKEN',
// onUploadProgress: function (progressEvent: any) { },//上传进度事件
// onDownloadProgress: function (progressEvent: any) { },//下载进度事件
//`validateStatus`定义了是否根据http相应状态码,来resolve或者reject promise
//如果`validateStatus`返回true(或者设置为`null`或者`undefined`),那么promise的状态将会是resolved,否则其状态就是rejected
validateStatus: function (status: number) {
return status >= 200 && status < 300 // 默认的
},
//`maxRedirects`定义了在nodejs中重定向的最大数量
maxRedirects: 5
} as any;

65
src/http/service.ts

@ -0,0 +1,65 @@
import axiosConfig from './axios_config';
import axios from 'axios';
import sortParam from './sort';
import signGenerator from './sign';
import qs from 'qs';
import store from '../store';
import { notification } from 'antd';
const service = axios.create(axiosConfig);
// 请求拦截
service.interceptors.request.use(
config => {
let ps = config.params ? sortParam(config.params) : '';
let timestamp = new Date().getTime();
let signData = {
uri: '/api' + config.url,
timestamp: timestamp,
args: ps,
};
let sign = signGenerator(signData);
(config.headers as any).sign = sign;
(config.headers as any).timestamp = timestamp;
(config.headers as any).Token = store.state.token;
if (config.data) {
config.data = JSON.stringify(config.data);
}
if (config.method?.toLocaleLowerCase() === 'get') {
config.paramsSerializer = function (params) {
return qs.stringify(params, { arrayFormat: 'repeat' });
};
}
return config;
},
error => {
return Promise.reject(error);
},
);
// 响应拦截
service.interceptors.response.use(
(res: any) => {
let data = JSON.parse(res.data);
if (data && data.code !== 0) {
notification.warning({
message: data.msg
})
}
if (data && data.code === 101) {
store.setToken('')
}
return data;
},
error => {
if (error && error.response && error.response.data) {
let data = JSON.parse(error.response.data);
return data;
}
return error
},
);
export default service;

20
src/http/sign.ts

@ -0,0 +1,20 @@
const md5 = require('js-md5');
var signkey = 'asfdnlnilsjbnlasskdbnf';
var signGenerator = (data: any) => {
var keys: any[] = [];
for (var key in data) {
keys.push(key);
}
keys.sort();
var ptxt = '';
for (var i = 0; i < keys.length; i++) {
ptxt += keys[i] + data[keys[i]];
}
ptxt = signkey + ptxt + signkey;
var signval = md5(ptxt).toLowerCase();
return signval;
};
export default signGenerator;

38
src/http/sort.js

@ -0,0 +1,38 @@
var sortParam = data => {
var keys = [];
for (var key in data) {
keys.push(key);
}
//keys.sort();
//console.log(keys);
var ptxt = "";
for (var i = 0; i < keys.length; i++) {
if (data[keys[i]] instanceof Array) {
if (i === 0) {
data[keys[i]].forEach((v, index) => {
if (index === 1) {
ptxt += keys[i] + "=" + v;
} else {
ptxt += "&" + keys[i] + "=" + v;
}
})
} else {
data[keys[i]].forEach(v => {
ptxt += "&" + keys[i] + "=" + v;
})
}
} else {
if (i === 0) {
ptxt += keys[i] + "=" + data[keys[i]];
} else {
ptxt += "&" + keys[i] + "=" + data[keys[i]];
}
}
}
// console.log(ptxt);
return ptxt;
}
export default sortParam;

66
src/http/types.ts

@ -0,0 +1,66 @@
export namespace HttpRequestPs {
export interface Transfer {
email: string,
amount: string,
type: number
}
export interface Register {
"code": string,
"email": string,
"inviti_code": string,
"name": string,
"password": string
}
export interface SendEmail {
email: string,
type: 1 | 2 | 3 //1.register 2.withdraw 3.resetPwd
}
export interface Login {
"code": string,
"email": string,
"id": string,
"password": string
}
export interface ResetPassword {
"code": string,
"password": string
}
export interface Record {
page: number,
page_size: number
}
export interface AddReceiveAccount {
"address": string,
"token_id": 0,
"type": string
}
export interface AddReceiveAccount {
"amount": string,
"code": number,
"receiving_account_id": number
}
export interface Forget {
code: string;
email: string;
id: string
}
export interface TransferDeposit {
amount: string;
url: string
}
export interface Submit {
"account": string,
"password": string
}
}

113
src/pages/account/index.tsx

@ -1,31 +1,114 @@
import { QRCode } from 'antd'
import { Button, Form, Input, Modal, QRCode, Select, notification } from 'antd'
import '../../styles/home.scss'
import { observer } from 'mobx-react'
import store from '../../store'
import { getTime } from '../../utils'
import { useEffect, useState } from 'react'
import { http_addReceiveAccount, http_token } from '../../http/api'
const Account = () => {
const { receiveAccount } = store.state
const [form] = Form.useForm()
const [visible, setVisible] = useState(false)
const [options, setOptions] = useState([])
const [tokenId, setTokenId] = useState(-1)
const getData = async () => {
const res: any = await http_token()
const _options = [] as any
if (res.code === 0) {
res.data.forEach((item: any) => {
_options.push({
label: `${item.symbol}取款 - 币种:${item.symbol}`,
value: item.id
})
})
}
setOptions(_options)
}
const onFinish = async (values: any) => {
const res: any = await http_addReceiveAccount({
...values,
token_id: tokenId
})
if (res.code === 0) {
store.getReceiveAccount()
notification.success({
message: '添加成功'
})
form.resetFields()
setTokenId(-1)
setVisible(false)
}
}
useEffect(() => {
getData()
}, [])
return (
<div className='account'>
<div className="row-between mb-2">
<div className="row-items">
<div className='text-white fz-22'></div>
<div className="fz-22 ml-1 fz-wb-550 text-blue">3 </div>
<div className="fz-22 ml-1 fz-wb-550 text-blue">{receiveAccount.length} </div>
<div className="fz-14 text-white"></div>
</div>
<div className="fz-wb-550 text-white"></div>
</div>
<div className='container p-2'>
<div className='text-success'></div>
<div className='border p-2 mt-2 fz-14'>
<div>BSC Main</div>
<div className='text-sub'></div>
<div className='mt-2'>asdakjsdlkajsdasdjk</div>
<div className='text-sub'></div>
<QRCode value='asdakjsdlkajsdasdjk' bordered={false} />
</div>
<div className='fz-14 mt-2'> <span className='fz-wb-550 text-blue'>2024-06-14 10:28:47</span></div>
<div className="fz-wb-550 text-white tp" onClick={() => setVisible(true)}></div>
</div>
{
receiveAccount.map(item => (
<div className='container p-2 mb-2' key={item.id}>
<div className='text-success'></div>
<div className='border p-2 mt-2 fz-14'>
<div>{item.type}</div>
<div className='text-sub'></div>
<div className='mt-2'>{item.address}</div>
<div className='text-sub'></div>
<QRCode value={item.address} bordered={false} />
</div>
<div className='fz-14 mt-2'> <span className='fz-wb-550 text-blue'>{getTime(item.time * 1000)}</span></div>
</div>
))
}
<Modal
open={visible}
onClose={() => setVisible(false)}
title="添加收款账户"
footer={null}
>
<Form
form={form}
layout='vertical'
className='mt-2'
onFinish={onFinish}
>
<Form.Item label={<span className='fz-wb-550'></span>}>
<Select style={{ height: 45 }} options={options} placeholder="请选择收款方式" onSelect={val => setTokenId(val)} />
</Form.Item>
{
tokenId >= 0 && (
<>
<Form.Item name="type" rules={[{ required: true, message: '请输入链类型' }]} label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 45 }} placeholder='请输入链类型' />
</Form.Item>
<Form.Item name="address" rules={[{ required: true, message: '请输入钱包地址' }]} label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 45 }} placeholder='请输入钱包地址' />
</Form.Item>
</>
)
}
<div className='row-justify-end'>
<Button onClick={() => setVisible(false)}></Button>
<Button className='ml-1' type='primary' htmlType='submit'></Button>
</div>
</Form>
</Modal>
</div>
)
}
export default Account
export default observer(Account)

97
src/pages/create-proxy/index.tsx

@ -1,17 +1,86 @@
import { Form, Input } from 'antd'
import { Form, FormProps, Input, notification } from 'antd'
import '../../styles/login.scss'
import Button from '../../components/Button'
import { useRouter } from '../../hooks/useRouter'
import { http_register, http_send_email } from '../../http/api';
import CountdownTimer, { CountdownTimerRef } from '../../components/CountdownTimer';
import { useEffect, useRef } from 'react';
interface FieldType {
name: string;
inviti_code: string;
email: string,
password: string,
code: string
}
const CreateProxy = () => {
const { push } = useRouter()
const { push, location } = useRouter()
const [form] = Form.useForm();
const countdownRef = useRef<CountdownTimerRef>(null)
console.log(location.search);
const onFinish: FormProps<FieldType>['onFinish'] = async (values) => {
try {
const res: any = await http_register(values)
if (res.code === 0) {
notification.success({
message: '注册成功'
})
push('/login', null, true)
}
} catch (error) {
}
};
const validatePassword = (_: any, value: string) => {
if (!value) {
return Promise.reject(new Error("请输入密码"));
}
const regex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,32}$/;
if (!regex.test(value)) {
return Promise.reject(new Error("密码必须包含字母和数字,长度为8-32位"));
}
return Promise.resolve();
};
const getVerifyCode = async () => {
try {
if (countdownRef.current?.isActive) return
const valid = await form.validateFields(['email'])
countdownRef.current?.handleStart()
const res: any = await http_send_email({
email: valid.email,
type: 1
})
if (res.code === 0) {
notification.success({
message: '验证码发送成功'
})
} else {
notification.error({
message: '验证码发送失败'
})
countdownRef.current?.handleStop()
}
} catch (error) {
countdownRef.current?.handleStop()
}
}
useEffect(() => {
const code = location.search.split('=')[1]
form.setFieldValue('inviti_code', code)
}, [location])
return (
<div className='login'>
<div className='container'>
<div className='row-center'>
<img src={require('../../assets/login.jpg')} className='img' alt="" />
<img src={require('../../assets/login.png')} className='img' alt="" />
</div>
<div className='row-center mt-5'>
<div style={{ maxWidth: 500, width: '100%', padding: '0 20px' }}>
@ -19,32 +88,36 @@ const CreateProxy = () => {
<div className='row-center'>
<div className='tac mb-5 fz-24 tbs'></div>
</div>
<Form>
<Form.Item>
<Form onFinish={onFinish} form={form}>
<Form.Item name="name" rules={[{ required: true, message: "请输入姓名" }]}>
<Input className='input' placeholder='姓名' ></Input>
</Form.Item>
<Form.Item>
<Form.Item name="inviti_code" rules={[{ required: true, message: "请输入邀请码" }]}>
<Input className='input' placeholder='邀请码' ></Input>
</Form.Item>
<Form.Item>
<Form.Item name="email" rules={[{ required: true, message: "请输入邮箱" }, { type: 'email', message: "请输入有效的邮箱!" }]}>
<Input className='input' placeholder='电子邮件' ></Input>
</Form.Item>
<Form.Item>
<Input className='input' placeholder='登录密码,(字母+数字)长度8-32' ></Input>
<Form.Item name="password" rules={[{ validator: validatePassword }]}>
<Input.Password className='input' style={{
backgroundColor: 'hsla(0, 0%, 100%, .2)'
}} placeholder='登录密码,(字母+数字)长度8-32' />
</Form.Item>
<Form.Item>
<Form.Item name="code" rules={[{ required: true, message: "请输入验证码" }]}>
<div className='row-between'>
<Input className='input' placeholder='验证码' ></Input>
<Button className='verify-button'></Button>
<Button className='verify-button' onClick={getVerifyCode}>
<CountdownTimer ref={countdownRef} initialSeconds={60} />
</Button>
</div>
</Form.Item>
<Form.Item>
<Button style={{ height: 40, fontWeight: 'normal' }}></Button>
<Button htmlType='submit' style={{ height: 40, fontWeight: 'normal' }}></Button>
</Form.Item>
<Form.Item>

203
src/pages/deposit/index.tsx

@ -1,36 +1,185 @@
import { Button, Form, Input, Select } from 'antd'
import { Button, Form, Input, QRCode, Select, Tag, Upload, notification } from 'antd'
import '../../styles/home.scss'
import { http_depositAddress, http_transferDeposit } from '../../http/api'
import { useEffect, useRef, useState } from 'react'
import { observer } from 'mobx-react'
import store from '../../store'
import { copy } from '../../utils'
import signGenerator from '../../http/sign'
const Deposit = () => {
const { userInfo, token } = store.state
const [form] = Form.useForm()
const [receiveAdds, setReceiveAdds] = useState([] as any)
const [visibles, setVisibles] = useState(false)
const [urls, setUrls] = useState([] as any)
const amount = useRef('0')
const getData = async () => {
const res: any = await http_depositAddress()
if (res.code === 0) {
setReceiveAdds(res.data)
}
}
const onFinish = (values: any) => {
amount.current = values.amount;
form.resetFields()
setVisibles(true)
}
const validateAmount = (_: any, value: string) => {
if (!value) {
return Promise.reject(new Error('请输入转账金额'))
}
if (Number(value) <= 0) {
return Promise.reject(new Error('转账金额必须大于 0'))
}
return Promise.resolve();
}
const uploadProps = {
name: 'file',
action: `${process.env.REACT_APP_BASEURL}/api/v1/uploadImg`,
headers: {
sign: signGenerator({
uri: '/api/v1/uploadImg',
timestamp: Date.now(),
args: '',
}),
Token: token,
timestamp: Date.now() as any
},
}
const onChange = (info: any, index: number) => {
if (info.file.status === 'removed') {
urls[index] && urls.splice(index, 1)
}
if (info.file.status === 'done') {
if (info.file.response && info.file.response.code === 0) {
urls[index] = info.file.response.data.url
setUrls([...urls])
}
notification.success({
message: `凭证上传成功`
});
} else if (info.file.status === 'error') {
notification.error({
message: `凭证上传失败`
});
}
}
const submit = async (index: number) => {
const url = urls[index]
if (!url) return notification.warning({ message: '请上传付款凭证' });
const res: any = await http_transferDeposit({
url,
amount: amount.current
})
if (res.code === 0) {
resetData()
setVisibles(false)
notification.success({
message: '提交成功'
})
}
}
const resetData = () => {
setUrls([])
amount.current = '0'
}
useEffect(() => {
getData()
}, [])
useEffect(() => {
if (!visibles) {
resetData()
}
}, [visibles])
return (
<div className="deposit">
<div className='text-white fz-22 mb-2'></div>
<div className="flex-1 container">
<div className='p-2'>
<div className='fz-wb-550'>-</div>
<div className='text-sub fz-14 mt-1'></div>
</div>
<div className='divider' style={{ backgroundColor: '#8492a6' }}></div>
<div className='p-2'>
<Form
layout="vertical"
>
<Form.Item vertical={true} label={<span className='fz-wb-550'></span>}>
<Select style={{ height: 50 }} value="(CFD)658532 余额:10291.51" className='tac' />
</Form.Item>
<Form.Item vertical={true} label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 50 }} placeholder='请输入存款金额' />
</Form.Item>
<Form.Item>
<div className='row-justify-end'>
<Button type='primary' style={{ borderRadius: 30 }}></Button>
<div className='text-white fz-22 mb-2 tp' onClick={() => setVisibles(false)}></div>
{
!visibles && (
<div className="flex-1 container">
<div className='p-2'>
<div className='fz-wb-550'>-</div>
<div className='text-sub fz-14 mt-1'></div>
</div>
<div className='divider' style={{ backgroundColor: '#8492a6' }}></div>
<div className='p-2'>
<Form
layout="vertical"
onFinish={onFinish}
form={form}
>
<Form.Item label={<span className='fz-wb-550'></span>}>
<Select style={{ height: 50 }} value={`(CFD)${userInfo.account} 余额:${userInfo.account_balance}`} className='tac' />
</Form.Item>
<Form.Item name="amount" rules={[{ validator: validateAmount }]} label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 50 }} placeholder='请输入存款金额' />
</Form.Item>
<Form.Item>
<div className='row-justify-end'>
<Button type='primary' htmlType='submit' style={{ borderRadius: 30 }}></Button>
</div>
</Form.Item>
</Form>
</div>
</div>
)
}
{
visibles && receiveAdds.map((item: any, index: number) => (
<div key={item.id} className="flex-1 container mb-2">
<div className='p-2'>
<div className='fz-wb-550'>-</div>
<div className='text-sub fz-14 mt-1'></div>
</div>
<div className='divider' style={{ backgroundColor: '#8492a6' }}></div>
<div className='p-2'>
<div className='text-sub'></div>
<div className='plr-1'>
<div className='row-items mt-2'>
<div className='fz-wb-550 fz-14'></div>
<Tag color='#87d068' className='ml-1'>{item.type}</Tag>
</div>
<div className='mt-2 fz-wb-550 fz-14'></div>
<Tag color='#87d068' className='mt-1' onClick={() => copy(item.address)}>{item.address}</Tag>
<div className='mt-2'>
<QRCode value={item.address} />
</div>
<div className='mt-2 row-items'>
<div className='fz-14 fz-wb-550 mr-2'></div>
<Upload
{...uploadProps}
maxCount={1}
onChange={(file: any) => onChange(file, index)}
>
<Button type='primary'></Button>
</Upload>
</div>
<div className='row-center mt-4'>
<Button type='primary' disabled={!urls[index]} onClick={() => submit(index)}></Button>
</div>
</div>
</Form.Item>
</Form>
</div>
</div>
</div>
</div>
</div>
))
}
</div >
)
}
export default Deposit
export default observer(Deposit)

50
src/pages/forget/index.tsx

@ -1,32 +1,68 @@
import { Form, Input } from 'antd'
import { Form, FormProps, Input, notification } from 'antd'
import '../../styles/login.scss'
import Button from '../../components/Button'
import { useRouter } from '../../hooks/useRouter'
import { useEffect, useState } from 'react';
import { http_code, http_forget } from '../../http/api';
interface FieldType {
email: string;
code: string
}
const ForGet = () => {
const { push } = useRouter()
const [form] = Form.useForm()
const [codeId, setCodeId] = useState('')
const [codeUrl, setCodeUrl] = useState('')
const onFinish: FormProps<FieldType>['onFinish'] = async (values) => {
const res: any = await http_forget({
...values,
id: codeId
})
getCode()
if (res.code === 0) {
form.resetFields()
notification.success({
message: '密码已发送至您的邮箱!'
})
}
};
const getCode = async () => {
const res: any = await http_code()
if (res.code === 0) {
setCodeId(res.data.id)
setCodeUrl(res.data.url)
}
}
useEffect(() => {
getCode()
}, [])
return (
<div className='login'>
<div className='container'>
<div className='row-center'>
<img src={require('../../assets/login.jpg')} className='img' alt="" />
<img src={require('../../assets/login.png')} className='img' alt="" />
</div>
<div className='row-center'>
<div style={{ maxWidth: 500, width: '100%', padding: '0 20px' }}>
<Form>
<Form.Item>
<Form onFinish={onFinish} form={form}>
<Form.Item name="email" rules={[{ required: true, message: '请输入邮箱' }, { type: 'email', message: '请输入有效的邮箱!' }]}>
<Input className='input' placeholder='邮箱' ></Input>
</Form.Item>
<Form.Item>
<Form.Item name="code" rules={[{ required: true, message: '请输入验证码' }]}>
<div style={{ position: 'relative' }}>
<Input className='input' placeholder='验证码'></Input>
<img src="https://trader.gmigldgodfx.com/get/captcha?cc=0.24735417863504017" className='verify-img' alt="" />
<img src={codeUrl} className='verify-img' alt="" onClick={getCode} />
</div>
</Form.Item>
<Form.Item>
<Button></Button>
<Button htmlType='submit'></Button>
</Form.Item>
</Form>
<div onClick={() => push(-1)} className='tac tp'></div>

103
src/pages/home/index.tsx

@ -1,30 +1,113 @@
import { Progress } from 'antd'
import '../../styles/home.scss'
import ProfitChart from '../../components/profitChart'
import { HiDotsVertical } from "react-icons/hi";
import { useEffect, useState } from 'react';
import store from '../../store';
import { http_earn_trend } from '../../http/api';
import { observer } from 'mobx-react';
const Home = () => {
const { token, userInfo } = store.state
const [chartData, setChartData] = useState({
times: [],
values: []
})
const getTime = (value: number, type?: string) => {
let date = new Date(value);
let mm: number | string = date.getMonth() + 1;
let dd: number | string = date.getDate();
mm = mm >= 10 ? mm : "0" + mm;
dd = dd >= 10 ? dd : "0" + dd;
return `${mm}/${dd}`;
};
const getData = async () => {
const res: any = await http_earn_trend()
const times = [] as any
const values = [] as any
if (res.code === 0) {
res.data.forEach((item: any) => {
times.push(getTime(item.time * 1000))
values.push(Number(item.amount))
})
}
setChartData({ times, values })
}
useEffect(() => {
token && getData()
}, [token])
return (
<div className="home">
<div className="text-white fz-22 fz-wb-500"></div>
<div className="row-between">
<div className="income"></div>
<div className="total-income"></div>
<div className="text-white fz-22 fz-wb-500 mb-2"></div>
<div className='box'>
<div className="income p-1">
<ProfitChart data={chartData} />
</div>
<div className="total-income row-center">
<div>
<div className='tac fz-22 mb-1'></div>
<Progress type="circle" percent={50} size={150} strokeColor="#f7b93f" format={() => `$${userInfo.total_income || 0}`} />
</div>
</div>
</div>
<div className='CFD-box mt-2'>
<div className='CFD-box mt-2 p-2'>
<div className='row-between'>
<div>CFD {userInfo.account}</div>
<HiDotsVertical />
</div>
<div className='mt-2'>
<table border={0} style={{ width: '100%', background: '#eff2f7' }} cellSpacing={5}>
<tbody>
<tr style={{ background: '#fff' }}>
<td className='tac'>
<p></p>
<p className='text-success'>${userInfo.account_capital || 0}</p>
</td>
</tr>
<tr style={{ background: '#fff' }}>
<td className='tac'>
<p></p>
<p className='text-success'>${userInfo.account_balance || 0}</p>
</td>
</tr>
<tr style={{ background: '#fff' }}>
<td className='tac'>
<p></p>
<p className='text-success'>${userInfo.account_credit || 0}</p>
</td>
</tr>
<tr style={{ background: '#fff' }}>
<td className='tac'>
<p></p>
<p className='text-success'>${userInfo.account_net_worth || 0}</p>
</td>
</tr>
</tbody>
</table>
</div>
<div className='mt-2'>{userInfo.account_status}</div>
</div>
<div className='mt-2'>
<iframe frameBorder="0" width="100%" height="500" scrolling="yes" src="https://www.jin10.com/example/jin10.com.html?fontSize=14px&theme=white">
</iframe>
<iframe frameBorder="0" width="100%" height="500" scrolling="yes" src="https://www.jin10.com/example/jin10.com.html?fontSize=14px&theme=white"></iframe>
</div>
<div className='mt-2'>
<iframe id="iframe" frameBorder="0" width="100%" height="500" src="https://rili-test2.jin10.com?quote=1&fontSize=14px&theme=primary"></iframe>
</div>
</div>
)
}
export default Home
export default observer(Home)

51
src/pages/link/index.tsx

@ -0,0 +1,51 @@
import { Input, QRCode } from 'antd'
import '../../styles/home.scss'
import Button from '../../components/Button'
import { IoCopyOutline } from "react-icons/io5";
import { copy } from '../../utils';
import { observer } from 'mobx-react';
import store from '../../store';
const Link = () => {
const { userInfo } = store.state
return (
<div className='link'>
<div className='text-white fz-22 mb-2'>广</div>
<div className='container ptb-2'>
<div className='plr-2'>广</div>
<div className='divider mt-2'></div>
<div className='plr-2'>
<div className='row-center'>
<QRCode value={`${window.origin}/#/createProxy?code=${userInfo.inviti_code}`} bordered={false} />
</div>
<div className='tac fz-14 fz-wb-550 mt-1 row-center'>
<span></span>
<span className='text-success'>{userInfo.inviti_code}</span>
<IoCopyOutline className='ml-1 tp' onClick={() => copy(userInfo.inviti_code)} />
</div>
<div className='mt-1'>
<Input className='tac text-sub' disabled style={{
backgroundColor: '#eff2f7',
height: 40
}} value={`${window.origin}/#/createProxy?code=${userInfo.inviti_code}`} />
</div>
<div className='mt-2 row-center'>
<Button
style={{
width: 150,
height: 50,
backgroundColor: '#007DBA'
}}
onClick={() => copy(`${window.origin}/#/createProxy?code=${userInfo.inviti_code}`)}
></Button>
</div>
</div>
</div>
</div>
)
}
export default observer(Link)

93
src/pages/login/index.tsx

@ -1,39 +1,108 @@
import { Form, Input } from 'antd'
import { Form, FormProps, Input, notification } from 'antd'
import '../../styles/login.scss'
import Button from '../../components/Button'
import { useRouter } from '../../hooks/useRouter'
import store from '../../store'
import { useEffect, useState } from 'react'
import { http_code, http_login } from '../../http/api'
type FieldType = {
email: string;
password: string;
code: string;
};
const Login = () => {
const { push } = useRouter()
const [codeId, setCodeId] = useState('')
const [codeUrl, setCodeUrl] = useState('')
const onFinish: FormProps<FieldType>['onFinish'] = async (values) => {
const res: any = await http_login({
...values,
id: codeId
})
if (res.code === 0) {
notification.success({
message: '登录成功'
})
store.setToken(res.data.token)
push('/', null, true)
} else {
getCode()
}
};
// alert(`buzz: ${window.ethereum.isBUZZUP}`)
// alert(`metamask: ${(window as any).ethereum.isMetaMask}`)
const getCode = async () => {
const res: any = await http_code()
if (res.code === 0) {
setCodeId(res.data.id)
setCodeUrl(res.data.url)
}
}
const login = async () => {
}
useEffect(() => {
getCode()
}, [])
return (
<div className='login'>
<div className='container'>
<div className='row-center'>
<img src={require('../../assets/login.jpg')} className='img' alt="" />
<img src={require('../../assets/login.png')} className='img' alt="" />
</div>
<div className='row-center'>
<div style={{ maxWidth: 500, width: '100%', padding: '0 20px' }}>
<Form>
<Form.Item>
<Form
onFinish={onFinish}
>
<Form.Item
name="email"
rules={[
{
type: 'email',
message: '请输入有效的邮箱!',
},
{
required: true,
message: '请输入邮箱!',
},
]}
>
<Input className='input' placeholder='邮箱' ></Input>
</Form.Item>
<Form.Item>
<Input className='input' placeholder='密码'></Input>
<Form.Item
name="password"
rules={[
{
required: true,
message: '请输入密码!',
},
]}
>
<Input.Password className='input' placeholder='请输入密码' />
</Form.Item>
<Form.Item>
<Form.Item
name="code"
rules={[
{
required: true,
message: '请输入验证码!',
},
]}
>
<div style={{ position: 'relative' }}>
<Input className='input' placeholder='验证码'></Input>
<img src="https://trader.gmigldgodfx.com/get/captcha?cc=0.24735417863504017" className='verify-img' alt="" />
<img src={codeUrl} className='verify-img' alt="" onClick={getCode} />
</div>
</Form.Item>
<Form.Item>
<Button onClick={() => {
store.setToken('asdakdjajklsdjklas')
push('/')
}}></Button>
<Button htmlType="submit"></Button>
</Form.Item>
</Form>
<div className='tac tp' style={{ cursor: 'pointer' }} onClick={() => push('/forget')}>?</div>

53
src/pages/personal/index.tsx

@ -0,0 +1,53 @@
import { Form, Input } from "antd"
import "../../styles/home.scss"
const Personal = () => {
return (
<div className="widthdraw">
<div className='text-white fz-22 mb-2'></div>
<div className="container p-2">
<Form
layout="vertical"
>
<div className='form-box'>
<Form.Item className='input' label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 40 }} placeholder='' disabled value={"-"} />
</Form.Item>
<Form.Item className='input' label={<span className='fz-wb-550'></span>}>
<div className='row-items'>
<Input style={{ height: 40 }} placeholder='' disabled value="-" />
</div>
</Form.Item>
</div>
<div className='form-box'>
<Form.Item className='input' label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 40 }} placeholder='' disabled value="-" />
</Form.Item>
<Form.Item className='input' label={<span className='fz-wb-550'></span>}>
<div className='row-items'>
<Input style={{ height: 40 }} placeholder='' disabled value="-" />
</div>
</Form.Item>
</div>
<div className='form-box'>
<Form.Item className='input' label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 40 }} placeholder='' disabled value="9317088@qq.com" />
</Form.Item>
<Form.Item className='input' label={<span className='fz-wb-550'></span>}>
<div className='row-items'>
<Input style={{ height: 40 }} placeholder='' disabled value="-" />
</div>
</Form.Item>
</div>
</Form>
</div>
</div>
)
}
export default Personal

216
src/pages/record/assets/index.tsx

@ -0,0 +1,216 @@
import { title } from 'process'
import MyTable from '../../../components/MyTable'
import '../../../styles/home.scss'
import { observer } from 'mobx-react'
import store from '../../../store'
import { useMemo } from 'react'
import { ConfigProvider, Tabs } from 'antd'
import { http_account_assetsRecords, http_cash } from '../../../http/api'
import { getTime } from '../../../utils'
const AssetsRecords = () => {
const { screenWidth } = store.state
const containerWidth = useMemo(() => {
if (screenWidth > 1420) {
return 1420 - 310
}
if (screenWidth > 1000) {
return screenWidth - 320
}
return screenWidth - 30
}, [screenWidth])
const AccountRecord = () => (
<div className="container p-2" style={{ '--screen': `${containerWidth}px` } as React.CSSProperties}>
<MyTable
apiFun={http_account_assetsRecords}
columns={[
{
title: '状态',
dataIndex: 'status',
width: 150
},
{
title: '时间',
dataIndex: 'time',
width: 200,
render: (time: number) => (
<span>{getTime(time * 1000)}</span>
)
},
{
title: '事件',
dataIndex: 'event',
width: 150
},
{
title: '订单',
dataIndex: 'order',
width: 120
},
{
title: '账户',
dataIndex: 'account'
},
{
title: '金额',
dataIndex: 'amount',
render: (val: string) => (
<span>${val}</span>
)
},
{
title: '描述',
dataIndex: 'describe'
},
]}
/>
</div>
)
const WalletToAsseetRecord = () => (
<div className="container p-2" style={{ '--screen': `${containerWidth}px` } as React.CSSProperties}>
<MyTable
apiFun={http_cash}
extraProps={{ type: 1 }}
columns={[
{
title: '时间',
dataIndex: 'time',
width: 200,
render: (time: number) => (
<span>{getTime(time * 1000)}</span>
)
},
{
title: '订单',
dataIndex: 'order',
},
{
title: '描述',
dataIndex: 'describe',
},
{
title: '剩余',
dataIndex: 'balance',
render: (val: string) => (
<span>${val}</span>
)
},
]}
/>
</div>
)
const AssetToVipRecords = () => (
<div className="container p-2" style={{ '--screen': `${containerWidth}px` } as React.CSSProperties}>
<MyTable
apiFun={http_cash}
extraProps={{ type: 2 }}
columns={[
{
title: '时间',
dataIndex: 'time',
width: 200,
render: (time: number) => (
<span>{getTime(time * 1000)}</span>
)
},
{
title: '订单',
dataIndex: 'order',
},
{
title: '描述',
dataIndex: 'describe',
},
{
title: '剩余',
dataIndex: 'balance',
render: (val: string) => (
<span>${val}</span>
)
},
]}
/>
</div>
)
const AssetToWalletRecords = () => (
<div className="container p-2" style={{ '--screen': `${containerWidth}px` } as React.CSSProperties}>
<MyTable
apiFun={http_cash}
extraProps={{ type: 3 }}
columns={[
{
title: '时间',
dataIndex: 'time',
width: 200,
render: (time: number) => (
<span>{getTime(time * 1000)}</span>
)
},
{
title: '订单',
dataIndex: 'order',
},
{
title: '描述',
dataIndex: 'describe',
},
{
title: '剩余',
dataIndex: 'balance',
render: (val: string) => (
<span>${val}</span>
)
},
]}
/>
</div>
)
return (
<div className="records" >
<div className='text-white fz-22'></div>
<ConfigProvider theme={{
components: {
Tabs: {
itemColor: '#fff',
itemSelectedColor: '#f7b93f',
inkBarColor: '#f7b93f'
}
}
}}>
<Tabs defaultActiveKey='1' tabPosition="top" items={[
{
key: '1',
label: '资金账户记录',
children: AccountRecord(),
},
{
key: '2',
label: '现金钱包->资金账户',
children: WalletToAsseetRecord(),
},
{
key: '3',
label: '现金钱包->其他会员',
children: AssetToVipRecords(),
},
{
key: '4',
label: '资产账户->现金钱包',
children: AssetToWalletRecords(),
}
]} />
</ConfigProvider>
</div >
)
}
export default observer(AssetsRecords)

111
src/pages/record/bonus/index.tsx

@ -0,0 +1,111 @@
import { title } from 'process'
import MyTable from '../../../components/MyTable'
import '../../../styles/home.scss'
import { observer } from 'mobx-react'
import store from '../../../store'
import { useMemo } from 'react'
import { http_bonus } from '../../../http/api'
import { getTime } from '../../../utils'
import { Input, Select } from 'antd'
const BonusRecords = () => {
const { screenWidth } = store.state
const containerWidth = useMemo(() => {
if (screenWidth > 1420) {
return 1420 - 310
}
if (screenWidth > 1000) {
return screenWidth - 320
}
return screenWidth - 30
}, [screenWidth])
const options = [
{ label: '请选择类型', value: 0 },
{ label: '交易利润', value: 1 },
{ label: '盈利分红', value: 2 },
{ label: '超级奖励', value: 3 },
{ label: '交易佣金', value: 4 },
{ label: '星际奖励', value: 5 },
]
const searchConfigList = [
{
key: 'type',
name: 'type',
label: '全部类型',
initialValue: 0,
slot: <Select style={{ maxWidth: 200, minWidth: 150 }} placeholder="请选择类型" options={options} />
},
{
key: 'order',
name: 'order',
label: '订单号',
initialValue: '',
slot: <Input style={{ maxWidth: 200, minWidth: 150 }} placeholder='请输入订单号' />
},
{
key: 'assets',
name: 'assets',
label: '资金账户',
initialValue: '',
slot: <Input style={{ maxWidth: 200, minWidth: 150 }} placeholder='请输入资金账户' />
}
]
return (
<div className="records" >
<div className='text-white fz-22 mb-2'></div>
<div className="container p-2" style={{ '--screen': `${containerWidth}px` } as React.CSSProperties}>
<MyTable
searchConfigList={searchConfigList}
apiFun={http_bonus}
columns={[
{
title: '时间',
dataIndex: 'time',
width: 200,
render: (time: number) => (
<span>{getTime(time * 1000)}</span>
)
},
{
title: '订单',
dataIndex: 'order'
},
{
title: '事件',
dataIndex: 'event',
width: 200
},
{
title: '来自',
dataIndex: 'source'
},
// {
// title: '受益'
// },
{
title: '金额',
dataIndex: 'amount',
render: (val: string) => (
<span>${val}</span>
)
},
{
title: '余额',
dataIndex: 'balance',
render: (val: string) => (
<span>${val}</span>
)
},
]}
/>
</div>
</div >
)
}
export default observer(BonusRecords)

75
src/pages/record/deposit/index.tsx

@ -0,0 +1,75 @@
import { title } from 'process'
import MyTable from '../../../components/MyTable'
import '../../../styles/home.scss'
import { observer } from 'mobx-react'
import store from '../../../store'
import { useMemo } from 'react'
import { http_depositHistory, http_withdrawRecords } from '../../../http/api'
import { getTime } from '../../../utils'
const DepositRecords = () => {
const { screenWidth } = store.state
const containerWidth = useMemo(() => {
if (screenWidth > 1420) {
return 1420 - 310
}
if (screenWidth > 1000) {
return screenWidth - 320
}
return screenWidth - 30
}, [screenWidth])
return (
<div className="records" >
<div className='text-white fz-22 mb-2'></div>
<div className="container p-2" style={{ '--screen': `${containerWidth}px` } as React.CSSProperties}>
<MyTable
apiFun={http_depositHistory}
columns={[
{
title: '状态',
dataIndex: 'status',
width: 150,
},
{
title: '时间',
dataIndex: 'time',
width: 200,
render: (time: number) => (
<span>{getTime(time * 1000)}</span>
)
},
{
title: '订单',
dataIndex: 'order'
},
{
title: '到账金额',
dataIndex: 'amount',
width: 160,
render: (val: string) => (
<span>${val}</span>
)
},
{
title: '付款金额',
dataIndex: 'payment_amount',
width: 160,
render: (val: string) => (
<span>{val} USDT</span>
)
},
{
title: '账号',
dataIndex: 'account'
}
]}
/>
</div>
</div >
)
}
export default observer(DepositRecords)

67
src/pages/record/escrow/index.tsx

@ -0,0 +1,67 @@
import { title } from 'process'
import MyTable from '../../../components/MyTable'
import '../../../styles/home.scss'
import { observer } from 'mobx-react'
import store from '../../../store'
import { useMemo } from 'react'
import { http_escrowRecords } from '../../../http/api'
import { getTime } from '../../../utils'
const EscrowRecords = () => {
const { screenWidth } = store.state
const containerWidth = useMemo(() => {
if (screenWidth > 1420) {
return 1420 - 310
}
if (screenWidth > 1000) {
return screenWidth - 320
}
return screenWidth - 30
}, [screenWidth])
return (
<div className="records" >
<div className='text-white fz-22 mb-2'></div>
<div className="container p-2" style={{ '--screen': `${containerWidth}px` } as React.CSSProperties}>
<MyTable
apiFun={http_escrowRecords}
columns={[
{
title: '账号',
dataIndex: 'account',
width: 200
},
{
title: '开始时间',
dataIndex: 'start_time',
width: 200,
render: (time: number) => (
<span>{getTime(time * 1000)}</span>
)
},
{
title: '结束时间',
dataIndex: 'end_time',
width: 200,
render: (time: number) => (
<span>{getTime(time * 1000)}</span>
)
},
{
title: '金额',
dataIndex: 'amount',
width: 160,
render: (val: string) => (
<span>${val}</span>
)
}
]}
/>
</div>
</div >
)
}
export default observer(EscrowRecords)

81
src/pages/record/withdraw/index.tsx

@ -0,0 +1,81 @@
import { title } from 'process'
import MyTable from '../../../components/MyTable'
import '../../../styles/home.scss'
import { observer } from 'mobx-react'
import store from '../../../store'
import { useMemo } from 'react'
import { http_withdrawRecords } from '../../../http/api'
import { getTime } from '../../../utils'
const WithdrawRecords = () => {
const { screenWidth } = store.state
const containerWidth = useMemo(() => {
if (screenWidth > 1420) {
return 1420 - 310
}
if (screenWidth > 1000) {
return screenWidth - 320
}
return screenWidth - 30
}, [screenWidth])
return (
<div className="records" >
<div className='text-white fz-22 mb-2'></div>
<div className="container p-2" style={{ '--screen': `${containerWidth}px` } as React.CSSProperties}>
<MyTable
apiFun={http_withdrawRecords}
columns={[
{
title: '状态',
dataIndex: 'status',
width: 150,
},
{
title: '时间',
dataIndex: 'time',
width: 200,
render: (time: number) => (
<span>{getTime(time * 1000)}</span>
)
},
{
title: '订单',
dataIndex: 'order'
},
{
title: '取款金额',
dataIndex: 'amount',
render: (val: string) => (
<span>${val}</span>
)
},
{
title: '到账金额',
dataIndex: 'receipt_amount',
render: (val: string) => (
<span>${val}</span>
)
},
{
title: '账号',
dataIndex: 'account'
},
{
title: '收款户名',
dataIndex: 'type'
},
{
title: '收款地址',
dataIndex: 'address'
}
]}
/>
</div>
</div >
)
}
export default observer(WithdrawRecords)

75
src/pages/security/index.tsx

@ -1,7 +1,64 @@
import { Button, Form, Input } from "antd"
import { Button, Form, FormProps, Input, notification } from "antd"
import "../../styles/home.scss"
import { HttpRequestPs } from "../../http/types"
import { http_reset_password, http_send_email } from "../../http/api"
import CountdownTimer, { CountdownTimerRef } from "../../components/CountdownTimer"
import { useRef } from "react"
import { observer } from "mobx-react"
import store from "../../store"
const Security = () => {
const { userInfo } = store.state
const [form] = Form.useForm()
const countdownRef = useRef<CountdownTimerRef>(null)
const onFinish: FormProps<HttpRequestPs.ResetPassword>['onFinish'] = async (values) => {
const res: any = await http_reset_password(values)
if (res.code === 0) {
notification.success({
message: '修改成功,请重新登录'
})
store.setToken('')
}
}
const validatePassword = (_: any, value: string) => {
if (!value) {
return Promise.reject(new Error("请输入新密码"));
}
const regex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,32}$/;
if (!regex.test(value)) {
return Promise.reject(new Error("密码必须包含字母和数字,长度为8-32位"));
}
return Promise.resolve();
};
const getVerifyCode = async () => {
try {
if (countdownRef.current?.isActive) return
countdownRef.current?.handleStart()
const res: any = await http_send_email({
email: userInfo.email,
type: 3
})
if (res.code === 0) {
notification.success({
message: '验证码发送成功'
})
} else {
notification.error({
message: '验证码发送失败'
})
countdownRef.current?.handleStop()
}
} catch (error) {
countdownRef.current?.handleStop()
}
}
return (
<div className="securify">
<div className='text-white fz-22 mb-2'></div>
@ -15,18 +72,20 @@ const Security = () => {
</div>
<div className="divider"></div>
<div className="plr-2">
<Form layout="vertical">
<Form.Item label={<span className="fz-wb-550"></span>}>
<Input style={{ height: 50 }} placeholder="密码必须是字母加数字的组合" />
<Form layout="vertical" form={form} onFinish={onFinish}>
<Form.Item name="password" rules={[{ validator: validatePassword }]} label={<span className="fz-wb-550" ></span>}>
<Input.Password style={{ height: 50 }} placeholder="密码必须是字母加数字的组合" />
</Form.Item>
<Form.Item label={<span className="fz-wb-550"></span>}>
<Form.Item name="code" rules={[{ required: true, message: '请输入验证码' }]} label={<span className="fz-wb-550"></span>}>
<div className="row-items">
<Input style={{ height: 50 }} placeholder="密码必须是字母加数字的组合" />
<Button type="primary" style={{ height: 50 }} className="ml-1"></Button>
<Button type="primary" style={{ height: 50, width: 150 }} className="ml-1" onClick={getVerifyCode}>
<CountdownTimer initialSeconds={60} ref={countdownRef} />
</Button>
</div>
</Form.Item>
<Form.Item className="row-center">
<Button type="primary" style={{ borderRadius: 50 }}></Button>
<Button type="primary" htmlType="submit" style={{ borderRadius: 50 }}></Button>
</Form.Item>
</Form>
</div>
@ -35,4 +94,4 @@ const Security = () => {
)
}
export default Security
export default observer(Security)

78
src/pages/submit/index.tsx

@ -0,0 +1,78 @@
import { Form, FormProps, Input, notification } from 'antd'
import '../../styles/login.scss'
import Button from '../../components/Button'
import { useRouter } from '../../hooks/useRouter'
import store from '../../store'
import { useEffect, useState } from 'react'
import { http_code, http_login, http_submit } from '../../http/api'
type FieldType = {
account: string;
password: string;
};
const Submit = () => {
const [form] = Form.useForm()
const onFinish: FormProps<FieldType>['onFinish'] = async (values) => {
const res: any = await http_submit({
...values
})
if (res.code === 0) {
notification.success({
message: '提交成功'
})
form.resetFields()
}
};
useEffect(() => {
}, [])
return (
<div className='login'>
<div className='container'>
<div className='row-center'>
<img src={require('../../assets/login.png')} className='img' alt="" />
</div>
<div className='row-center'>
<div style={{ maxWidth: 500, width: '100%', padding: '0 20px' }}>
<Form
onFinish={onFinish}
form={form}
>
<Form.Item
name="account"
rules={[
{
required: true,
message: '请输入账号!',
},
]}
>
<Input className='input' placeholder='账号' ></Input>
</Form.Item>
<Form.Item
name="password"
rules={[
{
required: true,
message: '请输入密码!',
},
]}
>
<Input.Password className='input' placeholder='请输入密码' />
</Form.Item>
<Form.Item>
<Button htmlType="submit"></Button>
</Form.Item>
</Form>
</div>
</div>
</div>
</div>
)
}
export default Submit

110
src/pages/team/index.tsx

@ -0,0 +1,110 @@
import { QRCode } from 'antd'
import '../../styles/home.scss'
import { useEffect, useState } from 'react'
import { http_team } from '../../http/api'
import { observer } from 'mobx-react'
import store from '../../store'
const Team = () => {
const { token } = store.state
const [rootAccount, setRootState] = useState('')
const [teamAccount, setTeamAccount] = useState([])
useEffect(() => {
const getData = async () => {
const res: any = await http_team()
if (res.code === 0) {
setRootState(res.data.email)
setTeamAccount(res.data.team)
}
}
token && getData()
}, [token])
return (
<div className='team'>
<div className='text-white fz-22 mb-2'></div>
<div className='container p-2'>
<div className='row-items'>
<img src={require('../../assets/root.png')} alt="" />
<div className='ml-1 tp'>{rootAccount}</div>
</div>
{
teamAccount.map((item: any) => (
<div className='row-items' key={item.id}>
<img src={require('../../assets/_line2.png')} alt="" />
<div className='ml-1 tp'>{item.account}</div>
</div>
))
}
</div>
{/* <div className='container p-2 mt-2'>
<div className='text-sub fz-wb-550'>CE08,658532</div>
<div className='row-items mt-2 flex-wrap'>
<div className='box-width'>
<div>$0</div>
<div className='text-sub'></div>
</div>
<div className='box-width'>
<div>$0</div>
<div className='text-sub'></div>
</div>
<div className='box-width'>
<div>$0</div>
<div className='text-sub'></div>
</div>
<div className='box-width'>
<div>$0</div>
<div className='text-sub'></div>
</div>
</div>
</div>
<div className='container p-2 mt-2'>
<div className='text-sub fz-wb-550'></div>
<div className='row-items mt-2 flex-wrap'>
<div className='box-width'>
<div>$0</div>
<div className='text-sub'></div>
</div>
<div className='box-width'>
<div>$0</div>
<div className='text-sub'></div>
</div>
<div className='box-width'>
<div>1</div>
<div className='text-sub'></div>
</div>
<div className='box-width'>
<div>$0</div>
<div className='text-sub'></div>
</div>
<div className='box-width'>
<div>$0</div>
<div className='text-sub'></div>
</div>
<div className='box-width'>
<div>0</div>
<div className='text-sub'></div>
</div>
<div className='box-width'>
<div>$0</div>
<div className='text-sub'></div>
</div>
</div>
</div> */}
</div>
)
}
export default observer(Team)

73
src/pages/transfer/index.tsx

@ -1,7 +1,40 @@
import { Button, Form, Input, Select } from 'antd'
import { Button, Form, Input, Select, notification } from 'antd'
import '../../styles/home.scss'
import { useState } from 'react'
import { observer } from 'mobx-react'
import store from '../../store'
import { http_transfer } from '../../http/api'
const Transfer = () => {
const { userInfo } = store.state
const [form] = Form.useForm()
const options = [
{ label: '现金钱包-资产账户', value: 1 },
{ label: '现金钱包-其他会员', value: 2 },
{ label: '资产账户-现金钱包', value: 3 }
]
const [type, setType] = useState(-1)
const onSelect = (_type: any) => {
setType(_type)
}
const onFinish = async (values: any) => {
const res: any = await http_transfer({
...values,
type
})
if (res.code === 0) {
form.resetFields()
notification.success({
message: '提交成功'
})
}
}
return (
<div className="deposit">
<div className='text-white fz-22 mb-2'></div>
@ -18,22 +51,48 @@ const Transfer = () => {
<div className='p-2'>
<Form
layout="vertical"
onFinish={onFinish}
form={form}
>
<Form.Item vertical={true} label={<span className='fz-wb-550'></span>}>
<Select style={{ height: 50 }} placeholder="请选择操作类型..." />
<Form.Item label={<span className='fz-wb-550'></span>}>
<Select style={{ height: 50 }} placeholder="请选择操作类型..." options={options} onSelect={onSelect} />
<div>
<span className='fz-12 text-primary'></span>
<span className='fz-12 text-success'>$0.00</span>
<span className='fz-12 text-success'>${userInfo.account_balance}</span>
</div>
</Form.Item>
<Form.Item vertical={true} label={<span className='fz-wb-550'></span>}>
{
type === 2 && (
<Form.Item name="email" label="会员账号" rules={[{ required: true, message: "请输入对方登录账户(邮箱)" }, { type: 'email', message: '邮箱格式错误' }]}>
<Input style={{ height: 50 }} placeholder='请输入对方登录账户(邮箱)' />
</Form.Item>
)
}
{
type === 1 && (
<Form.Item label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 50 }} placeholder='请输入转账金额' value={`(CFD) ${userInfo.account},余额:${userInfo.account_balance}`} disabled />
</Form.Item>
)
}
{
type === 3 && (
<Form.Item label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 50 }} placeholder='请输入转账金额' value={`(CFD) ${userInfo.account},余额:${userInfo.account_balance}`} disabled />
</Form.Item>
)
}
<Form.Item name="amount" rules={[{ required: true, message: "请输入转账金额" }]} label={<span className='fz-wb-550'></span>}>
<Input style={{ height: 50 }} placeholder='请输入转账金额' />
</Form.Item>
<Form.Item>
<div className='row-center'>
<Button type='primary' style={{ borderRadius: 30 }}></Button>
<Button type='primary' htmlType='submit' style={{ borderRadius: 30 }}></Button>
</div>
</Form.Item>
</Form>
@ -43,4 +102,4 @@ const Transfer = () => {
)
}
export default Transfer
export default observer(Transfer)

95
src/pages/withdraw/index.tsx

@ -1,9 +1,74 @@
import { Button, Form, Input, Select } from 'antd'
import { Button, Form, Input, Select, notification } from 'antd'
import '../../styles/home.scss'
import { observer } from 'mobx-react'
import store from '../../store'
import { useMemo, useRef, useState } from 'react'
import CountdownTimer, { CountdownTimerRef } from '../../components/CountdownTimer'
import { http_send_email, http_withdraw } from '../../http/api'
const Withdraw = () => {
const [form] = Form.useForm()
const { receiveAccount, userInfo } = store.state
const countdownRef = useRef<CountdownTimerRef>(null)
const options = useMemo(() => {
const map = receiveAccount.map(item => ({
label: `${item.type}:${item.address}`,
value: item.id
}))
return map
}, [receiveAccount])
const onFinish = async (values: any) => {
if (Number(values.amount) > Number(userInfo.balance)) {
notification.warning({
message: '余额不足'
})
return
}
return
const res: any = await http_withdraw({
...values,
receiving_account_id: Number(values.receiving_account_id)
})
if (res.code === 0) {
form.resetFields()
notification.success({
message: '提交成功'
})
}
}
const getVerifyCode = async () => {
try {
if (countdownRef.current?.isActive) return
countdownRef.current?.handleStart()
const res: any = await http_send_email({
email: userInfo.email,
type: 2
})
if (res.code === 0) {
notification.success({
message: '验证码发送成功'
})
} else {
notification.error({
message: '验证码发送失败'
})
countdownRef.current?.handleStop()
}
} catch (error) {
countdownRef.current?.handleStop()
}
}
return (
<div className="deposit">
<div className="widthdraw">
<div className='text-white fz-22 mb-2'></div>
<div className="flex-1 container">
<div className='p-2'>
@ -13,13 +78,15 @@ const Withdraw = () => {
<div className='divider' style={{ backgroundColor: '#8492a6' }}></div>
<div className='p-2'>
<Form
onFinish={onFinish}
layout="vertical"
form={form}
>
<div className='row-between'>
<Form.Item style={{ width: '48%' }} vertical={true} label={<span className='fz-wb-550'></span>}>
<Select style={{ height: 40 }} value="现金钱包 余额:$10291.51" />
<div className={'form-box'}>
<Form.Item className='input' label={<span className='fz-wb-550'></span>}>
<Select style={{ height: 40 }} value={`现金钱包 余额:$${userInfo.balance}`} />
</Form.Item>
<Form.Item style={{ width: '48%' }} vertical={true} label={<span className='fz-wb-550'></span>}>
<Form.Item name="amount" rules={[{ required: true, message: '请输入取款金额' }]} className='input' label={<span className='fz-wb-550'></span>}>
<div className='row-items'>
<Input style={{ height: 40, flex: 1 }} placeholder='请输入取款金额' />
<div className='fz-wb-550 ml-1'>USD</div>
@ -27,21 +94,23 @@ const Withdraw = () => {
</Form.Item>
</div>
<div className='row-between'>
<Form.Item style={{ width: '48%' }} vertical={true} label={<span className='fz-wb-550'></span>}>
<Select style={{ height: 40 }} placeholder="选择收款方式..." />
<div className='form-box'>
<Form.Item name="receiving_account_id" rules={[{ required: true, message: '请选择收款方式...' }]} className='input' label={<span className='fz-wb-550'></span>}>
<Select style={{ height: 40 }} placeholder="选择收款方式..." options={options} />
</Form.Item>
<Form.Item style={{ width: '48%' }} vertical={true} label={<span className='fz-wb-550'></span>}>
<Form.Item rules={[{ required: true, message: '请选择收款方式...' }]} name="code" className='input' label={<span className='fz-wb-550'></span>}>
<div className='row-items'>
<Input style={{ height: 40, flex: 1 }} placeholder='请输入验证码' />
<Button type="primary" className='ml-1' style={{ height: 40 }}></Button>
<Button type="primary" className='ml-1' style={{ height: 40, width: 100 }} onClick={getVerifyCode}>
<CountdownTimer ref={countdownRef} initialSeconds={60} />
</Button>
</div>
</Form.Item>
</div>
<Form.Item>
<div className='row-center'>
<Button type='primary' style={{ borderRadius: 30 }}></Button>
<Button type='primary' htmlType='submit' style={{ borderRadius: 30 }}></Button>
</div>
</Form.Item>
</Form>
@ -51,4 +120,4 @@ const Withdraw = () => {
)
}
export default Withdraw
export default observer(Withdraw)

43
src/router/index.tsx

@ -5,11 +5,12 @@ import '../styles/app.scss'
import { useRouter } from "../hooks/useRouter";
import Slider from "../components/layout/Slider";
import Header from "../components/layout/Header";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import { unLoginPath } from "./routes";
const LayoutRouter = () => {
const { push } = useRouter()
const { push, location } = useRouter()
const { token, screenWidth } = store.state
//
@ -18,12 +19,46 @@ const LayoutRouter = () => {
window.addEventListener("resize", (e) => {
const _width = window.innerWidth;
store.setScreenWidth(_width)
console.log('react', window.innerWidth);
})
window.onload = () => {
const _width = window.innerWidth;
store.setScreenWidth(_width)
}
}, [])
useEffect(() => {
if (!token && !unLoginPath.includes(location.pathname) && location.pathname !== "/submit") {
push('/login', null, true)
}
if (token && unLoginPath.includes(location.pathname) && location.pathname !== "/submit") {
push('/', null, true)
}
}, [location.pathname, token])
useEffect(() => {
token && store.getUserInfo()
token && store.getReceiveAccount()
!token && store.resetUserInfo()
!token && store.resetReceiveAccount()
}, [token])
useEffect(() => {
window.addEventListener('message', (res) => {
if (res.origin === process.env.REACT_APP_ORIGIN) {
if (res && res.data && res.data.token) {
store.setToken(res.data.token)
if (!window.sessionStorage.getItem('first_render')) {
window.location.reload()
}
window.sessionStorage.setItem('first_render', 'true')
}
}
})
}, [])
return (
token ? (
!token ? (
<RenderRouter />
) : (
<div className="layout">

3
src/router/renderRouter.tsx

@ -1,6 +1,7 @@
import { Suspense } from "react";
import { Route, Routes } from "react-router-dom";
import routes from "./routes";
import { observer } from "mobx-react";
const RenderRouter = () => {
return (
@ -21,4 +22,4 @@ const RenderRouter = () => {
)
}
export default RenderRouter;
export default observer(RenderRouter);

46
src/router/routes.tsx

@ -10,6 +10,20 @@ const Transfer = lazy(() => import("../pages/transfer"));
const Withdraw = lazy(() => import("../pages/withdraw"));
const Account = lazy(() => import("../pages/account"));
const Security = lazy(() => import("../pages/security"));
const Link = lazy(() => import("../pages/link"));
const Team = lazy(() => import("../pages/team"));
const AssetsRecords = lazy(() => import("../pages/record/assets"));
const WithdrawRecords = lazy(() => import("../pages/record/withdraw"));
const DepositRecords = lazy(() => import("../pages/record/deposit"));
const Personal = lazy(() => import("../pages/personal"));
const BonusRecords = lazy(() => import("../pages/record/bonus"));
const EscrowRecords = lazy(() => import("../pages/record/escrow"));
export const unLoginPath = [
"/login",
"/forget",
"/createProxy"
]
const routes = [
{
@ -48,6 +62,38 @@ const routes = [
path: "/security",
element: <Security />
},
{
path: "/link",
element: <Link />
},
{
path: "/team",
element: <Team />
},
{
path: "/assetsRecords",
element: <AssetsRecords />
},
{
path: "/personal",
element: <Personal />
},
{
path: '/withdrawRecords',
element: <WithdrawRecords />
},
{
path: '/depositRecords',
element: <DepositRecords />
},
{
path: '/bonusRecords',
element: <BonusRecords />
},
{
path: '/escrowRecords',
element: <EscrowRecords />
}
] as RouteObject[];
export default routes;

38
src/store/index.ts

@ -1,13 +1,13 @@
import { makeAutoObservable } from "mobx";
import { Store } from "../types";
import { http_account, http_receiveAccount } from "../http/api";
interface Store {
state: object;
}
class AppStore implements Store {
class AppStore {
state = {
token: "",
screenWidth: 0
screenWidth: 0,
userInfo: {} as Store.AccountDetails,
receiveAccount: [] as Store.ReceiveAccount[]
};
constructor() {
@ -22,17 +22,41 @@ class AppStore implements Store {
// let addr =
// window.sessionStorage.getItem(StoreLocalStorageKey.ADDRESS) || "";
// this.state.walletAddress = addr;
this.state.token = window.localStorage.getItem('Tarder_Token') || ""
this.state.screenWidth = window.innerWidth
}
setToken(_token: string) {
this.state.token = _token
this.state.token = _token;
window.localStorage.setItem('Tarder_Token', _token)
}
setScreenWidth(_width: number) {
this.state.screenWidth = _width
}
async getUserInfo() {
const res: any = await http_account()
if (res.code === 0) {
this.state.userInfo = res.data
}
}
resetUserInfo() {
this.state.userInfo = {} as Store.AccountDetails
}
async getReceiveAccount() {
const res: any = await http_receiveAccount()
if (res.code === 0) {
this.state.receiveAccount = res.data
}
}
resetReceiveAccount() {
this.state.receiveAccount = []
}
}
const store = new AppStore();

12
src/styles/app.scss

@ -85,4 +85,14 @@
&::-webkit-scrollbar {
display: none;
}
}
}
.ant-collapse-expand-icon{
color: #fff;
font-weight: bold;
}
.ant-collapse-header-text{
color: #fff;
font-weight: bold;
}

4
src/styles/components.scss

@ -10,6 +10,10 @@
justify-content: center;
align-items: center;
cursor: pointer;
border: none;
padding: 0;
text-decoration: none;
outline: inherit;
&:hover {
opacity: 0.9;

23
src/styles/global.scss

@ -9,7 +9,7 @@ $iterations: 10;
body {
font-size: 1rem;
color: #000;
user-select: none;
// user-select: none;
background-color: #f8f8fb;
font-family: Play;
width: 100%;
@ -296,4 +296,25 @@ body {
height: 1px;
background-color: #fff;
transform: scaleY(0.1);
}
.tx-over {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
:where(.css-dev-only-do-not-override-1q0h2je).ant-table-wrapper .ant-table-pagination-right {
justify-content: left;
}
:where(.css-dev-only-do-not-override-1q0h2je).ant-input-affix-wrapper>input.ant-input,
:where(.css-dev-only-do-not-override-1q0h2je).ant-input-affix-wrapper>textarea.ant-input {
&::placeholder {
color: #999;
}
}
:where(.css-dev-only-do-not-override-myver8).ant-table-wrapper .ant-table-pagination-right {
justify-content: left;
}

114
src/styles/home.scss

@ -1,4 +1,10 @@
.home {
.box {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.income {
width: 60%;
height: 400px;
@ -17,19 +23,35 @@
.CFD-box {
width: 360px;
height: 430px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 .05rem 0.04rem rgba(8, 5, 3, .03);
}
}
@media (max-width:1000px) {
.income {
width: 100%;
height: 420px;
}
.total-income {
width: 100%;
height: 250px;
margin-top: 20px;
}
.box {
flex-direction: column;
}
.CFD-box {
width: 100%;
}
}
}
.deposit {
@ -56,11 +78,97 @@
}
.securify {
.container {
width: 100%;
background-color: #fff;
box-shadow: 0 .05rem 0.04rem rgba(8, 5, 3, .03);
border-radius: 5px;
}
}
.widthdraw {
.container {
width: 100%;
background-color: #fff;
box-shadow: 0 .05rem 0.04rem rgba(8, 5, 3, .03);
border-radius: 5px;
.form-box {
display: flex;
flex-direction: row;
justify-content: space-between;
.input {
width: 48%;
}
}
@media (max-width:700px) {
.form-box {
flex-direction: column;
.input {
width: 100%;
}
}
}
}
}
.link {
.container {
width: 100%;
background-color: #fff;
box-shadow: 0 .05rem 0.04rem rgba(8, 5, 3, .03);
border-radius: 5px;
max-width: 450px;
}
}
.team {
.container {
width: 100%;
background-color: #fff;
box-shadow: 0 .05rem 0.04rem rgba(8, 5, 3, .03);
border-radius: 5px;
}
.box-width {
width: 25%;
}
@media (max-width:700px) {
.box-width {
width: 50%;
}
}
@media (max-width:350px) {
.box-width {
width: 100%;
}
}
}
.records {
.container {
width: var(--screen);
background-color: #fff;
box-shadow: 0 .05rem 0.04rem rgba(8, 5, 3, .03);
border-radius: 5px;
height: 100%;
overflow-x: scroll;
scrollbar-color: #f7b93f #f5f5f5;
}
}
.ant-tabs-nav-wrap {
overflow: hidden;
overflow-x: scroll;
width: 400px;
}

10
src/styles/login.scss

@ -13,10 +13,9 @@
color: #fff;
.img {
max-width: 100%;
margin-top: 20px;
max-width: 140px;
margin-top: 100px;
margin-bottom: 30px;
}
.verify-button {
@ -43,8 +42,11 @@
&::placeholder {
color: #999;
}
}
&:hover {
background: none;
}
}
.button {
width: 100%;
height: 52px;

27
src/types/index.ts

@ -0,0 +1,27 @@
export namespace Store {
export interface AccountDetails {
account: string;
account_balance: string;
account_capital: string;
account_credit: string;
account_net_worth: string;
account_observer_password: string;
account_prepaid: string;
account_status: string;
balance: string;
email: string;
inviti_code: string;
name: string;
total_assets: string;
total_income: string;
}
export interface ReceiveAccount {
"address": string,
"fee": string,
"id": number,
"time": number,
"type": string
}
}

47
src/utils/index.ts

@ -0,0 +1,47 @@
import { notification } from "antd"
export function copy(value: string, cb?: Function) {
// 动态创建 textarea 标签
const textarea: any = document.createElement('textarea')
// 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
textarea.readOnly = 'readonly'
textarea.style.position = 'absolute'
textarea.style.left = '-9999px'
// 将要 copy 的值赋给 textarea 标签的 value 属性
// 网上有些例子是赋值给innerText,这样也会赋值成功,但是识别不了\r\n的换行符,赋值给value属性就可以
textarea.value = value
// 将 textarea 插入到 body 中
document.body.appendChild(textarea)
// 选中值并复制
textarea.select()
textarea.setSelectionRange(0, textarea.value.length)
document.execCommand('Copy')
document.body.removeChild(textarea)
notification.success({
message: '复制成功'
})
if (cb && Object.prototype.toString.call(cb) === '[object Function]') {
cb()
}
}
/**
* @param value
* @returns
*/
export const getTime = (value: number, type?: string) => {
let date = new Date(value);
let yy: number | string = date.getFullYear();
let mm: number | string = date.getMonth() + 1;
let dd: number | string = date.getDate();
let xs: number | string = date.getHours();
let ff: number | string = date.getMinutes();
let ss: number | string = date.getSeconds();
mm = mm >= 10 ? mm : "0" + mm;
dd = dd >= 10 ? dd : "0" + dd;
xs = xs >= 10 ? xs : "0" + xs;
ff = ff >= 10 ? ff : "0" + ff;
ss = ss >= 10 ? ss : "0" + ss;
if (type === "day") return `${yy}-${mm}-${dd}`;
return `${yy}-${mm}-${dd} ${xs}:${ff}`;
};

68
yarn.lock

@ -3067,6 +3067,15 @@ axe-core@=4.7.0:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf"
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
axios@^1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621"
integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axobject-query@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
@ -4262,6 +4271,22 @@ eastasianwidth@^0.2.0:
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
echarts-for-react@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/echarts-for-react/-/echarts-for-react-3.0.2.tgz#ac5859157048a1066d4553e34b328abb24f2b7c1"
integrity sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==
dependencies:
fast-deep-equal "^3.1.3"
size-sensor "^1.0.1"
echarts@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.5.0.tgz#c13945a7f3acdd67c134d8a9ac67e917830113ac"
integrity sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==
dependencies:
tslib "2.3.0"
zrender "5.5.0"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -5029,7 +5054,7 @@ flatted@^3.2.9:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
follow-redirects@^1.0.0:
follow-redirects@^1.0.0, follow-redirects@^1.15.6:
version "1.15.6"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
@ -5077,6 +5102,15 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -6497,6 +6531,11 @@ jiti@^1.21.0:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
js-md5@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/js-md5/-/js-md5-0.8.3.tgz#921bab7efa95bfc9d62b87ee08a57f8fe4305b69"
integrity sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -8107,6 +8146,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
@ -8589,6 +8633,11 @@ react-error-overlay@^6.0.11:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
react-icons@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.2.1.tgz#28c2040917b2a2eda639b0f797bff1888e018e4a"
integrity sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -9223,6 +9272,11 @@ sisteransi@^1.0.5:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
size-sensor@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/size-sensor/-/size-sensor-1.0.2.tgz#b8f8da029683cf2b4e22f12bf8b8f0a1145e8471"
integrity sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@ -9838,6 +9892,11 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@ -10697,3 +10756,10 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zrender@5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.5.0.tgz#54d0d6c4eda81a96d9f60a9cd74dc48ea026bc1e"
integrity sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==
dependencies:
tslib "2.3.0"
Loading…
Cancel
Save