yyy9608
11 months ago
42 changed files with 1538 additions and 278 deletions
-
1.env
-
5package.json
-
3public/index.html
-
80src/api/index.ts
-
10src/api/service.ts
-
26src/assets/iconfont/iconfont.css
-
2src/assets/iconfont/iconfont.js
-
35src/assets/iconfont/iconfont.json
-
BINsrc/assets/iconfont/iconfont.ttf
-
BINsrc/assets/iconfont/iconfont.woff
-
BINsrc/assets/iconfont/iconfont.woff2
-
BINsrc/assets/share.png
-
46src/components/CoinPicker.tsx
-
51src/components/Modal.tsx
-
23src/components/ProductItem.tsx
-
37src/hooks/useWs.ts
-
6src/index.tsx
-
42src/pages/home/index.tsx
-
13src/pages/personal/AccountAssetsCard.tsx
-
42src/pages/personal/index.tsx
-
5src/pages/product/index.tsx
-
79src/pages/recharge/index.tsx
-
129src/pages/record/index.tsx
-
209src/pages/share/index.tsx
-
252src/pages/team/index.tsx
-
64src/pages/withdraw/index.tsx
-
131src/router/layout/index.tsx
-
123src/router/layout/ui.tsx
-
10src/router/routes.tsx
-
24src/store/index.ts
-
19src/styles/components.scss
-
27src/styles/layout.scss
-
89src/styles/personal.scss
-
3src/styles/recharge.scss
-
20src/styles/share.scss
-
2src/styles/theme.scss
-
14src/types/api.d.ts
-
49src/types/store.d.ts
-
2src/utils/copy.ts
-
33src/utils/index.ts
-
1src/utils/sign/sign.ts
-
109yarn.lock
@ -1,5 +1,6 @@ |
|||
SKIP_PREFLIGHT_CHECK=true |
|||
GENERATE_SOURCEMAP=false |
|||
REACT_APP_BASE_URL='http://14.29.101.215:30307' |
|||
REACT_APP_WS_URL='ws://14.29.101.215:30307' |
|||
REACT_APP_SHARE_LINK='http://14.29.101.215:30305/#/' |
|||
REACT_APP_SIGN_KEY='9527nft9527_@fsdgfsx' |
2
src/assets/iconfont/iconfont.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
After Width: 444 | Height: 465 | Size: 11 KiB |
@ -0,0 +1,46 @@ |
|||
import { useMemo } from "react"; |
|||
import { Picker } from "react-vant"; |
|||
import { CoinList } from "~/types/store"; |
|||
|
|||
interface CoinPickerProps { |
|||
setIndex: Function; |
|||
list: CoinList[]; |
|||
index: number |
|||
} |
|||
|
|||
const CoinPicker = ({ setIndex, list, index }: CoinPickerProps) => { |
|||
|
|||
const coinList = useMemo(() => list.map(item => item.symbol), [list]); |
|||
|
|||
return ( |
|||
<Picker |
|||
columns={coinList} |
|||
popup={{ |
|||
round: true |
|||
}} |
|||
onConfirm={(_val: string, _item: Object, _index: number) => { |
|||
setIndex(_index); |
|||
}} |
|||
placeholder="" |
|||
value={coinList[index]} |
|||
> |
|||
{(_val, _, _action) => ( |
|||
<div onClick={() => _action.open()} style={{ width: '100%' }}> |
|||
{ |
|||
list[index] && ( |
|||
<div className='row-between'> |
|||
<div className='row-items'> |
|||
<img src={require(`~/assets/${list[index].symbol}.png`)} alt="" className="img-20" /> |
|||
<div className='ml-1 mt-3px fz-wb-550'>{list[index].symbol}</div> |
|||
</div> |
|||
<div className='iconfont icon-arrow-right-bold fz-20 fz-wb-550'></div> |
|||
</div> |
|||
) |
|||
} |
|||
</div> |
|||
)} |
|||
</Picker> |
|||
) |
|||
} |
|||
|
|||
export default CoinPicker; |
@ -0,0 +1,51 @@ |
|||
import { Overlay } from 'react-vant'; |
|||
import '~/styles/components.scss' |
|||
|
|||
interface ModalProps { |
|||
buttonText: string; |
|||
title: string; |
|||
children: JSX.Element; |
|||
buttonClick: Function; |
|||
visible: boolean; |
|||
setVisible: Function; |
|||
hiddenCloseIcon?: boolean; |
|||
showCancelButton?: boolean; |
|||
showCancelButtonText?: string; |
|||
showCancelButtonClick?: Function; |
|||
} |
|||
|
|||
const Modal = ( |
|||
{ title, buttonClick, visible, setVisible, children, buttonText, hiddenCloseIcon, showCancelButton, showCancelButtonText, showCancelButtonClick }: ModalProps |
|||
) => { |
|||
return ( |
|||
<Overlay |
|||
visible={visible} |
|||
className='row-center' |
|||
style={{ height: '100%' }} |
|||
> |
|||
<div className='modal-content'> |
|||
<div className='text-center row-center'> |
|||
<div style={{ flex: 1 }}></div> |
|||
<div style={{ flex: 1, whiteSpace: 'nowrap' }} className='fz-20 fz-wb-550'>{title}</div> |
|||
<div style={{ flex: 1 }} className="tae"> |
|||
{ |
|||
!hiddenCloseIcon && ( |
|||
<i className='iconfont icon-guanbi2 fz-24 fz-wb-550' onClick={() => setVisible(false)} /> |
|||
) |
|||
} |
|||
</div> |
|||
</div> |
|||
{children} |
|||
<div className='mt-3 row-center'> |
|||
<div className='modal-button row-center' onClick={() => buttonClick()}>{buttonText}</div> |
|||
{ |
|||
showCancelButton && |
|||
<div className='modal-button row-center ml-2' onClick={() => showCancelButtonClick && showCancelButtonClick()}>{showCancelButtonText || '關閉'}</div> |
|||
} |
|||
</div> |
|||
</div> |
|||
</Overlay> |
|||
) |
|||
} |
|||
|
|||
export default Modal |
@ -0,0 +1,37 @@ |
|||
import { useRef } from "react"; |
|||
import signGenerator from "~/utils/sign/sign"; |
|||
|
|||
const useWs = (path: string) => { |
|||
const baseUrl = `ws://14.29.101.215:30307/api/v1/${path}`; |
|||
// const baseUrl = `ws://192.168.124.52:8083/api/v1/${path}`;
|
|||
const ws = useRef<any>(null); |
|||
|
|||
const connect = (token: string) => { |
|||
let timestamp = Date.now(); |
|||
let signData = { |
|||
uri: `/api/v1/${path}`, |
|||
timestamp: timestamp, |
|||
args: "", |
|||
}; |
|||
const sign = signGenerator(signData); |
|||
ws.current = new WebSocket( |
|||
`${baseUrl}?Token=${token}&sign=${sign}×tamp=${timestamp}` |
|||
); |
|||
|
|||
ws.current.onMessage = (data: any) => { |
|||
console.log(data); |
|||
}; |
|||
}; |
|||
|
|||
const disconnect = () => { |
|||
ws.current && ws.current.close(); |
|||
ws.current = null; |
|||
}; |
|||
|
|||
return { |
|||
connect, |
|||
disconnect, |
|||
}; |
|||
}; |
|||
|
|||
export default useWs; |
@ -0,0 +1,252 @@ |
|||
import '~/styles/personal.scss' |
|||
import { Button, Divider, List, Overlay, Swiper, SwiperInstance, Toast } from "react-vant" |
|||
import BackBar from "~/components/BackBar" |
|||
import { useEffect, useRef, useState } from 'react' |
|||
import { getTime, splitAddress } from '~/utils' |
|||
import { observer } from 'mobx-react' |
|||
import store from '~/store' |
|||
import { my_invite, team_info } from '~/api' |
|||
import useCopyLink from '~/hooks/useCopy' |
|||
import { InviteRecordData } from '~/types/store' |
|||
import { useRouter } from '~/hooks/useRouter' |
|||
import { copy } from '~/utils/copy' |
|||
import QRCode from 'qrcode' |
|||
|
|||
const Team = () => { |
|||
|
|||
const { push } = useRouter() |
|||
const { token } = store.state |
|||
const { copyVal } = useCopyLink() |
|||
const [address, setAddress] = useState('') |
|||
const [inviteList, setInviteList] = useState([] as InviteRecordData[]) |
|||
const [finished, setFinished] = useState(true); |
|||
const [visible, setVisible] = useState(false); |
|||
const [cardState, setCardState] = useState([ |
|||
[ |
|||
{ title: '我的推薦人', value: '000000000', id: 1 }, |
|||
{ title: '級別獎勵', value: '10% 5%', id: 2 }, |
|||
], |
|||
[ |
|||
{ title: '獎勵金額', value: '0U', id: 4 }, |
|||
{ title: '直推人數', value: '0人', id: 6 }, |
|||
{ title: '閒推人數', value: '0人', id: 5 }, |
|||
], |
|||
]) |
|||
|
|||
const query = useRef({ page: 1, page_size: 20 }) |
|||
|
|||
const getInviteData = async () => { |
|||
let res: any = await my_invite(query.current); |
|||
if (res && res.code === 0 && res.data) { |
|||
if (res.data.length < 20) { |
|||
if (inviteList.length <= 0) { |
|||
setInviteList(res.data) |
|||
} else { |
|||
setInviteList([...inviteList, ...res.data]) |
|||
} |
|||
setFinished(true) |
|||
return |
|||
} |
|||
query.current.page = query.current.page + 1 |
|||
if (inviteList.length <= 0) { |
|||
setInviteList(res.data) |
|||
} else { |
|||
setInviteList([...inviteList, ...res.data]) |
|||
}; |
|||
setFinished(false) |
|||
} |
|||
} |
|||
|
|||
|
|||
useEffect(() => { |
|||
const getData = async () => { |
|||
const res: any = await team_info() |
|||
if (res && res.code === 0) { |
|||
cardState[0][0].value = res.data.inviti_address || '000000000000000' |
|||
cardState[1][0].value = res.data.award ? res.data.award + "U" : "0U" |
|||
cardState[1][1].value = res.data.direct_count ? res.data.direct_count + '人' : '0人' |
|||
cardState[1][2].value = res.data.indirect_count ? res.data.indirect_count + '人' : '0人' |
|||
setCardState([...cardState]) |
|||
setAddress(res.data.address) |
|||
} |
|||
console.log(res); |
|||
} |
|||
|
|||
token && getData() |
|||
token && getInviteData(); |
|||
!token && push('/', null, true) |
|||
}, [token]) |
|||
|
|||
return ( |
|||
<div className="plr-2 team"> |
|||
<BackBar |
|||
title='团队' |
|||
/> |
|||
<Button className="mt-2 button" onClick={() => setVisible(true)}>分享赚取佣金</Button> |
|||
<div className="row-between" style={{ alignItems: 'flex-end' }}> |
|||
<div className='fz-24 fz-wb-550 mb-6px'>邀请好友,挣奖励</div> |
|||
<div className="tar"> |
|||
<div className="fz-wb-550">最高可得</div> |
|||
<div className="fz-wb-550 fz-60">10%</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div className='card-box'> |
|||
{ |
|||
cardState.map((v, index) => ( |
|||
<div key={index} className="row-between" style={{ justifyContent: 'space-around' }}> |
|||
{ |
|||
v.map(item => ( |
|||
<div key={item.id} className={`tac ${index === 1 && 'mt-2'}`}> |
|||
<div className='fz-14'>{item.title}</div> |
|||
<div className='mt-1 fz-wb-550'> |
|||
{item.id === 1 ? splitAddress(item.value) : item.value} |
|||
{ |
|||
item.id === 1 && ( |
|||
<i className='iconfont icon-fuzhi_copy white ml-5px' onClick={() => copyVal(item.value)}></i> |
|||
) |
|||
} |
|||
</div> |
|||
</div> |
|||
)) |
|||
} |
|||
</div> |
|||
)) |
|||
} |
|||
</div> |
|||
|
|||
<div className='mt-3'> |
|||
<div className='fz-18'>我的邀请</div> |
|||
<div className='mt-5px'> |
|||
<Divider style={{ margin: 0, borderColor: '#2A2C24' }} /> |
|||
</div> |
|||
<div className='row mt-1 tac fz-14'> |
|||
<div style={{ flex: 1 }}>地址</div> |
|||
<div style={{ flex: 1 }}></div> |
|||
<div style={{ flex: 1 }}>時間</div> |
|||
</div> |
|||
<List |
|||
finished={finished} |
|||
onLoad={getInviteData} |
|||
errorText="請求失敗,點擊重新加載" |
|||
finishedText="已經到底了" |
|||
offset={10} |
|||
> |
|||
<div> |
|||
{ |
|||
inviteList.map((item, index) => ( |
|||
<div className='row mt-2 tac fz-14' key={index}> |
|||
<div style={{ flex: 1 }}>{splitAddress(item.address)}</div> |
|||
<div style={{ flex: 1 }}></div> |
|||
<div style={{ flex: 1 }}>{getTime(item.time * 1000)}</div> |
|||
</div> |
|||
)) |
|||
} |
|||
</div> |
|||
</List> |
|||
</div> |
|||
{ |
|||
visible && <ShareModal visible={visible} setVisible={setVisible} address={address} /> |
|||
} |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
const ShareModal = ( |
|||
{ visible, setVisible, address }: { visible: boolean, setVisible: Function, address: string } |
|||
) => { |
|||
|
|||
const [swiperIndex, setSwiperIndex] = useState(0) |
|||
const swiperRef = useRef<SwiperInstance>(null) |
|||
const [qrcodeUri, setQrcodeUri] = useState('') |
|||
|
|||
const handleSwiper = () => { |
|||
if (swiperIndex === 0) { |
|||
swiperRef.current?.swipeNext() |
|||
} else { |
|||
swiperRef.current?.swipePrev() |
|||
} |
|||
} |
|||
|
|||
const downloadQrcode = () => { |
|||
if (!qrcodeUri) return |
|||
const link = document.createElement('a') |
|||
link.href = qrcodeUri |
|||
link.download = `${Date.now()}.png` |
|||
link.click() |
|||
link.remove() |
|||
} |
|||
|
|||
useEffect(() => { |
|||
copy(process.env.REACT_APP_SHARE_LINK + address) |
|||
QRCode.toDataURL(process.env.REACT_APP_SHARE_LINK + address, (err, url) => { |
|||
if (url) { |
|||
setQrcodeUri(url) |
|||
} |
|||
}) |
|||
}, []) |
|||
|
|||
return ( |
|||
<Overlay visible={visible}> |
|||
<div className='mt-5 white-color overlay'> |
|||
<div className='row-justify-end pr-3'> |
|||
<i className='iconfont icon-close fz-36' onClick={() => setVisible(false)}></i> |
|||
</div> |
|||
<Swiper |
|||
ref={swiperRef} |
|||
indicator={() => <></>} |
|||
onChange={(index) => setSwiperIndex(index)} |
|||
initialSwipe={swiperIndex} |
|||
touchable={false} |
|||
> |
|||
<Swiper.Item> |
|||
<div className='swiper-height'> |
|||
<div className='row-center'> |
|||
<div className='mt-5'> |
|||
<img src={require('~/assets/share.png')} alt="" className='share-img' /> |
|||
</div> |
|||
</div> |
|||
<div className='url mt-2 row-center'> |
|||
{splitAddress(process.env.REACT_APP_SHARE_LINK + address, 14)} |
|||
<i className='iconfont icon-fuzhi_copy ml-1 fz-24' onClick={() => { |
|||
copy(process.env.REACT_APP_SHARE_LINK + address, () => { |
|||
Toast.success('复制成功') |
|||
}) |
|||
}}></i> |
|||
</div> |
|||
<div className='mt-2 tac fz-wb-550 fz-26'>成功複製分享碼</div> |
|||
<div className='mt-2 tac'>轉發給好友並在錢包裏打開完成綁定</div> |
|||
</div> |
|||
</Swiper.Item> |
|||
<Swiper.Item> |
|||
<div className='swiper-height'> |
|||
<div className='row-center mt-5'> |
|||
<img src={qrcodeUri} alt="" className='share-img' /> |
|||
</div> |
|||
<div className='url mt-2 row-center'> |
|||
截圖或下載圖片 |
|||
<i className='iconfont icon-download ml-1 fz-24' onClick={downloadQrcode}></i> |
|||
</div> |
|||
<div className='mt-2 tac fz-wb-550 fz-26'>掃描綁定關係</div> |
|||
<div className='mt-2 tac'>在好友錢包裡掃一掃完成綁定</div> |
|||
</div> |
|||
</Swiper.Item> |
|||
</Swiper> |
|||
|
|||
<div className='row-center' > |
|||
<div className='button' onClick={handleSwiper}> |
|||
<div className='text-index plr-2'> |
|||
<div> |
|||
{swiperIndex === 0 ? '切換二維碼' : '切換分享鏈接'} |
|||
</div> |
|||
<i className='iconfont icon-arrow-right-bold fz-20'></i> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</Overlay> |
|||
) |
|||
} |
|||
|
|||
|
|||
export default observer(Team) |
@ -0,0 +1,123 @@ |
|||
import { ethers } from "ethers"; |
|||
import { useRef } from "react"; |
|||
import { Toast } from "react-vant"; |
|||
import Modal from "../../components/Modal"; |
|||
interface UIProps { |
|||
visible: boolean, |
|||
setVisible: Function, |
|||
onClick?: Function, |
|||
address?: string |
|||
} |
|||
|
|||
export const UnLogin = ({ visible, setVisible }: UIProps) => ( |
|||
<Modal |
|||
title="拒絕訪問" |
|||
buttonClick={setVisible} |
|||
setVisible={setVisible} |
|||
visible={visible} |
|||
buttonText="關閉" |
|||
> |
|||
<div> |
|||
<div className="mt-2 fz-14 tac fz-wb-550" style={{ color: '#FF5300' }}>訪問失敗</div> |
|||
<div className="unlogin-box row-center fz-14">未檢測到錢包,請登錄錢包後重新點擊</div> |
|||
</div> |
|||
</Modal> |
|||
) |
|||
|
|||
export const VaildLink = ({ visible, setVisible }: UIProps) => ( |
|||
<Modal |
|||
title="綁定推薦人" |
|||
buttonClick={() => setVisible(false)} |
|||
setVisible={() => setVisible(false)} |
|||
visible={visible} |
|||
buttonText="關閉" |
|||
> |
|||
<div> |
|||
<div className="mt-2 fz-14 tac fz-wb-550" style={{ color: '#FF5300' }}>綁定失敗</div> |
|||
<div className="unlogin-box row-center fz-14">無效的分享鏈接</div> |
|||
</div> |
|||
</Modal> |
|||
) |
|||
|
|||
export const DefaultBind = ({ visible, setVisible, onClick }: UIProps) => { |
|||
|
|||
const addressRefs = useRef<HTMLInputElement>(null) |
|||
|
|||
const confirm = () => { |
|||
let value = addressRefs.current?.value |
|||
if (!value) return Toast.info('請輸入推薦鏈接!') |
|||
let newValue = value?.split('/#/') |
|||
if (!ethers.utils.isAddress(newValue[1])) return Toast.info('無效的分享鏈接') |
|||
onClick && onClick(newValue[1]) |
|||
} |
|||
|
|||
return ( |
|||
<Modal |
|||
visible={visible} |
|||
setVisible={setVisible} |
|||
title="綁定推薦人" |
|||
buttonClick={confirm} |
|||
buttonText="確認綁定" |
|||
> |
|||
<div> |
|||
<div className="tac mt-2 fz-14">推薦鏈接</div> |
|||
<input |
|||
type="text" |
|||
className="default-bind-input" |
|||
placeholder="請輸入推薦鏈接" |
|||
ref={addressRefs} |
|||
/> |
|||
</div> |
|||
</Modal> |
|||
) |
|||
} |
|||
|
|||
export const BindRmd = ({ visible, setVisible, address, onClick }: UIProps) => ( |
|||
<Modal |
|||
visible={visible} |
|||
setVisible={() => setVisible(false)} |
|||
title="綁定推薦人" |
|||
buttonClick={() => onClick && onClick(address)} |
|||
buttonText="確認綁定" |
|||
hiddenCloseIcon |
|||
> |
|||
<div> |
|||
<div className="tac mt-2 fz-14">推薦人地址</div> |
|||
<div className="default-bind-input row-center">{address}</div> |
|||
</div> |
|||
</Modal> |
|||
) |
|||
|
|||
export const BindSuccess = ({ visible, setVisible, address }: UIProps) => ( |
|||
<Modal |
|||
visible={visible} |
|||
setVisible={setVisible} |
|||
title="綁定推薦人" |
|||
buttonClick={setVisible} |
|||
buttonText="關閉" |
|||
hiddenCloseIcon |
|||
> |
|||
<div> |
|||
<div className="tac mt-2 fz-14">綁定成功</div> |
|||
<div className="default-bind-input row-center" style={{ color: '#1BA27A' }}>{address}</div> |
|||
</div> |
|||
</Modal> |
|||
) |
|||
|
|||
|
|||
export const AlreadyBind = ({ visible, setVisible, onClick }: UIProps) => ( |
|||
<Modal |
|||
title="綁定推薦人" |
|||
buttonClick={() => onClick && onClick()} |
|||
setVisible={() => setVisible(false)} |
|||
visible={visible} |
|||
buttonText='查看綁定人' |
|||
showCancelButton |
|||
showCancelButtonClick={() => setVisible(false)} |
|||
> |
|||
<div> |
|||
<div className="mt-2 fz-14 tac fz-wb-550" style={{ color: '#FF5300' }}>綁定失敗</div> |
|||
<div className="unlogin-box row-center fz-14">該錢包已有推薦人,不可重複綁定推薦人</div> |
|||
</div> |
|||
</Modal> |
|||
) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue