Browse Source

commit

master
yyy9608 11 months ago
parent
commit
5ea1b479d8
  1. 36
      src/api/index.ts
  2. BIN
      src/assets/buy-success.png
  3. 10
      src/assets/iconfont/iconfont.css
  4. 2
      src/assets/iconfont/iconfont.js
  5. 7
      src/assets/iconfont/iconfont.json
  6. BIN
      src/assets/iconfont/iconfont.ttf
  7. BIN
      src/assets/iconfont/iconfont.woff
  8. BIN
      src/assets/iconfont/iconfont.woff2
  9. 24
      src/components/Modal.tsx
  10. 63
      src/components/ProductInfo.tsx
  11. 41
      src/hooks/useLike.ts
  12. 95
      src/pages/detail/BuyNft.tsx
  13. 35
      src/pages/detail/NFTProperties.tsx
  14. 18
      src/pages/detail/OnShelvesNFT.tsx
  15. 106
      src/pages/detail/index.tsx
  16. 93
      src/pages/home/index.tsx
  17. 63
      src/pages/product/index.tsx
  18. 35
      src/pages/share/index.tsx
  19. 23
      src/router/layout/index.tsx
  20. 23
      src/router/layout/ui.tsx
  21. 42
      src/store/index.ts
  22. 9
      src/styles/components.scss
  23. 8
      src/styles/detail.scss
  24. 11
      src/styles/global.scss
  25. 47
      src/styles/home.scss
  26. 2
      src/styles/product.scss
  27. 3
      src/styles/theme.scss
  28. 1
      src/types/store.d.ts
  29. 3
      src/utils/index.ts

36
src/api/index.ts

@ -38,9 +38,9 @@ export const coin_list = () => request({ url: "/v1/tokenList" });
/** /**
* @description market list * @description market list
* @param {type} 1. 2.
* @param {type} 1. 2. 3. 4.
*/ */
export const market_list = (type: 1 | 2) =>
export const market_list = (type: number) =>
request({ url: "/v1/market", data: { type } }); request({ url: "/v1/market", data: { type } });
/** /**
@ -96,3 +96,35 @@ export const my_invite = (query: { page: number; page_size: number }) =>
*/ */
export const bind_rmd = (address: string) => export const bind_rmd = (address: string) =>
request({ url: "/v1/binding", data: { address } }); request({ url: "/v1/binding", data: { address } });
/**
* @description
* @param id NFT ID
* @param status 1. 2.
*/
export const set_like = (id: number, status: number) =>
request({ url: "/v1/like", data: { id, status } });
/**
* @description NFT Search
* @param name
*/
export const search_nft = (name: string) =>
request({ url: "/v1/search", data: { name } });
/**
* @description NFT Detail
* @param id
* @param type
*/
export const nft_detail = (query: object) =>
request({ url: "/v1/getNft", data: query });
/**
* @description Buy NFT
* @param {number} id
* @param {number} type
* @param {string} amount
*/
export const buy_nft = (query: object) =>
request({ url: "/v1/buy", data: query });

BIN
src/assets/buy-success.png

After

Width: 251  |  Height: 294  |  Size: 48 KiB

10
src/assets/iconfont/iconfont.css

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4379626 */ font-family: "iconfont"; /* Project id 4379626 */
src: url('iconfont.woff2?t=1704271682012') format('woff2'),
url('iconfont.woff?t=1704271682012') format('woff'),
url('iconfont.ttf?t=1704271682012') format('truetype');
src: url('iconfont.woff2?t=1704333891284') format('woff2'),
url('iconfont.woff?t=1704333891284') format('woff'),
url('iconfont.ttf?t=1704333891284') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-daohangshoucangyishoucang:before {
content: "\e625";
}
.icon-close:before { .icon-close:before {
content: "\e6a7"; content: "\e6a7";
} }

2
src/assets/iconfont/iconfont.js
File diff suppressed because it is too large
View File

7
src/assets/iconfont/iconfont.json

