diff --git a/.env b/.env new file mode 100644 index 0000000..c01847d --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +SKIP_PREFLIGHT_CHECK=true +GENERATE_SOURCEMAP=false +REACT_APP_BASEURL='https://matontrading.com' +REACT_APP_ORIGIN='https://matontrading.com' \ No newline at end of file diff --git a/env-temp b/env-temp new file mode 100644 index 0000000..18b442a --- /dev/null +++ b/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' \ No newline at end of file diff --git a/package.json b/package.json index 1e0c84d..f606aed 100644 --- a/package.json +++ b/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" } -} +} \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico index a11777c..d6bd8a8 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html index 056501f..f998606 100644 --- a/public/index.html +++ b/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`. --> - React App + Maton Trading diff --git a/public/login192.png b/public/login192.png new file mode 100644 index 0000000..4b9782c Binary files /dev/null and b/public/login192.png differ diff --git a/public/login512.png b/public/login512.png new file mode 100644 index 0000000..4b9782c Binary files /dev/null and b/public/login512.png differ diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/src/App.tsx b/src/App.tsx index 4827d74..3e4c6e1 100644 --- a/src/App.tsx +++ b/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 ( + +
+ {/* */} +
加载中...
+
+ }> + + } /> + +
+
); diff --git a/src/assets/_line2.png b/src/assets/_line2.png new file mode 100644 index 0000000..fd3544f Binary files /dev/null and b/src/assets/_line2.png differ diff --git a/src/assets/login.jpg b/src/assets/login.jpg deleted file mode 100644 index d88d170..0000000 Binary files a/src/assets/login.jpg and /dev/null differ diff --git a/src/assets/login.png b/src/assets/login.png new file mode 100644 index 0000000..4b9782c Binary files /dev/null and b/src/assets/login.png differ diff --git a/src/assets/root.png b/src/assets/root.png new file mode 100644 index 0000000..61b861e Binary files /dev/null and b/src/assets/root.png differ diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 22f6213..0b0b0b3 100644 --- a/src/components/Button.tsx +++ b/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) => void; + htmlType?: "button" | "submit" | "reset" | undefined } -const Button = (props: ButtonProps) => { - - const { children, style, className, onClick } = props +const Button = forwardRef((props, ref) => { + const { children, style, className, onClick, htmlType = "button" } = props; return ( -
onClick && onClick()} className={`default-button ${className}`} style={style}>{children}
- ) -} + + ); +}); -export default Button \ No newline at end of file +export default Button; diff --git a/src/components/CountdownTimer.tsx b/src/components/CountdownTimer.tsx new file mode 100644 index 0000000..bf95ab0 --- /dev/null +++ b/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((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 ( +
{isActive ? {seconds}s : title}
+ ); +}); + +export default CountdownTimer; diff --git a/src/components/MyTable/index.tsx b/src/components/MyTable/index.tsx new file mode 100644 index 0000000..4a90bb0 --- /dev/null +++ b/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 = 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 ( +
+ {/* 搜索栏 */} + {searchConfigList && searchConfigList.length > 0 && ( +
+ + {header} +
+ )} + {/* 列表 */} + `共 ${all} 条`, + }} + /> + + ) + } +) + +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 diff --git a/src/components/MyTable/tableHook.tsx b/src/components/MyTable/tableHook.tsx new file mode 100644 index 0000000..51e22ea --- /dev/null +++ b/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 diff --git a/src/components/SearchForm/index.tsx b/src/components/SearchForm/index.tsx new file mode 100644 index 0000000..8eb76e5 --- /dev/null +++ b/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 = forwardRef( + (props: SearchProps, ref) => { + const { config, handleSearch, beforeSearch, onFieldsChange } = props + const [form] = Form.useForm() + const getFields = (): JSX.Element[] => { + return config.map((item: any) => { + return ( + + {item.slot} + + ) + }) + } + + 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 ( +
+ {getFields()} + + + + + ) + } +) + +SearchForm.defaultProps = { + beforeSearch: () => { }, + onFieldsChange: () => { } +} + +export default SearchForm diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index a71f23b..b464dc9 100644 --- a/src/components/layout/Header.tsx +++ b/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(null) + + const panelStyle: React.CSSProperties = { + background: 'none', + borderRadius: 0, + border: 'none', + }; const PopoverData = [ { - title: '资金管理', - content: ( + label: '资金管理', + key: '1', + style: panelStyle, + children: (
-

资金划转

-

账单记录

+

to('/transfer')}>资金划转

+

to('/assetsRecords')}>账单记录

+

to('/escrowRecords')}>托管记录

) }, { - title: '存款', - content: ( + label: '存款', + style: panelStyle, + key: '2', + children: (
-

转账存款

-

存款记录

+

push('/deposit')}>转账存款

+

push('/depositRecords')}>存款记录

) }, { - title: '取款', - content: ( + label: '取款', + style: panelStyle, + key: '3', + children: (
-

收款账户

-

办理取款

-

取款记录

+

push('/account')}>收款账户

+

push('/withdraw')}>办理取款

+

push('/withdrawRecords')}>取款记录

) }, { - title: '我的团队', - content: ( + label: '我的团队', + style: panelStyle, + key: '4', + children: (
-

推广链接

-

团队详情

-

奖金明细

+

push('/link')}>推广链接

+

to('/team')}>团队详情

+

push('/bonusRecords')}>奖金明细

) }, ] - 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 = ( +
+
您好,{userInfo.name}!
+
push('/security')}>安全设定
+
+
退出登录
+
+ ) + const renderPc = (
@@ -93,32 +110,38 @@ const Header = () => { { PopoverData.map((item, index) => (
- -
{item.title}
+ +
+
{item.label}
+ +
)) } -
-
菜单
- + {/*
菜单
*/} + + +
) const renderH5 = (
-
setOpenMenu(true)}>菜单
+
setOpenMenu(true)}>
- -
菜单
+ + + +
) @@ -136,31 +159,25 @@ const Header = () => { key="left" closable={false} width={screenWidth > 700 ? '50%' : '80%'} - style={{ background: '#009688' }} >
-
-
- +
diff --git a/src/components/layout/Slider.tsx b/src/components/layout/Slider.tsx index b2431d0..f31349f 100644 --- a/src/components/layout/Slider.tsx +++ b/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: }, + { title: 'IPhone 下载', id: 2, Icon: }, + { title: '仪表盘', id: 3, Icon: }, + { title: '办理存款', id: 4, Icon: }, + { title: '资金划转', id: 5, Icon: }, + { title: '办理取款', id: 6, Icon: }, + { title: '收款账户', id: 7, Icon: }, + { title: '安全设定', id: 8, Icon: }, ] const handleLeftMenu = (id: number) => { @@ -51,24 +60,29 @@ const Slider = () => { return (
- + push('/')} className='img' alt="" />
-
chainon@12345.com
+
{userInfo.email}
350 ? 'row-between ' : 'column-center'} flex-wrap`}> { tabs.map((item, index) => ( -
handleLeftMenu(item.id)}>{item.title}
+
handleLeftMenu(item.id)}> +
+
{item.Icon}
+
{item.title}
+
+
)) }
diff --git a/src/components/profitChart.tsx b/src/components/profitChart.tsx new file mode 100644 index 0000000..636386e --- /dev/null +++ b/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(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}
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 ( + + ); +}; + +export default ProfitChart; diff --git a/src/http/api.ts b/src/http/api.ts new file mode 100644 index 0000000..344a2d6 --- /dev/null +++ b/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 }) + diff --git a/src/http/axios_config.ts b/src/http/axios_config.ts new file mode 100644 index 0000000..0dfdade --- /dev/null +++ b/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; \ No newline at end of file diff --git a/src/http/service.ts b/src/http/service.ts new file mode 100644 index 0000000..e3b95e0 --- /dev/null +++ b/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; diff --git a/src/http/sign.ts b/src/http/sign.ts new file mode 100644 index 0000000..3b12635 --- /dev/null +++ b/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; diff --git a/src/http/sort.js b/src/http/sort.js new file mode 100644 index 0000000..1256c0f --- /dev/null +++ b/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; \ No newline at end of file diff --git a/src/http/types.ts b/src/http/types.ts new file mode 100644 index 0000000..2f2604f --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/src/pages/account/index.tsx b/src/pages/account/index.tsx index a05138a..e653bf8 100644 --- a/src/pages/account/index.tsx +++ b/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 (
办理存款
-
3条
+
{receiveAccount.length}条
已绑定
-
填写收款账户
-
-
-
有效
-
-
BSC Main
-
钱包类型
-
asdakjsdlkajsdasdjk
-
钱包地址
- -
-
时间 2024-06-14 10:28:47
+
setVisible(true)}>填写收款账户
+ { + receiveAccount.map(item => ( +
+
有效
+
+
{item.type}
+
钱包类型
+
{item.address}
+
钱包地址
+ +
+
时间 {getTime(item.time * 1000)}
+
+ )) + } + setVisible(false)} + title="添加收款账户" + footer={null} + > +
+ 收款方式}> + + + 钱包地址}> + + + + ) + } + +
+ + +
+ +
) } -export default Account \ No newline at end of file +export default observer(Account) \ No newline at end of file diff --git a/src/pages/create-proxy/index.tsx b/src/pages/create-proxy/index.tsx index d2a665c..6d3d768 100644 --- a/src/pages/create-proxy/index.tsx +++ b/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(null) + console.log(location.search); + + const onFinish: FormProps['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 (
- +
@@ -19,32 +88,36 @@ const CreateProxy = () => {
创建代理账户
-
- + + - + - + - - + + - +
- +
- + diff --git a/src/pages/deposit/index.tsx b/src/pages/deposit/index.tsx index c755db0..c2ab664 100644 --- a/src/pages/deposit/index.tsx +++ b/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 (
-
办理存款
-
-
-
策略量化存款-存款注意事项
-
尊敬的客户,请您转账时务必仔细检查钱包地址全部一致。转账完毕后,请及时上传转账凭证。
-
-
-
- - 存款到}> - - - -
- +
setVisibles(false)}>办理存款
+ + { + !visibles && ( +
+
+
策略量化存款-存款注意事项
+
尊敬的客户,请您转账时务必仔细检查钱包地址全部一致。转账完毕后,请及时上传转账凭证。
+
+
+
+ + 存款到}> + + + +
+ +
+
+ +
+
+ ) + } + + { + visibles && receiveAdds.map((item: any, index: number) => ( +
+
+
策略量化存款-存款注意事项
+
尊敬的客户,请您转账时务必仔细检查钱包地址全部一致。转账完毕后,请及时上传转账凭证。
+
+
+
+
请向下方账户转账
+
+
+
钱包类型
+ {item.type} +
+
钱包地址
+ copy(item.address)}>{item.address} +
+ +
+
+
上传付款凭证
+ onChange(file, index)} + > + + +
+
+ +
- - -
-
-
+
+
+ )) + } +
) } -export default Deposit \ No newline at end of file +export default observer(Deposit) \ No newline at end of file diff --git a/src/pages/forget/index.tsx b/src/pages/forget/index.tsx index f26c480..0e3aae5 100644 --- a/src/pages/forget/index.tsx +++ b/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['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 (
- +
-
- + + - +
- +
- +
push(-1)} className='tac tp'>已有账号,登录
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 4969819..781c5e8 100644 --- a/src/pages/home/index.tsx +++ b/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 (
-
仪表盘
-
-
-
+
仪表盘
+
+
+ +
+
+
+
总收益
+ `$${userInfo.total_income || 0}`} /> +
+
-
+
+
+
CFD 账户 {userInfo.account}
+ +
+
+
+ + + + + + + + + + + + + + + + + +
+

资本

+

${userInfo.account_capital || 0}

+
+

余额

+

${userInfo.account_balance || 0}

+
+

信用度

+

${userInfo.account_credit || 0}

+
+

净值

+

${userInfo.account_net_worth || 0}

+
+
+
账户状态:{userInfo.account_status}
- +
-
+ ) } -export default Home \ No newline at end of file +export default observer(Home) \ No newline at end of file diff --git a/src/pages/link/index.tsx b/src/pages/link/index.tsx new file mode 100644 index 0000000..3db66af --- /dev/null +++ b/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 ( +
+
推广链接
+ +
+
代理推广链接
+
+
+
+ +
+
+ 推荐码: + {userInfo.inviti_code} + copy(userInfo.inviti_code)} /> +
+
+ +
+
+ +
+
+
+
+ ) +} + +export default observer(Link) \ No newline at end of file diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index c2cf215..1a25092 100644 --- a/src/pages/login/index.tsx +++ b/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['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 (
- +
-
- + + - - + + - +
- +
- +
push('/forget')}>忘记密码?
diff --git a/src/pages/personal/index.tsx b/src/pages/personal/index.tsx new file mode 100644 index 0000000..875c46b --- /dev/null +++ b/src/pages/personal/index.tsx @@ -0,0 +1,53 @@ +import { Form, Input } from "antd" +import "../../styles/home.scss" + +const Personal = () => { + return ( +
+
个人资料
+
+
+ +
+ 姓名}> + + + 国家}> +
+ +
+
+
+ +
+ 省}> + + + 地址}> +
+ +
+
+
+ +
+ 邮箱}> + + + 电话}> +
+ +
+
+
+ +
+
+ +
+ ) +} + +export default Personal \ No newline at end of file diff --git a/src/pages/record/assets/index.tsx b/src/pages/record/assets/index.tsx new file mode 100644 index 0000000..cc7d82d --- /dev/null +++ b/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 = () => ( +
+ ( + {getTime(time * 1000)} + ) + }, + { + title: '事件', + dataIndex: 'event', + width: 150 + }, + { + title: '订单', + dataIndex: 'order', + width: 120 + }, + { + title: '账户', + dataIndex: 'account' + }, + { + title: '金额', + dataIndex: 'amount', + render: (val: string) => ( + ${val} + ) + }, + { + title: '描述', + dataIndex: 'describe' + }, + + ]} + /> +
+ ) + + const WalletToAsseetRecord = () => ( +
+ ( + {getTime(time * 1000)} + ) + }, + { + title: '订单', + dataIndex: 'order', + }, + { + title: '描述', + dataIndex: 'describe', + }, + { + title: '剩余', + dataIndex: 'balance', + render: (val: string) => ( + ${val} + ) + }, + ]} + /> +
+ ) + + const AssetToVipRecords = () => ( +
+ ( + {getTime(time * 1000)} + ) + }, + { + title: '订单', + dataIndex: 'order', + }, + { + title: '描述', + dataIndex: 'describe', + }, + { + title: '剩余', + dataIndex: 'balance', + render: (val: string) => ( + ${val} + ) + }, + ]} + /> +
+ ) + + const AssetToWalletRecords = () => ( +
+ ( + {getTime(time * 1000)} + ) + }, + { + title: '订单', + dataIndex: 'order', + }, + { + title: '描述', + dataIndex: 'describe', + }, + { + title: '剩余', + dataIndex: 'balance', + render: (val: string) => ( + ${val} + ) + }, + ]} + /> +
+ ) + + return ( +
+
账单记录
+ + 资金账户', + children: WalletToAsseetRecord(), + }, + { + key: '3', + label: '现金钱包->其他会员', + children: AssetToVipRecords(), + }, + { + key: '4', + label: '资产账户->现金钱包', + children: AssetToWalletRecords(), + } + ]} /> + + +
+ ) +} + +export default observer(AssetsRecords) \ No newline at end of file diff --git a/src/pages/record/bonus/index.tsx b/src/pages/record/bonus/index.tsx new file mode 100644 index 0000000..065ef85 --- /dev/null +++ b/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: + }, + { + key: 'assets', + name: 'assets', + label: '资金账户', + initialValue: '', + slot: + } + ] + + return ( +
+
奖金明细
+
+ ( + {getTime(time * 1000)} + ) + }, + { + title: '订单', + dataIndex: 'order' + }, + { + title: '事件', + dataIndex: 'event', + width: 200 + }, + { + title: '来自', + dataIndex: 'source' + }, + // { + // title: '受益' + // }, + { + title: '金额', + dataIndex: 'amount', + render: (val: string) => ( + ${val} + ) + }, + { + title: '余额', + dataIndex: 'balance', + render: (val: string) => ( + ${val} + ) + }, + ]} + /> +
+
+ ) +} + +export default observer(BonusRecords) \ No newline at end of file diff --git a/src/pages/record/deposit/index.tsx b/src/pages/record/deposit/index.tsx new file mode 100644 index 0000000..920cda7 --- /dev/null +++ b/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 ( +
+
存款记录
+
+ ( + {getTime(time * 1000)} + ) + }, + { + title: '订单', + dataIndex: 'order' + }, + { + title: '到账金额', + dataIndex: 'amount', + width: 160, + render: (val: string) => ( + ${val} + ) + }, + { + title: '付款金额', + dataIndex: 'payment_amount', + width: 160, + render: (val: string) => ( + {val} USDT + ) + }, + { + title: '账号', + dataIndex: 'account' + } + ]} + /> +
+
+ ) +} + +export default observer(DepositRecords) \ No newline at end of file diff --git a/src/pages/record/escrow/index.tsx b/src/pages/record/escrow/index.tsx new file mode 100644 index 0000000..106449e --- /dev/null +++ b/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 ( +
+
托管记录
+
+ ( + {getTime(time * 1000)} + ) + }, + { + title: '结束时间', + dataIndex: 'end_time', + width: 200, + render: (time: number) => ( + {getTime(time * 1000)} + ) + }, + { + title: '金额', + dataIndex: 'amount', + width: 160, + render: (val: string) => ( + ${val} + ) + } + ]} + /> +
+
+ ) +} + +export default observer(EscrowRecords) \ No newline at end of file diff --git a/src/pages/record/withdraw/index.tsx b/src/pages/record/withdraw/index.tsx new file mode 100644 index 0000000..52fa766 --- /dev/null +++ b/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 ( +
+
取款记录
+
+ ( + {getTime(time * 1000)} + ) + }, + { + title: '订单', + dataIndex: 'order' + }, + { + title: '取款金额', + dataIndex: 'amount', + render: (val: string) => ( + ${val} + ) + }, + { + title: '到账金额', + dataIndex: 'receipt_amount', + render: (val: string) => ( + ${val} + ) + }, + { + title: '账号', + dataIndex: 'account' + }, + { + title: '收款户名', + dataIndex: 'type' + }, + { + title: '收款地址', + dataIndex: 'address' + } + ]} + /> +
+
+ ) +} + +export default observer(WithdrawRecords) \ No newline at end of file diff --git a/src/pages/security/index.tsx b/src/pages/security/index.tsx index bc2088e..0a2a093 100644 --- a/src/pages/security/index.tsx +++ b/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(null) + + const onFinish: FormProps['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 (
办理存款
@@ -15,18 +72,20 @@ const Security = () => {
-
- 新密码}> - + + 新密码}> + - 验证码}> + 验证码}>
- +
- +
@@ -35,4 +94,4 @@ const Security = () => { ) } -export default Security \ No newline at end of file +export default observer(Security) \ No newline at end of file diff --git a/src/pages/submit/index.tsx b/src/pages/submit/index.tsx new file mode 100644 index 0000000..1f91ab1 --- /dev/null +++ b/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['onFinish'] = async (values) => { + const res: any = await http_submit({ + ...values + }) + if (res.code === 0) { + notification.success({ + message: '提交成功' + }) + form.resetFields() + } + }; + + + useEffect(() => { + }, []) + + return ( +
+
+
+ +
+
+
+
+ + + + + + + + + +
+
+
+
+
+ ) +} + +export default Submit \ No newline at end of file diff --git a/src/pages/team/index.tsx b/src/pages/team/index.tsx new file mode 100644 index 0000000..33a9376 --- /dev/null +++ b/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 ( +
+
团队详情
+ +
+ +
+ +
{rootAccount}
+
+ + { + teamAccount.map((item: any) => ( +
+ +
{item.account}
+
+ )) + } + +
+ + {/*
+
CE08,658532
+
+
+
$0
+
总存款
+
+
+
$0
+
总取款
+
+
+
$0
+
总交易量
+
+
+
$0
+
总利润
+
+ +
+
+ +
+
团队统计数量
+
+
+
$0
+
团队总资本金
+
+
+
$0
+
团队总余额
+
+
+
1
+
团队人数
+
+
+
$0
+
团队存款
+
+
+
$0
+
团队取款
+
+
+
0
+
团队总交易量
+
+
+
$0
+
团队总利润
+
+ +
+
*/} + +
+ ) +} + +export default observer(Team) \ No newline at end of file diff --git a/src/pages/transfer/index.tsx b/src/pages/transfer/index.tsx index 2bc961b..e8f72ca 100644 --- a/src/pages/transfer/index.tsx +++ b/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 (
资金划转
@@ -18,22 +51,48 @@ const Transfer = () => {
- 资金操作}> -
钱包金额: - $0.00 + ${userInfo.account_balance}
- 转账金额}> + { + type === 2 && ( + + + + ) + } + + { + type === 1 && ( + 资金账户}> + + + ) + } + + { + type === 3 && ( + 资金账户}> + + + ) + } + + 转账金额}>
- +
@@ -43,4 +102,4 @@ const Transfer = () => { ) } -export default Transfer \ No newline at end of file +export default observer(Transfer) \ No newline at end of file diff --git a/src/pages/withdraw/index.tsx b/src/pages/withdraw/index.tsx index f8bca7e..c32282b 100644 --- a/src/pages/withdraw/index.tsx +++ b/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(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 ( -
+
办理取款
@@ -13,13 +78,15 @@ const Withdraw = () => {
-
- 取款账号}> - - 取款金额}> + 取款金额}>
USD
@@ -27,21 +94,23 @@ const Withdraw = () => {
-
- 取款方式}> - - 验证码}> + 验证码}>
- +
- +
@@ -51,4 +120,4 @@ const Withdraw = () => { ) } -export default Withdraw \ No newline at end of file +export default observer(Withdraw) \ No newline at end of file diff --git a/src/router/index.tsx b/src/router/index.tsx index d750c19..ca19445 100644 --- a/src/router/index.tsx +++ b/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 ? ( ) : (
diff --git a/src/router/renderRouter.tsx b/src/router/renderRouter.tsx index bcc8b2a..24c7974 100644 --- a/src/router/renderRouter.tsx +++ b/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); diff --git a/src/router/routes.tsx b/src/router/routes.tsx index 8f05217..7731ad7 100644 --- a/src/router/routes.tsx +++ b/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: }, + { + path: "/link", + element: + }, + { + path: "/team", + element: + }, + { + path: "/assetsRecords", + element: + }, + { + path: "/personal", + element: + }, + { + path: '/withdrawRecords', + element: + }, + { + path: '/depositRecords', + element: + }, + { + path: '/bonusRecords', + element: + }, + { + path: '/escrowRecords', + element: + } ] as RouteObject[]; export default routes; diff --git a/src/store/index.ts b/src/store/index.ts index 8de59f9..6e65254 100644 --- a/src/store/index.ts +++ b/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(); diff --git a/src/styles/app.scss b/src/styles/app.scss index 1c1fc1d..7751b99 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -85,4 +85,14 @@ &::-webkit-scrollbar { display: none; } -} \ No newline at end of file +} + +.ant-collapse-expand-icon{ + color: #fff; + font-weight: bold; +} + +.ant-collapse-header-text{ + color: #fff; + font-weight: bold; +} diff --git a/src/styles/components.scss b/src/styles/components.scss index 79f331c..b31e164 100644 --- a/src/styles/components.scss +++ b/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; diff --git a/src/styles/global.scss b/src/styles/global.scss index 68c6892..429c90e 100644 --- a/src/styles/global.scss +++ b/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; } \ No newline at end of file diff --git a/src/styles/home.scss b/src/styles/home.scss index fbe3307..89a9ee1 100644 --- a/src/styles/home.scss +++ b/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; } \ No newline at end of file diff --git a/src/styles/login.scss b/src/styles/login.scss index 4ba30e9..4f50cb2 100644 --- a/src/styles/login.scss +++ b/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; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..8b55a10 --- /dev/null +++ b/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 + } +} + diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..9dea92c --- /dev/null +++ b/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}`; +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e6940ad..945be30 100644 --- a/yarn.lock +++ b/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"