@ -5,6 +5,13 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "1079642",
"name": "导航_收藏_已收藏",
"font_class": "daohangshoucangyishoucang",
"unicode": "e625",
"unicode_decimal": 58917
},
{ {
"icon_id": "257406", "icon_id": "257406",
"name": "close", "name": "close",

BIN
src/assets/iconfont/iconfont.ttf

BIN
src/assets/iconfont/iconfont.woff

BIN
src/assets/iconfont/iconfont.woff2

24
src/components/Modal.tsx

@ -2,42 +2,50 @@ import { Overlay } from 'react-vant';
import '~/styles/components.scss' import '~/styles/components.scss'
interface ModalProps { interface ModalProps {
buttonText: string;
title: string;
buttonText?: string | JSX.Element;
title?: string;
children: JSX.Element; children: JSX.Element;
buttonClick: Function;
buttonClick?: Function;
visible: boolean; visible: boolean;
setVisible: Function; setVisible: Function;
hiddenCloseIcon?: boolean; hiddenCloseIcon?: boolean;
showCancelButton?: boolean; showCancelButton?: boolean;
showCancelButtonText?: string; showCancelButtonText?: string;
showCancelButtonClick?: Function; showCancelButtonClick?: Function;
showConfirmButton?: boolean;
confirmButtonClass?: string;
backgroundColosed?: boolean;
} }
const Modal = ( const Modal = (
{ title, buttonClick, visible, setVisible, children, buttonText, hiddenCloseIcon, showCancelButton, showCancelButtonText, showCancelButtonClick }: ModalProps
{ title, buttonClick, visible, setVisible, children, buttonText, hiddenCloseIcon, showCancelButton, showCancelButtonText, showCancelButtonClick, showConfirmButton, backgroundColosed }: ModalProps
) => { ) => {
return ( return (
<Overlay <Overlay
visible={visible} visible={visible}
className='row-center' className='row-center'
style={{ height: '100%' }} style={{ height: '100%' }}
onClick={() => backgroundColosed && setVisible(false)}
> >
<div className='modal-content'> <div className='modal-content'>
<div className='text-center row-center'>
<div className='tac row-center'>
<div style={{ flex: 1 }}></div> <div style={{ flex: 1 }}></div>
<div style={{ flex: 1, whiteSpace: 'nowrap' }} className='fz-20 fz-wb-550'>{title}</div> <div style={{ flex: 1, whiteSpace: 'nowrap' }} className='fz-20 fz-wb-550'>{title}</div>
<div style={{ flex: 1 }} className="tae">
<div style={{ flex: 1 }} className="tar">
{ {
!hiddenCloseIcon && ( !hiddenCloseIcon && (
<i className='iconfont icon-guanbi2 fz-24 fz-wb-550' onClick={() => setVisible(false)} />
<i className='iconfont icon-close fz-24 fz-wb-550' onClick={() => setVisible(false)} />
) )
} }
</div> </div>
</div> </div>
{children} {children}
<div className='mt-3 row-center'> <div className='mt-3 row-center'>
<div className='modal-button row-center' onClick={() => buttonClick()}>{buttonText}</div>
{
!showConfirmButton && (
<div className={`modal-button row-center`} onClick={() => buttonClick && buttonClick()}>{buttonText}</div>
)
}
{ {
showCancelButton && showCancelButton &&
<div className='modal-button row-center ml-2' onClick={() => showCancelButtonClick && showCancelButtonClick()}>{showCancelButtonText || '關閉'}</div> <div className='modal-button row-center ml-2' onClick={() => showCancelButtonClick && showCancelButtonClick()}>{showCancelButtonText || '關閉'}</div>

63
src/components/ProductInfo.tsx

@ -1,39 +1,72 @@
import { observer } from 'mobx-react'
import { useState } from 'react'
import useLike from '~/hooks/useLike'
import store from '~/store'
import '~/styles/components.scss' import '~/styles/components.scss'
import { MarketNFTData } from '~/types/store'
import { toThousands } from '~/utils'
import { copy } from '~/utils/copy'
import Modal from './Modal'
interface ProductInfoProps {
data: MarketNFTData
}
const ProductInfo = (props: ProductInfoProps) => {
const { data } = props
const { likeNft } = store.state
const { setLike, isLike } = useLike()
const [visible, setVisible] = useState(false)
const share = () => {
const pathname = `${process.env.REACT_APP_SHARE_LINK}detail?id=${data.id}&type=${data.type}`
copy(pathname)
setVisible(true)
}
const ProductInfo = () => {
return ( return (
<div className='product-info'> <div className='product-info'>
<div className='row-center'> <div className='row-center'>
<div className='cover-box row-center'> <div className='cover-box row-center'>
<img src={require('~/assets/nft/dragon.jpg')} className="img" alt="" />
</div>
<img src={data.url} className="img" alt="" />
</div> </div>
<div className='mt-1 row-center'>
<i className='iconfont icon-shoucang fz-20' />
<i className='iconfont icon-shangchuandaochu fz-24 ml-4' />
<i className='iconfont icon-caidan fz-24 ml-4' />
</div> </div>
<div className='mt-3 fz-30 fz-wb-550'>-</div>
<div className='price-tag mt-1'>USDT 21,100.15</div>
{data.type !== 0 && <div className='mt-1 row-center'>
<i className={`iconfont icon-daohangshoucangyishoucang fz-20 ${isLike(data.id, likeNft) ? 'like-text' : 'sub-text'}`} onClick={() => setLike(data.id)} />
<i className='iconfont icon-shangchuandaochu fz-24 ml-4' onClick={share} />
</div>}
<div className='mt-3 fz-30 fz-wb-550'>{data.name}</div>
<div className={`price-tag mt-1 ${data.symbol}-bg`}>{data.symbol} {toThousands(data.price)}</div>
<div className='mt-3 row-between'> <div className='mt-3 row-between'>
<div> <div>
<div></div> <div></div>
<div className='user-tag mt-2 plr-1 row-items'> <div className='user-tag mt-2 plr-1 row-items'>
<img src={require('~/assets/user.png')} alt="" />
<div className='fz-12 tac flex-1'>Filefast</div>
<img src={data.cast_url} alt="" />
<div className='fz-12 tac flex-1 name-overflow'>{data.cast_name}</div>
</div> </div>
</div> </div>
<div> <div>
<div></div> <div></div>
<div className='user-tag mt-2 plr-1 row-items'> <div className='user-tag mt-2 plr-1 row-items'>
<img src={require('~/assets/user.png')} alt="" />
<div className='fz-12 tac flex-1'>Filefast</div>
<img src={data.sell_url} alt="" />
<div className='fz-12 tac flex-1 name-overflow'>{data.sell_name}</div>
</div> </div>
</div> </div>
</div> </div>
<Modal
visible={visible}
title="分享給朋友"
setVisible={setVisible}
showConfirmButton
backgroundColosed
>
<div className='tac mt-2 like-text'>
</div>
</Modal>
</div> </div>
) )
} }
export default ProductInfo
export default observer(ProductInfo)

41
src/hooks/useLike.ts

@ -0,0 +1,41 @@
import { useRef } from "react";
import { set_like } from "~/api";
import store from "~/store";
import { MarketNFTData } from "~/types/store";
const useLike = () => {
const throttle = useRef(false)
/**
* @description NFT
* @param id nftid
* 1. 2.
*/
const setLike = async (id: number) => {
if (!store.state.token) return store.setVisibleUnLogin(true);
if(throttle.current)return
throttle.current = true
let item = store.state.likeNft.find((v) => v.id === id);
let status = item ? 2 : 1;
let res: any = await set_like(id, status);
throttle.current = false
if (res && res.code === 0) {
store.getMyNft("likeNft", 2);
}
};
/**
* NFt
*/
const isLike = (id: number, likeNft: MarketNFTData[]) => {
const item = likeNft.find((v) => v.id === id);
if (item) return true;
return false;
};
return {
setLike,
isLike,
};
};
export default useLike;

95
src/pages/detail/BuyNft.tsx

@ -0,0 +1,95 @@
import '~/styles/detail.scss'
import { Button, Toast } from 'react-vant'
import { MarketNFTData } from '~/types/store'
import Modal from '~/components/Modal'
import { useState } from 'react'
import { observer } from 'mobx-react'
import store from '~/store'
import { toThousands } from '~/utils'
import { buy_nft } from '~/api'
interface BuyNftProps {
data: MarketNFTData
}
const BuyNft = (props: BuyNftProps) => {
const { data } = props
const { userInfo, token } = store.state
const [buyVisible, setBuyVisible] = useState(false)
const [successVisible, setSuccessVisible] = useState(false)
const openBuyModal = () => {
if (!token) return store.setVisibleUnLogin(true)
let symbol = data.symbol
if (symbol === 'USDT' && Number(userInfo.balance_Usdt) < Number(data.price)) {
Toast.fail('餘額不足')
return
}
if (symbol === 'FIL' && Number(userInfo.balance_fil) < Number(data.price)) {
Toast.fail('餘額不足')
return
}
setBuyVisible(true)
}
const buyNft = async () => {
setBuyVisible(false)
const params = {
id: data.id,
type: data.type
}
const res: any = await buy_nft(params)
if (res && res.code === 0) {
setSuccessVisible(true)
store.getMyNft("myNft", 1)
store.getMarketNft("issueNft")
store.getMarketNft("sellNft")
}
}
return (
<div className='mt-4 buy-nft'>
<Button
className='button'
style={{
background: 'linear-gradient(103deg, #1DD0DF -1%, #1DD0DF -1%, #1BEDFF -1%, #14BDEB 108%)',
borderRadius: 10,
}}
onClick={openBuyModal}
>
<span className="fz-20 fz-wb-550"></span>
</Button>
{/* Buy Modal */}
<Modal
visible={buyVisible}
setVisible={setBuyVisible}
title="購買"
buttonText={<div className='black fz-wb-550'></div>}
buttonClick={buyNft}
>
<div className='fz-wb-550' style={{ fontStyle: 'italic' }}>
<div className='tac mt-3'> <span className='primary'>{toThousands(data.price)}</span> {data.symbol}</div>
<div className='tac mt-5px'> "{data.name}" NFT</div>
</div>
</Modal >
{/* success */}
<Modal
visible={successVisible}
setVisible={setSuccessVisible}
showConfirmButton
title=''
>
<div>
<div className='row-center'>
<img src={require('~/assets/buy-success.png')} alt="" className='success-img' />
</div>
<div className='fz-18 fz-wb-550 tac'></div>
<div className='fz-24 fz-wb-550 tac mt-1'>{data.name}</div>
</div>
</Modal>
</div >
)
}
export default observer(BuyNft)

35
src/pages/detail/NFTProperties.tsx

@ -0,0 +1,35 @@
import { MarketNFTData } from "~/types/store"
import { getTime } from "~/utils"
interface NFTPropertiesProps {
data: MarketNFTData
}
const NFTProperties = (props: NFTPropertiesProps) => {
const { data } = props
return (
<div>
<div className="fz-wb-550">NFT屬性:</div>
<div className="row fz-14 mt-1">
<div className="flex-1">{getTime(data.time * 1000, 'day')}</div>
<div className="flex-1">NFT名稱{data.name}</div>
</div>
<div className="row fz-14 mt-1">
<div className="flex-1">NFT編號{data.number}</div>
<div className="flex-1">NFT發行數{data.total_stock}</div>
</div>
<div className="row fz-14 mt-1">
<div className="flex-1">NFT功能{data.function}</div>
<div className="flex-1">{data.platform}</div>
</div>
<div className="row fz-14 mt-1">
<div className="flex-1">{data.award}</div>
<div className="flex-1">{data.day}</div>
</div>
</div>
)
}
export default NFTProperties

18
src/pages/detail/OnShelvesNFT.tsx

@ -0,0 +1,18 @@
import { MarketNFTData } from "~/types/store"
interface OnShelvesNFTProps {
data: MarketNFTData
}
const OnShelvesNFT = (props: OnShelvesNFTProps) => {
const { data } = props
return (
<div className="mt-2">
</div>
)
}
export default OnShelvesNFT

106
src/pages/detail/index.tsx

@ -1,22 +1,104 @@
import { Button } from "react-vant"
import { useEffect, useMemo, useState } from "react"
import { Button, Empty, Loading } from "react-vant"
import { nft_detail } from "~/api"
import BackBar from "~/components/BackBar"
import ProductInfo from "~/components/ProductInfo" import ProductInfo from "~/components/ProductInfo"
import { useRouter } from "~/hooks/useRouter"
import '~/styles/detail.scss' import '~/styles/detail.scss'
import { MarketNFTData } from "~/types/store"
import BuyNft from "./BuyNft"
import NFTProperties from "./NFTProperties"
import OnShelvesNFT from "./OnShelvesNFT"
const Detail = () => { const Detail = () => {
const { location } = useRouter()
const query = useMemo(() => {
let obj = {} as { [key: string]: number }
if (location.state) {
obj.id = Number(location.state.id)
obj.type = Number(location.state.type)
}
if (location.search) {
let val = location.search.substring(1).split('&')
val.forEach(item => {
let value = item.split('=')
obj[value[0]] = Number(value[1])
})
}
return obj
}, [location])
const [product, setProduct] = useState({} as MarketNFTData)
const [loading, setLoading] = useState(false)
console.log(product);
const titles = useMemo(() => ({
"0": "上架",
"1": "拍賣",
"2": "售賣",
"3": "發行"
} as any), [])
useEffect(() => {
const getData = async () => {
setLoading(true)
const res: any = await nft_detail(query)
setLoading(false)
res && res.code === 0 && setProduct(res.data)
}
Object.keys(query).length === 2 && getData()
}, [])
if (Object.keys(query).length < 2) return (
<div className="plr-3">
<BackBar
title={titles[product.type] || "詳情"}
/>
<div className="row-center" style={{ height: '70vh' }}>
<Empty image="error" description="沒有該NFT" imageSize={200} />
</div>
</div>
)
if (loading) return (
<div className="row-center" style={{ height: '70vh' }}>
<div className="tac">
<Loading type="ball" />
<div className="fz-12 sub-text ml-1">...</div>
</div>
</div>
)
return ( return (
<div className="plr-3 detail"> <div className="plr-3 detail">
<ProductInfo />
<div className='mt-4'>
<Button
className='button'
style={{
background: 'linear-gradient(103deg, #1DD0DF -1%, #1DD0DF -1%, #1BEDFF -1%, #14BDEB 108%)',
borderRadius: 10,
}}
>
<span className="fz-20 fz-wb-550"></span>
</Button>
<BackBar
title={titles[`${product.type}`] || "詳情"}
/>
<div className="mt-2">
<ProductInfo data={product} />
</div>
{/* type 0.表示上架NFT 2.購買NFT 3.搶購 */}
{
product.type === 0 && (
<div className="mt-3">
<NFTProperties data={product} />
</div> </div>
)
}
{
product.type === 0 && (
<OnShelvesNFT data={product} />
)
}
{
product.type === 2 && (
<BuyNft data={product} />
)
}
</div> </div>
) )
} }

93
src/pages/home/index.tsx

@ -1,44 +1,81 @@
import '~/styles/home.scss' import '~/styles/home.scss'
import ProductItem from '~/components/ProductItem' import ProductItem from '~/components/ProductItem'
import { useRouter } from '~/hooks/useRouter' import { useRouter } from '~/hooks/useRouter'
import { Button } from 'react-vant'
import { useEffect, useState } from 'react'
import { market_list } from '~/api'
import { MarketNFTData } from '~/types/store'
import { Button, Swiper, SwiperInstance } from 'react-vant'
import { useEffect, useRef } from 'react'
import { splitAddress } from '~/utils' import { splitAddress } from '~/utils'
import { observer } from 'mobx-react' import { observer } from 'mobx-react'
import store from '~/store'
import useLike from '~/hooks/useLike'
const Home = () => { const Home = () => {
const { push } = useRouter() const { push } = useRouter()
const [marketData, setMarketData] = useState([] as MarketNFTData[])
const [auctionData, setAuctionData] = useState([] as MarketNFTData[])
const { sellNft, auctionNft, likeNft } = store.state
const like = useLike()
const swiperRef = useRef<SwiperInstance>(null)
useEffect(() => { useEffect(() => {
const getMarketData = async () => {
const res: any = await market_list(2)
res && res.code === 0 && res.data && setMarketData(res.data)
}
const getAuctionData = async () => {
const res: any = await market_list(1)
res && res.code === 0 && res.data && setAuctionData(res.data)
}
getMarketData()
getAuctionData()
store.getMarketNft("sellNft")
store.getMarketNft("auctionNft")
store.getMarketNft("issueNft")
}, []) }, [])
return ( return (
<div className="home"> <div className="home">
<div className='plr-3 fz-20 mt-2'></div>
<div className='fz-20 mt-2 plr-2'></div>
<div className='row-between mt-2 plr-5px'>
<div>
<i className='iconfont icon-arrow-left-bold fz-wb-550 fz-24' onClick={() => {
swiperRef.current && swiperRef.current.swipePrev()
}}></i>
</div>
<div style={{ flex: 1 }}>
<Swiper ref={swiperRef} indicator={false} autoplay={5000}>
<Swiper.Item>
<div className='plr-5px'>
<div className='rush-swiper'>
<img src={require('~/assets/cover.png')} alt="" />
<div className='content-box p-2'>
<div className='sub-text fz-14'>NFT</div>
<div className='mt-5px'>-</div>
<div className='mt-5px fz-12'>
<div className='row-between'>
<div className='tag'>USDT 10000</div>
<div className='row-center right-box'>
<img src={require('~/assets/user.png')} className='avatar-img' alt="" />
</div>
</div>
<div className='row-between'>
<div className='row-items'>
<div></div>
<div className='ml-1 fz-wb-550 fz-14 like-text'>54:42:15</div>
</div>
<div className='right-box'>Filefaasdasdasdfdsfsdf</div>
</div>
</div>
</div>
</div>
</div>
</Swiper.Item>
</Swiper>
</div>
<div>
<i className='iconfont icon-arrow-right-bold fz-wb-550 fz-24' onClick={() => {
swiperRef.current && swiperRef.current.swipeNext()
}}></i>
</div>
</div>
<div className='plr-2 fz-20 mt-2'></div>
<div className='mt-2 row-items swiper'> <div className='mt-2 row-items swiper'>
{ {
auctionData.map((item, index) => (
auctionNft.map((item, index) => (
<div <div
key={index} key={index}
className={`swiper-item ${index === 0 ? 'ml-3' : 'ml-1'} ${index === 4 && 'mr-3'}`}
className={`swiper-item ${index === 0 ? 'ml-2' : 'ml-1'} ${index === 4 && 'mr-2'}`}
> >
<img src={item.url} alt="" className='swiper-img' onClick={() => push('/detail', { id: item.id })} />
<img src={item.url} alt="" className='swiper-img' onClick={() => push('/detail', { id: item.id, type: item.type })} />
<div className='cover-box'> <div className='cover-box'>
<div className='cover-box-item p-1 fz-12'> <div className='cover-box-item p-1 fz-12'>
<div> <div>
@ -64,7 +101,9 @@ const Home = () => {
<Button className='button' round> <Button className='button' round>
<div className='fz-wb-550'></div> <div className='fz-wb-550'></div>
</Button> </Button>
<i className='iconfont icon-shoucang fz-14' />
<i className={`
iconfont icon-daohangshoucangyishoucang fz-18 ${like.isLike(item.id, likeNft) ? 'like-text' : 'sub-text'}
`} onClick={() => like.setLike(item.id)} />
</div> </div>
</div> </div>
</div> </div>
@ -72,12 +111,12 @@ const Home = () => {
)) ))
} }
</div> </div>
<div className='plr-3 fz-20 mt-2'></div>
<div className='plr-3'>
<div className='plr-2 fz-20 mt-2'></div>
<div className='plr-2'>
<div className='row-between flex-wrap'> <div className='row-between flex-wrap'>
{ {
marketData.map((item, index) => (
<div key={index} onClick={() => push('/detail', { id: item.id })}>
sellNft.map((item, index) => (
<div key={index} onClick={() => push('/detail', { id: item.id, type: item.type })}>
<ProductItem data={item} /> <ProductItem data={item} />
</div> </div>
)) ))
@ -89,4 +128,4 @@ const Home = () => {
) )
} }
export default Home
export default observer(Home)

63
src/pages/product/index.tsx

@ -1,25 +1,72 @@
import { Divider } from 'react-vant'
import { debounce } from 'lodash'
import { observer } from 'mobx-react'
import { useEffect, useState } from 'react'
import { Divider, Loading } from 'react-vant'
import { search_nft } from '~/api'
import { useRouter } from '~/hooks/useRouter'
import store from '~/store'
import '~/styles/product.scss' import '~/styles/product.scss'
import { MarketNFTData } from '~/types/store' import { MarketNFTData } from '~/types/store'
import ProductItem from '../../components/ProductItem' import ProductItem from '../../components/ProductItem'
const Product = () => { const Product = () => {
const { rmdNft } = store.state
const { push } = useRouter()
const [nftList, setNftList] = useState([] as MarketNFTData[])
const [loading, setLoading] = useState(false)
const onChange = (e: any) => {
searchNft(e.target.value)
}
const searchNft = debounce(async (value: string) => {
if (!value) return setNftList([])
setLoading(true)
const res: any = await search_nft(value)
setLoading(false)
if (res && res.code === 0 && res.data) {
setNftList(res.data)
}
}, 700)
useEffect(() => {
store.getMarketNft("rmdNft")
}, [])
return ( return (
<div className="product plr-3">
<div className="product plr-2">
<div className="input-box mt-2"> <div className="input-box mt-2">
<i className='iconfont icon-search'></i> <i className='iconfont icon-search'></i>
<input type="text" placeholder='Search Here' />
<input type="text" placeholder='Search Here' onChange={onChange} />
{
loading && (
<div className='ml-1'>
<Loading size={18} color="#0FC6D4" />
</div>
)
}
</div>
<div className='mt-2 fz-16 fz-wb-550'>{nftList.length}</div>
<div className='box'>
<div className='row-between flex-wrap'>
{
nftList.map((item, index) => (
<div key={index} onClick={() => push("/detail", { id: item.id, type: item.type })}>
<ProductItem data={item} />
</div>
))
}
</div>
</div> </div>
<div className='mt-2 fz-16 fz-wb-550'>0</div>
<div className='box'></div>
<Divider style={{ borderColor: '#dedede', borderWidth: 1 }} /> <Divider style={{ borderColor: '#dedede', borderWidth: 1 }} />
<div className='mt-1'></div> <div className='mt-1'></div>
<div className='row-between flex-wrap'> <div className='row-between flex-wrap'>
{ {
Array.from({ length: 6 }).map((_, index) => (
<ProductItem key={index} data={{} as MarketNFTData} />
rmdNft.map((item, index) => (
<div key={index} onClick={() => push("/detail", { id: item.id, type: item.type })}>
<ProductItem data={item} />
</div>
)) ))
} }
</div> </div>
@ -28,4 +75,4 @@ const Product = () => {
) )
} }
export default Product
export default observer(Product)

35
src/pages/share/index.tsx

@ -1,16 +1,17 @@
import '~/styles/share.scss' import '~/styles/share.scss'
import { Popup, Tabs, Toast } from 'react-vant' import { Popup, Tabs, Toast } from 'react-vant'
import ProductItem from '~/components/ProductItem' import ProductItem from '~/components/ProductItem'
import { MarketNFTData } from '~/types/store'
import store from '~/store' import store from '~/store'
import { observer } from 'mobx-react' import { observer } from 'mobx-react'
import { toFixed2 } from '~/utils' import { toFixed2 } from '~/utils'
import { useEffect, useMemo, useRef, useState } from 'react'
import { open_page, personal_nft, reset_name, upload_image } from '~/api'
import { useMemo, useRef, useState } from 'react'
import { open_page, reset_name, upload_image } from '~/api'
import { useRouter } from '~/hooks/useRouter'
const Share = () => { const Share = () => {
const { userInfo, token } = store.state
const { userInfo, token, likeNft, myNft } = store.state
const { push } = useRouter()
const [visible, setVisible] = useState(false) const [visible, setVisible] = useState(false)
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
const nameRef = useRef<HTMLInputElement>(null) const nameRef = useRef<HTMLInputElement>(null)
@ -18,10 +19,6 @@ const Share = () => {
const prevent = useRef(false) //阻止重複點擊 const prevent = useRef(false) //阻止重複點擊
const fileRef = useRef(null as any) const fileRef = useRef(null as any)
const [tempUrl, setTempUrl] = useState(null as any) const [tempUrl, setTempUrl] = useState(null as any)
const [nftList, setNftList] = useState([
[], //我的
[],//我喜歡的
] as MarketNFTData[][])
const tabs = useMemo(() => ['我的NFT', '我喜歡的NFT'], []) const tabs = useMemo(() => ['我的NFT', '我喜歡的NFT'], [])
const avatarmenuClick = (event: any) => { const avatarmenuClick = (event: any) => {
@ -90,18 +87,6 @@ const Share = () => {
inputRef.current!.value = '' inputRef.current!.value = ''
} }
useEffect(() => {
const getData = async () => {
const res: any = await personal_nft(1)
if (res && res.code === 0) {
nftList[0] = res.data
setNftList([...nftList])
}
}
token && getData()
}, [token])
return ( return (
<div className="share"> <div className="share">
<div className='box'> <div className='box'>
@ -180,12 +165,18 @@ const Share = () => {
key={index} key={index}
title={<div> title={<div>
<span>{item}</span> <span>{item}</span>
<span className='ml-5px' style={{ color: '#11C0CB' }}>{nftList[index].length}</span>
<span className='ml-5px' style={{ color: '#11C0CB' }}>{
index === 0 ? myNft.length : likeNft.length
}</span>
</div>} </div>}
titleClass='fz-wb-550' titleClass='fz-wb-550'
> >
<div className='row-between flex-wrap plr-3'> <div className='row-between flex-wrap plr-3'>
{nftList[index].map((item, index) => <ProductItem key={index} data={item} />)}
{(index === 0 ? myNft : likeNft).map((item, index) => (
<div key={index} onClick={() => push('/detail', { id: item.id, type: item.type })}>
<ProductItem data={item} />
</div>
))}
</div> </div>
</Tabs.TabPane> </Tabs.TabPane>
)) ))

23
src/router/layout/index.tsx

@ -41,10 +41,20 @@ const LayoutRouter = () => {
} }
useEffect(() => { useEffect(() => {
token && store.getUserInfo()
token && store.getCoinList()
!token && store.resetCoinList()
!token && store.resetUserInfo()
if (token) {
store.getUserInfo()
store.getCoinList()
store.getMyNft('myNft', 1)
store.getMyNft('likeNft', 2)
}
if (!token) {
store.resetCoinList()
store.resetUserInfo()
store.resetNft("likeNft")
store.resetNft("myNft")
}
// token && messageWs.connect(token) // token && messageWs.connect(token)
// !token && messageWs.disconnect() // !token && messageWs.disconnect()
}, [token]) }, [token])
@ -55,7 +65,6 @@ const LayoutRouter = () => {
let isRouter = routes.find(v => v.path === location.pathname) let isRouter = routes.find(v => v.path === location.pathname)
const address = location.pathname.substring(1, location.pathname.length) const address = location.pathname.substring(1, location.pathname.length)
if (user.is_bound) return
if (isRouter) return if (isRouter) return
if (Object.keys(user).length <= 0) { if (Object.keys(user).length <= 0) {
@ -79,7 +88,7 @@ const LayoutRouter = () => {
return return
} }
// 已有推荐人 // 已有推荐人
if (user.is_bound) {
if (user.is_bound && ethers.utils.isAddress(address)) {
setVisibleAlreadyBind(true) setVisibleAlreadyBind(true)
} }
} }
@ -120,7 +129,7 @@ const LayoutRouter = () => {
onClick={() => { onClick={() => {
push('/', null, true) push('/', null, true)
setVisibleAlreadyBind(false) setVisibleAlreadyBind(false)
push('/share')
push('/team')
}} }}
visible={visibleAlreadyBind} visible={visibleAlreadyBind}
setVisible={setVisibleAlreadyBind} setVisible={setVisibleAlreadyBind}

23
src/router/layout/ui.tsx

@ -1,6 +1,7 @@
import { ethers } from "ethers"; import { ethers } from "ethers";
import { useRef } from "react"; import { useRef } from "react";
import { Toast } from "react-vant"; import { Toast } from "react-vant";
import { useRouter } from "~/hooks/useRouter";
import Modal from "../../components/Modal"; import Modal from "../../components/Modal";
interface UIProps { interface UIProps {
visible: boolean, visible: boolean,
@ -88,22 +89,32 @@ export const BindRmd = ({ visible, setVisible, address, onClick }: UIProps) => (
</Modal> </Modal>
) )
export const BindSuccess = ({ visible, setVisible, address }: UIProps) => (
export const BindSuccess = ({ visible, setVisible, address }: UIProps) => {
const { push } = useRouter()
return (
<Modal <Modal
visible={visible} visible={visible}
setVisible={setVisible} setVisible={setVisible}
title="綁定推薦人" title="綁定推薦人"
buttonClick={setVisible}
buttonText="關閉"
buttonClick={() => {
push('/', null, true)
setVisible()
push('/team')
}}
buttonText="查看綁定"
hiddenCloseIcon hiddenCloseIcon
showCancelButton
showCancelButtonText="關閉"
showCancelButtonClick={setVisible}
> >
<div> <div>
<div className="tac mt-2 fz-14"></div>
<div className="default-bind-input row-center" style={{ color: '#1BA27A' }}>{address}</div>
<div className="tac mt-2 fz-14 green"></div>
<div className="default-bind-input row-center green" style={{ color: '#1BA27A' }}>{address}</div>
</div> </div>
</Modal> </Modal>
)
)
}
export const AlreadyBind = ({ visible, setVisible, onClick }: UIProps) => ( export const AlreadyBind = ({ visible, setVisible, onClick }: UIProps) => (
<Modal <Modal

42
src/store/index.ts

@ -1,7 +1,7 @@
import { makeAutoObservable } from "mobx"; import { makeAutoObservable } from "mobx";
import { accountInfo, coin_list } from "~/api";
import { accountInfo, coin_list, market_list, personal_nft } from "~/api";
import { StoreLocalStorageKey } from "~/types"; import { StoreLocalStorageKey } from "~/types";
import { CoinList, UserInfo } from "~/types/store";
import { CoinList, MarketNFTData, UserInfo } from "~/types/store";
interface Store { interface Store {
state: object; state: object;
@ -11,10 +11,15 @@ class AppStore implements Store {
state = { state = {
token: "", token: "",
walletAddress: "", walletAddress: "",
coinIndex: 0,
coinList: [] as CoinList[], coinList: [] as CoinList[],
userInfo: {} as UserInfo, userInfo: {} as UserInfo,
visibleUnLogin: false, visibleUnLogin: false,
likeNft: [] as MarketNFTData[], //喜歡的NFT
myNft: [] as MarketNFTData[], //我的NFT
auctionNft: [] as MarketNFTData[], //拍賣NFT
sellNft: [] as MarketNFTData[], //售賣NFT
issueNft: [] as MarketNFTData[], //發行搶購NFT
rmdNft: [] as MarketNFTData[], //推薦NFT
}; };
constructor() { constructor() {
@ -87,14 +92,37 @@ class AppStore implements Store {
this.state.coinList = []; this.state.coinList = [];
} }
setCoinIndex(index: number): void {
this.state.coinIndex = index;
setVisibleUnLogin(bool: boolean): void {
this.state.visibleUnLogin = bool;
} }
setVisibleUnLogin(bool: boolean): void {
this.state.visibleUnLogin = bool
/**
* @param key
* @param type 1. 2.
*/
async getMyNft(key: "myNft" | "likeNft", type: number) {
const res: any = await personal_nft(type);
if (res && res.code === 0) {
this.state[key] = res.data;
}
} }
resetNft(key: "myNft" | "likeNft") {
this.state[key] = [];
}
async getMarketNft(key: "auctionNft" | "sellNft" | "rmdNft" | "issueNft") {
const types = {
auctionNft: 1,
sellNft: 2,
issueNft: 3,
rmdNft: 4,
};
const res: any = await market_list(types[key]);
if (res && res.code === 0 && res.data) {
this.state[key] = res.data;
}
}
} }
const store = new AppStore(); const store = new AppStore();

9
src/styles/components.scss

@ -53,6 +53,13 @@
} }
.product-info{ .product-info{
.name-overflow{
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.cover-box{ .cover-box{
width: 100%; width: 100%;
border-radius: 30px; border-radius: 30px;
@ -67,7 +74,7 @@
} }
.price-tag{ .price-tag{
background: linear-gradient(104deg, #320D6D 3%, #8A4CED 106%);
// background: linear-gradient(104deg, #320D6D 3%, #8A4CED 106%);
font-size: 14px; font-size: 14px;
color: $white; color: $white;
padding: 8px 20px; padding: 8px 20px;

8
src/styles/detail.scss

@ -5,4 +5,12 @@
height: 65px; height: 65px;
} }
.buy-nft{
.success-img{
width: 125px;
height: 100%;
object-fit: cover;
}
}
} }

11
src/styles/global.scss

@ -8,7 +8,8 @@ $colors:(
red : $red, red : $red,
white : $white, white : $white,
black : $black, black : $black,
page:$page
page:$page,
like-text:$like
); );
$iterations: 5; $iterations: 5;
@ -26,6 +27,14 @@ body {
background-color: rgb(248,247,255); background-color: rgb(248,247,255);
} }
.USDT-bg{
background: $usdt-bg;
}
.FIL-bg{
background: $fil-bg;
}
@each $key,$color in $colors{ @each $key,$color in $colors{
.#{""+$key}{ color:$color }; .#{""+$key}{ color:$color };
.#{""+$key}-bg{background-color: $color }; .#{""+$key}-bg{background-color: $color };

47
src/styles/home.scss

@ -1,5 +1,52 @@
.home{ .home{
.rush-swiper{
height: 320px;
width: 100%;
border-radius: 30px;
overflow: hidden;
background-color: $primary;
position: relative;
img{
width: 100%;
height: 100%;
object-fit: cover;
}
.content-box{
position: absolute;
bottom: 0;
left: 0;
background-color: $white;
width: 100%;
height: 130px;
.tag{
background: $usdt-bg;
color: $white;
}
.avatar-img {
width: 30px;
height: 30px;
object-fit: cover;
border-radius: 30px;
}
.right-box{
width: 80px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.plr-5px{
padding: 0px 5px;
}
.swiper{ .swiper{
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;

2
src/styles/product.scss

@ -26,7 +26,7 @@
} }
} }
.box{ .box{
height: 210px;
min-height: 260px;
width: 100%; width: 100%;
} }
} }

3
src/styles/theme.scss

@ -8,12 +8,15 @@ $red:#F6465D;
$white:#fff; $white:#fff;
$black:#2A2C24; $black:#2A2C24;
$page:rgb(248,247,255); $page:rgb(248,247,255);
$like:#F96900;
$width:var(--width); $width:var(--width);
$height:var(--height); $height:var(--height);
$button-background: linear-gradient(103deg, #1DD0DF -1%, #1DD0DF -1%, #1BEDFF -1%, #14BDEB 108%); $button-background: linear-gradient(103deg, #1DD0DF -1%, #1DD0DF -1%, #1BEDFF -1%, #14BDEB 108%);
$button-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.3); $button-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.3);
$fil-bg: linear-gradient(95deg, #F96900 0%, #FFAA6B 115%, #FF800B 115%);
$usdt-bg: linear-gradient(104deg, #320D6D 3%, #8A4CED 106%);
@mixin img-size($width,$height){ @mixin img-size($width,$height){
width: $white; width: $white;

1
src/types/store.d.ts

@ -59,6 +59,7 @@ interface MarketNFTData {
time: number; time: number;
total_stock: number; total_stock: number;
url: string; url: string;
type:number;
} }
interface InviteRecordData { interface InviteRecordData {

3
src/utils/index.ts

@ -63,7 +63,7 @@ const toThousands = (value: number | string) => {
* @param value * @param value
* @returns * @returns
*/ */
const getTime = (value: number) => {
const getTime = (value: number, type?: string) => {
let date = new Date(value); let date = new Date(value);
let yy: number | string = date.getFullYear(); let yy: number | string = date.getFullYear();
let mm: number | string = date.getMonth() + 1; let mm: number | string = date.getMonth() + 1;
@ -76,6 +76,7 @@ const getTime = (value: number) => {
xs = xs >= 10 ? xs : "0" + xs; xs = xs >= 10 ? xs : "0" + xs;
ff = ff >= 10 ? ff : "0" + ff; ff = ff >= 10 ? ff : "0" + ff;
ss = ss >= 10 ? ss : "0" + ss; ss = ss >= 10 ? ss : "0" + ss;
if(type === 'day') return `${yy}-${mm}-${dd}`
return `${yy}-${mm}-${dd} ${xs}:${ff}`; return `${yy}-${mm}-${dd} ${xs}:${ff}`;
}; };

Loading…
Cancel
Save