Browse Source

commit

master
yyy9608 9 months ago
parent
commit
ed648a7365
  1. 5
      .env
  2. 7
      config/webpackDevServer.config.js
  3. 8
      env.md
  4. 2
      package.json
  5. 2
      src/App.tsx
  6. 35
      src/api/axios_config.ts
  7. 15
      src/api/index.ts
  8. 65
      src/api/service.ts
  9. 10
      src/assets/iconfont/iconfont.css
  10. 2
      src/assets/iconfont/iconfont.js
  11. 7
      src/assets/iconfont/iconfont.json
  12. BIN
      src/assets/iconfont/iconfont.ttf
  13. BIN
      src/assets/iconfont/iconfont.woff
  14. BIN
      src/assets/iconfont/iconfont.woff2
  15. BIN
      src/assets/tabbar/tabbar-3-o.png
  16. BIN
      src/assets/tabbar/tabbar-3.png
  17. 3
      src/global.d.ts
  18. 87
      src/hooks/useConnectWallet.ts
  19. 1
      src/index.tsx
  20. 43
      src/pages/cart/index.tsx
  21. 43
      src/pages/home/index.tsx
  22. 87
      src/pages/personal/index.tsx
  23. 71
      src/pages/share/index.tsx
  24. 56
      src/router/index.tsx
  25. 78
      src/router/layout/ConnectButton.tsx
  26. 29
      src/router/layout/Navbar.tsx
  27. 0
      src/router/layout/Notify.tsx
  28. 6
      src/router/layout/RenderRouter.tsx
  29. 36
      src/router/layout/Tabbar.tsx
  30. 47
      src/router/layout/index.tsx
  31. 12
      src/router/routes.tsx
  32. 64
      src/store/index.ts
  33. 60
      src/styles/cart.scss
  34. 60
      src/styles/home.scss
  35. 21
      src/styles/layout.scss
  36. 80
      src/styles/personal.scss
  37. 44
      src/styles/share.scss
  38. 12
      src/types/api.d.ts
  39. 2
      src/types/index.ts
  40. 6
      src/types/store.d.ts
  41. 14
      src/utils/index.ts
  42. 21
      src/utils/sign/sign.ts
  43. 35
      src/utils/sign/sort.ts
  44. 23
      yarn.lock

5
.env

@ -0,0 +1,5 @@
SKIP_PREFLIGHT_CHECK=true
GENERATE_SOURCEMAP=false
REACT_APP_BASE_URL='http://14.29.101.215:30304'
REACT_APP_SHARE_LINK='http://14.29.101.215:30305/#/'
REACT_APP_SIGN_KEY='finance_ad123'

7
config/webpackDevServer.config.js

@ -100,7 +100,12 @@ module.exports = function (proxy, allowedHost) {
index: paths.publicUrlOrPath,
},
// `proxy` is run between `before` and `after` `webpack-dev-server` hooks
proxy,
proxy: {
'/api': {
target: 'http://14.29.101.215:30304', // 你要代理的后端API服务器地址
changeOrigin: true, // 设置为true,以处理跨域请求
},
},
onBeforeSetupMiddleware(devServer) {
// Keep `evalSourceMapMiddleware`
// middlewares before `redirectServedPath` otherwise will not have any effect

8
env.md

@ -0,0 +1,8 @@
```
SKIP_PREFLIGHT_CHECK=true
GENERATE_SOURCEMAP=false
<!-- dev -->
REACT_APP_BASE_URL='http://14.29.101.215:30304'
REACT_APP_SHARE_LINK='http://14.29.101.215:30305/#/'
REACT_APP_SIGN_KEY='finance_ad123'

2
package.json

@ -20,6 +20,7 @@
"babel-preset-react-app": "^10.0.1",
"bfj": "^7.0.2",
"browserslist": "^4.18.1",
"buffer": "^6.0.3",
"camelcase": "^6.2.1",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"css-loader": "^6.5.1",
@ -37,6 +38,7 @@
"jest": "^27.4.3",
"jest-resolve": "^27.4.2",
"jest-watch-typeahead": "^1.0.0",
"js-md5": "^0.8.3",
"mini-css-extract-plugin": "^2.4.5",
"mobx": "^6.12.0",
"mobx-react": "^9.1.0",

2
src/App.tsx

@ -1,6 +1,6 @@
import '~/assets/iconfont/iconfont.css';
import { HashRouter } from 'react-router-dom';
import LayoutRouter from '~/router'
import LayoutRouter from '~/router/layout'
function App() {
return (

35
src/api/axios_config.ts

@ -0,0 +1,35 @@
export default {
baseURL: process.env.NODE_ENV === 'development' ? '/api' : process.env.REACT_APP_BASE_URL + '/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;

15
src/api/index.ts

@ -0,0 +1,15 @@
import { PerformSignin, PerformSNonce } from "~/types"
import request from './service'
/**
* @description
*/
export const getNonce = (query: PerformSNonce) => request({
url: '/v1/nonce',
data: query
})
/**
* @description
*/
export const performSignin = (query: PerformSignin) => request({ url: '/v1/signin', data: query })

65
src/api/service.ts

@ -0,0 +1,65 @@
import axiosConfig from "./axios_config";
import axios from 'axios';
import signGenerator from "../utils/sign/sign";
import { Toast } from "react-vant";
import sortParam from "../utils/sign/sort";
import store from '../store';
const service = axios.create(axiosConfig);
// 请求拦截
service.interceptors.request.use(
config => {
(config.headers as any).token = store.state.token;
if (!config.data) config.data = {};
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.data = JSON.stringify(config.data);
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截
service.interceptors.response.use(
(res: any) => {
try {
let data = JSON.parse(res.data);
if (data.code === 101) { //Token 过期
store.removeAddr()
store.removeToken()
};
if (data.code !== 0) {
Toast.info({
message: data.msg,
duration: 2000
});
};
return data
} catch (error) {
return null;
}
},
error => {
try {
if(error.response){
let data = JSON.parse(error.response.data);
Toast.info(data.err);
return data
}
} catch (error) {
console.error(error);
}
}
)
export default service;

10
src/assets/iconfont/iconfont.css

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4379626 */
src: url('iconfont.woff2?t=1702978569341') format('woff2'),
url('iconfont.woff?t=1702978569341') format('woff'),
url('iconfont.ttf?t=1702978569341') format('truetype');
src: url('iconfont.woff2?t=1703215943461') format('woff2'),
url('iconfont.woff?t=1703215943461') format('woff'),
url('iconfont.ttf?t=1703215943461') format('truetype');
}
.iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-tuichu:before {
content: "\e669";
}
.icon-shangchuandaochu:before {
content: "\e8c6";
}

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-",
"description": "",
"glyphs": [
{
"icon_id": "12331673",
"name": "退出",
"font_class": "tuichu",
"unicode": "e669",
"unicode_decimal": 58985
},
{
"icon_id": "1727451",
"name": "219上传、导出",

BIN
src/assets/iconfont/iconfont.ttf

BIN
src/assets/iconfont/iconfont.woff

BIN
src/assets/iconfont/iconfont.woff2

BIN
src/assets/tabbar/tabbar-3-o.png

Before

Width: 79  |  Height: 71  |  Size: 1.5 KiB

After

Width: 72  |  Height: 72  |  Size: 1.9 KiB

BIN
src/assets/tabbar/tabbar-3.png

Before

Width: 79  |  Height: 71  |  Size: 1.8 KiB

After

Width: 72  |  Height: 72  |  Size: 3.3 KiB

3
src/global.d.ts

@ -0,0 +1,3 @@
interface Window {
ethereum?: any;
}

87
src/hooks/useConnectWallet.ts

@ -0,0 +1,87 @@
import { Toast } from "react-vant";
import { getNonce, performSignin } from "~/api";
import $store from "../store";
import { toNumber } from "ethers";
export default function useConnectWallet() {
const connect = async () => {
if (!(window as any).ethereum) {
return;
}
let res = await (window as any).ethereum.request({
method: "eth_requestAccounts",
});
if (res.length <= 0) {
removeTokenAndAddress();
} else {
return res;
}
};
const removeTokenAndAddress = () => {
$store.removeAddr();
$store.removeToken();
};
const getWallet = async () => {
if (!(window as any).ethereum) {
return;
}
let res = await (window as any).ethereum.request({
method: "eth_accounts",
});
if (res.length <= 0) {
removeTokenAndAddress();
}
};
const sign = async (nonce: string, address: string) => {
let random = "";
if (
(window as any).ethereum.isTokenPocket ||
(window as any).ethereum.isTrust
) {
random = nonce;
} else {
const Buffer = require("buffer").Buffer;
const buff = Buffer.from(nonce, "utf-8");
random = "0x" + buff.toString("hex");
}
const signature = await (window as any).ethereum.request({
method: "personal_sign",
params: [random, address],
});
return signature;
};
const login = async () => {
try {
let [address] = await connect();
const chain_id = await window.ethereum.request({
method: "eth_chainId",
params: [],
});
let nonce: any = await getNonce({
address: address,
chainId: toNumber(chain_id),
});
if (nonce.code !== 0) return;
let random = nonce.data.nonce;
let signature = await sign(random, address);
let res = await performSignin({
address,
nonce: random,
signature,
// chain_id
});
Toast.success("登錄成功");
$store.setAddress(address);
$store.setToken(res.data.token);
} catch (error) {
console.log(error);
}
};
return { login, getWallet };
}

1
src/index.tsx

@ -6,6 +6,7 @@ import '~/styles/global.scss'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />

43
src/pages/cart/index.tsx

@ -1,43 +0,0 @@
import { Divider } from 'react-vant'
import '~/styles/cart.scss'
const Cart = () => {
return (
<div className='plr-2 cart'>
<div className='fz-wb-550 mtb-2'></div>
<div>
{
Array.from({ length: 5 }).map((_, index) => (
<div className='row-items mt-3' key={index}>
<img src={require('~/assets/cover.png')} className="cover" alt="" />
<div className='box p-2'>
<div className='fz-14'>The Unkown</div>
<div className='row-items mt-5px'>
<div className='price-tag'>ETH 2.25</div>
<div className='user-tag ml-1'>
<img src={require('~/assets/user.png')} className="img" alt="" />
<div className='ml-3px'>iamjackrider</div>
</div>
</div>
<div className='fz-12 sub-text mt-1'>Top Bid is By You</div>
<div className='fz-12 sub-text mt-1'>Time Remaining</div>
<div className='row-items mt-5px'>
<div className='timing-box fz-12'>
<i className='iconfont icon-clock fz-12 fz-wb-550' style={{ color: '#F96900' }} />
<Divider type='vertical' style={{ borderColor: '#C4C4C4' }} className="ml-5px" />
<div className='ml-5px fz-wb-550' style={{ color: '#F96900' }}>00:02:30</div>
</div>
<div className='ml-4 row-center delete'>
<i className='iconfont icon-delete'></i>
</div>
</div>
</div>
</div>
))
}
</div>
</div>
)
}
export default Cart

43
src/pages/home/index.tsx

@ -1,6 +1,7 @@
import '~/styles/home.scss'
import ProductItem from '~/components/ProductItem'
import { useRouter } from '~/hooks/useRouter'
import { Button } from 'react-vant'
const Home = () => {
@ -10,9 +11,45 @@ const Home = () => {
<div className="home">
<div className='plr-3 fz-20 mt-2'></div>
<div className='mt-2 row-items swiper'>
<img src={require('~/assets/nft/dragon.jpg')} alt="" className='swiper-img ml-3' onClick={() => push('/detail')} />
<img src={require('~/assets/nft/dragon.jpg')} alt="" className='swiper-img ml-5' />
<img src={require('~/assets/nft/dragon.jpg')} alt="" className='swiper-img ml-5 mr-3' />
{
Array.from({ length: 5 }).map((_, index) => (
<div
key={index}
className={`swiper-item ${index === 0 ? 'ml-3' : 'ml-1'} ${index === 4 && 'mr-3'}`}
>
<img src={require('~/assets/nft/dragon.jpg')} alt="" className='swiper-img' onClick={() => push('/detail')} />
<div className='cover-box'>
<div className='cover-box-item p-1 fz-12'>
<div>
<div>-</div>
<div className='row-items mt-2px'>
<div className='flex-1'>
<div className='fz-8 fz-wb-550'></div>
<div className='user-tag row-items mt-2px'>
<img src={require('~/assets/user.png')} alt="" />
<div className='flex-1 ml-5px'>Filefast</div>
</div>
</div>
<div className='flex-1'>
<div className='fz-8 fz-wb-550'></div>
<div className='user-tag row-items mt-2px'>
<img src={require('~/assets/user.png')} alt="" />
<div className='flex-1 ml-5px'>Filefast</div>
</div>
</div>
</div>
</div>
<div className='row-between'>
<Button className='button' round>
<div className='fz-wb-550'></div>
</Button>
<i className='iconfont icon-shoucang fz-14' />
</div>
</div>
</div>
</div>
))
}
</div>
<div className='plr-3 fz-20 mt-2'></div>
<div className='plr-3'>

87
src/pages/personal/index.tsx

@ -1,59 +1,44 @@
import { Tabs } from 'react-vant'
import '~/styles/personal.scss'
import ProductItem from '~/components/ProductItem'
const Person = () => {
const Personal = () => {
return (
<div className="personal">
<div className='box'>
<img src={require('~/assets/personal.png')} alt="" className='bg-cover'></img>
<div className='row-center avatar'>
<img src={require('~/assets/avatar.png')} className="img" alt="" />
</div>
</div>
<div className='box-block'></div>
<div className='tac fz-wb-550 mt-1'>IamjackRider</div>
<div className='row-center'>
<div className='tag fz-wb-550 mt-5px'></div>
</div>
<div className='row-between plr-5 mt-3'>
<div className='tac'>
<div className='fz-20 fz-wb-550'>120K</div>
<div className='mt-8px'>ArtWorks</div>
</div>
<div className='tac'>
<div className='fz-20 fz-wb-550'>120K</div>
<div className='mt-8px'>Auctions</div>
</div>
<div className='tac'>
<div className='fz-20 fz-wb-550'>255 ETH</div>
<div className='mt-8px'>Earning</div>
</div>
</div>
<div className='mt-3'>
<Tabs
lineWidth={100}
background="none"
titleInactiveColor='#000'
titleActiveColor='#11C0CB'
color='#11C0CB'
animated
swipeable
>
<Tabs.TabPane title="我的NFT" titleClass='fz-wb-550'>
<div className='row-between flex-wrap plr-3'>
{Array.from({ length: 10 }).map((_, index) => <ProductItem key={index} />)}
</div>
</Tabs.TabPane>
<Tabs.TabPane title="我喜欢的NFT" titleClass='fz-wb-550'>
<div className='row-between flex-wrap plr-3'>
{Array.from({ length: 10 }).map((_, index) => <ProductItem key={index} />)}
<div className='plr-2 cart'>
{/* <div className='fz-wb-550 mtb-2'></div>
<div>
{
Array.from({ length: 5 }).map((_, index) => (
<div className='row-items mt-3' key={index}>
<img src={require('~/assets/cover.png')} className="cover" alt="" />
<div className='box p-2'>
<div className='fz-14'>The Unkown</div>
<div className='row-items mt-5px'>
<div className='price-tag'>ETH 2.25</div>
<div className='user-tag ml-1'>
<img src={require('~/assets/user.png')} className="img" alt="" />
<div className='ml-3px'>iamjackrider</div>
</div>
</div>
<div className='fz-12 sub-text mt-1'>Top Bid is By You</div>
<div className='fz-12 sub-text mt-1'>Time Remaining</div>
<div className='row-items mt-5px'>
<div className='timing-box fz-12'>
<i className='iconfont icon-clock fz-12 fz-wb-550' style={{ color: '#F96900' }} />
<Divider type='vertical' style={{ borderColor: '#C4C4C4' }} className="ml-5px" />
<div className='ml-5px fz-wb-550' style={{ color: '#F96900' }}>00:02:30</div>
</div>
<div className='ml-4 row-center delete'>
<i className='iconfont icon-delete'></i>
</div>
</div>
</div>
</div>
</Tabs.TabPane>
</Tabs>
</div>
))
} */}
{/* </div> */}
</div>
)
}
export default Person
export default Personal

71
src/pages/share/index.tsx

@ -0,0 +1,71 @@
import { Tabs } from 'react-vant'
import '~/styles/share.scss'
import ProductItem from '~/components/ProductItem'
const Share = () => {
return (
<div className="personal">
<div className='box'>
<img src={require('~/assets/personal.png')} alt="" className='bg-cover'></img>
<div className='row-center avatar'>
<img src={require('~/assets/avatar.png')} className="img" alt="" />
</div>
</div>
<div className='box-block'></div>
<div className='tac fz-wb-550 mt-1'>IamjackRider</div>
<div className='row-center'>
<div className='tag fz-wb-550 mt-5px'></div>
</div>
<div className='row-between plr-5 mt-3'>
<div className='tac'>
<div className='fz-20 fz-wb-550'>5</div>
<div className='mt-8px'></div>
</div>
<div className='tac'>
<div className='fz-20 fz-wb-550'>15</div>
<div className='mt-8px'></div>
</div>
<div className='tac'>
<div className='fz-20 fz-wb-550'>255 FIL</div>
<div className='mt-8px'></div>
</div>
</div>
<div className='mt-3'>
<Tabs
lineWidth={100}
background="none"
titleInactiveColor='#000'
titleActiveColor='#000'
color='#11C0CB'
animated
swipeable
>
<Tabs.TabPane
title={<div>
<span>NFT</span>
<span className='ml-5px' style={{ color: '#11C0CB' }}>25</span>
</div>}
titleClass='fz-wb-550'
>
<div className='row-between flex-wrap plr-3'>
{Array.from({ length: 10 }).map((_, index) => <ProductItem key={index} />)}
</div>
</Tabs.TabPane>
<Tabs.TabPane
title={<div>
<span>NFT</span>
<span className='ml-5px' style={{ color: '#11C0CB' }}>999+ </span>
</div>}
titleClass='fz-wb-550'
>
<div className='row-between flex-wrap plr-3'>
{Array.from({ length: 10 }).map((_, index) => <ProductItem key={index} />)}
</div>
</Tabs.TabPane>
</Tabs>
</div>
</div>
)
}
export default Share

56
src/router/index.tsx

@ -1,56 +0,0 @@
import { useState } from 'react';
import '~/styles/layout.scss'
import { useRouter } from '~/hooks/useRouter';
import Notify from './Notify';
import Router from './router';
import { tabbarData } from './routes';
const LayoutRouter = () => {
const { location, push } = useRouter()
const [visible, setVisible] = useState(false)
return (
<div className='layout'>
<div className={`header plr-3 ${location.pathname === '/personal' && 'header-bg-color'}`}>
<div className='fz-wb-550'>9527</div>
<div className='row' onClick={() => setVisible(true)}>
<div className='notify-circle'></div>
<i className='iconfont icon-messages fz-24 fz-wb-1000' />
</div>
</div>
<div className='pages'>
<Router />
{
tabbarData.includes(location.pathname) && <div className='tabbar-block'></div>
}
</div>
{
tabbarData.includes(location.pathname) && (
<div className='tabbar'>
{
tabbarData.map((item, index) => (
<div key={index} onClick={() => push(item)}>
<img
src={require(`~/assets/tabbar/tabbar-${index + 1}${item === location.pathname ? '-o' : ''}.png`)}
alt=""
className='img'
/>
{
item === location.pathname &&
<div className='row-center'>
<div className='circle'></div>
</div>
}
</div>
))
}
</div>
)
}
<Notify visible={visible} setVisible={setVisible} />
</div>
);
}
export default LayoutRouter

78
src/router/layout/ConnectButton.tsx

@ -0,0 +1,78 @@
import '~/styles/layout.scss'
import { useEffect, useRef, useState } from "react"
import { Button, Popover, PopoverInstance, Toast } from "react-vant"
import { observer } from 'mobx-react'
import useConnectWallet from '~/hooks/useConnectWallet'
import store from '~/store'
import { splitAddress } from '~/utils'
const ConnectButton = () => {
const { token, walletAddress } = store.state
const [loading, setLoading] = useState(false)
const { login } = useConnectWallet();
const popover = useRef<PopoverInstance>(null);
const connectWallet = async () => {
setLoading(true);
await login();
setLoading(false);
}
const logout = () => {
popover.current && popover.current.hide();
store.removeAddr();
store.removeToken();
Toast.success('退出成功');
}
useEffect(() => {
if (!(window as any).ethereum) {
return;
};
(window as any).ethereum.on('accountsChanged', () => {
store.removeAddr();
store.removeToken();
});
}, []);
return (
<div className="connect-button">
{
token ? (
<Popover
ref={popover}
className='popover'
reference={<Button
className="button"
>
{splitAddress(walletAddress)}
</Button>
}
>
<Button className="button" onClick={logout}>
<div className='row-items'>
<div className='iconfont icon-tuichu'></div>
<div className='ml-5px mt-3px'>退</div>
</div>
</Button>
</Popover>
) : (
<Button
className="button"
loading={loading}
loadingType='ball'
onClick={connectWallet}
>
</Button>
)
}
</div >
)
}
export default observer(ConnectButton)

29
src/router/layout/Navbar.tsx

@ -0,0 +1,29 @@
import '~/styles/layout.scss'
import ConnectButton from './ConnectButton'
interface NavbarProps {
pathname: string,
setVisible: Function
}
const Navbar = (props: NavbarProps) => {
const { pathname, setVisible } = props
return (
<div className={`header plr-3 ${pathname === '/share' && 'header-bg-color'}`}>
<div className='fz-wb-550' style={{ flex: 1 }}>9527</div>
{/* <div style={{ flex: 1 }} className='tac'>首页</div> */}
<div className='row-justify-end' style={{ flex: 1 }}>
<ConnectButton />
<div className='row ml-2' onClick={() => setVisible(true)}>
<div className='notify-circle'></div>
<i className='iconfont icon-messages fz-24 fz-wb-1000' />
</div>
</div>
</div>
)
}
export default Navbar

0
src/router/Notify.tsx → src/router/layout/Notify.tsx

6
src/router/router.tsx → src/router/layout/RenderRouter.tsx

@ -1,9 +1,9 @@
import { Suspense } from "react";
import { Route, Routes } from "react-router-dom";
import { Loading } from "react-vant";
import routes from "./routes";
import routes from "../routes";
const Router = () => {
const RenderRouter = () => {
return (
<Suspense fallback={<div className='row-center' style={{ height: '80vh' }}>
<div className='tac'>
@ -22,4 +22,4 @@ const Router = () => {
)
}
export default Router;
export default RenderRouter;

36
src/router/layout/Tabbar.tsx

@ -0,0 +1,36 @@
import '~/styles/layout.scss'
interface TabbarProps {
tabbarData: string[],
push: Function,
pathname: string
}
const Tabbar = (props: TabbarProps) => {
const { tabbarData, push, pathname } = props
return (
<div className='tabbar'>
{
tabbarData.map((item, index) => (
<div key={index} onClick={() => push(item)}>
<img
src={require(`~/assets/tabbar/tabbar-${index + 1}${item === pathname ? '-o' : ''}.png`)}
alt=""
className='img'
/>
{
item === pathname &&
<div className='row-center'>
<div className='circle'></div>
</div>
}
</div>
))
}
</div>
)
}
export default Tabbar

47
src/router/layout/index.tsx

@ -0,0 +1,47 @@
import '~/styles/layout.scss'
import { LegacyRef, useEffect, useRef, useState } from 'react';
import { useRouter } from '~/hooks/useRouter';
import Notify from './Notify';
import RenderRouter from './RenderRouter';
import { tabbarData } from '../routes';
import Navbar from './Navbar';
import Tabbar from './Tabbar';
const LayoutRouter = () => {
const { location, push } = useRouter()
const [visible, setVisible] = useState(false)
const pagesRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// 在路由变化时将自定义滚动条滚动到顶部
}, [location.pathname])
return (
<div className='layout'>
<Navbar
pathname={location.pathname}
setVisible={setVisible}
/>
<div className='pages' ref={pagesRef}>
<RenderRouter />
{
tabbarData.includes(location.pathname) && <div className='tabbar-block'></div>
}
</div>
{
tabbarData.includes(location.pathname) && (
<Tabbar
tabbarData={tabbarData}
push={push}
pathname={location.pathname}
/>
)
}
<Notify visible={visible} setVisible={setVisible} />
</div>
);
}
export default LayoutRouter

12
src/router/routes.tsx

@ -3,8 +3,8 @@ import { RouteObject } from "react-router-dom";
const Home = lazy(() => import("~/pages/home"));
const Product = lazy(() => import("~/pages/product"));
const Cart = lazy(() => import("~/pages/cart"));
const Person = lazy(() => import("~/pages/personal"));
const Share = lazy(() => import("~/pages/share"));
const Personal = lazy(() => import("~/pages/personal"));
const Detail = lazy(() => import("~/pages/detail"));
const routes = [
@ -17,12 +17,12 @@ const routes = [
element: <Product />,
},
{
path: "/cart",
element: <Cart />,
path: "/share",
element: <Share />,
},
{
path: "/personal",
element: <Person />,
element: <Personal />,
},
{
path: "/detail",
@ -33,7 +33,7 @@ const routes = [
export const tabbarData = [
'/',
'/product',
'/cart',
'/share',
'/personal',
]

64
src/store/index.ts

@ -0,0 +1,64 @@
import { makeAutoObservable } from "mobx";
import { StoreLocalStorageKey } from "~/types";
interface Store {
state: object;
}
class AppStore implements Store {
state = {
token: "",
walletAddress: "",
};
constructor() {
makeAutoObservable(this);
this.initState()
}
/**
* @description
*/
initState() {
let addr = window.localStorage.getItem(StoreLocalStorageKey.ADDRESS) || "";
let token = window.localStorage.getItem(StoreLocalStorageKey.TOKEN) || "";
this.state.walletAddress = addr;
this.state.token = token;
}
/**
* @description token
*/
setToken(token: string): void {
this.state.token = token;
window.localStorage.setItem(StoreLocalStorageKey.TOKEN, token);
}
/**
* @description token
*/
removeToken(): void {
this.state.token = "";
window.localStorage.removeItem(StoreLocalStorageKey.TOKEN);
}
/**
* @description
*/
setAddress(addr: string): void {
this.state.walletAddress = addr;
window.localStorage.setItem(StoreLocalStorageKey.ADDRESS, addr);
}
/**
* @description
*/
removeAddr(): void {
this.state.walletAddress = "";
window.localStorage.removeItem(StoreLocalStorageKey.ADDRESS);
}
}
const store = new AppStore();
export default store;

60
src/styles/cart.scss

@ -1,60 +0,0 @@
.cart{
.cover{
@include img-size(170px,170px)
}
.box{
height: 151px;
width: 100%;
background-color: $white;
box-shadow: 8px 8px 20px 0px rgba(0, 0, 0, 0.1);
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
.price-tag{
padding: 0px 8px;
height: 18px;
display: flex;
align-items: center;
background: linear-gradient(114deg, #320D6D 0%, #8A4CED 108%);
color: $white;
border-radius: 50px;
font-size: 12px;
}
.user-tag{
padding: 0px 8px 0px 0px;
height: 18px;
display: flex;
align-items: center;
background-color: #F1F1F1;
border-radius: 50px;
font-size: 12px;
.img{
@include img-size(15px,15px);
border-radius:8px
}
}
.timing-box{
width: 85px;
height: 20px;
border-radius: 20px;
background-color: #f1f1f1;
display: flex;
justify-content: center;
align-items: center;
}
.delete{
width: 22px;
height: 22px;
border-radius: 12px;
background-color: #f1f1f1;
color:#F96900;
}
}
}

60
src/styles/home.scss

@ -7,10 +7,68 @@
&::-webkit-scrollbar{
display: none;
}
.swiper-item{
min-width: 284px;
min-height: 375px;
position: relative;
}
.cover-box{
position: absolute;
width: 200px;
height: 130px;
right: 0;
bottom: 0;
background-color: rgb(248,247,255);
border-radius: 15px;
.cover-box-item{
position: absolute;
right: 5px;
bottom: 5px;
width: 190px;
height: 120px;
background-color: $white;
border: 1px solid #D8D8D8;
box-shadow: 0px 0px 50px 0px rgba(0, 0, 0, 0.06);
border-radius: 15px;
position: absolute;
display: flex;
justify-content: space-between;
flex-direction: column;
.user-tag{
width: 90%;
height: 16px;
background: #f5f5f5;
font-size: 8px;
border-radius: 8px;
img{
width: 16px;
height: 16px;
border-radius: 15px;
object-fit: cover;
}
}
.button{
width: 82px;
height: 30px;
background-color: $primary;
margin: 0;
padding: 0;
}
}
}
}
.swiper-img{
@include img-size(203px,266px)
width: 220px;
height: 310px;
object-fit: cover;
border: 1px solid #D8D8D8;
}
}

21
src/styles/layout.scss

@ -17,6 +17,7 @@
border-radius: 3px;
background-color: $red;
}
}
.header-bg-color{
@ -90,4 +91,24 @@
top: 49%;
left: 0;
}
}
.connect-button{
.button{
margin: 0;
padding: 0;
width: 126px;
height: 32px;
border-radius: 5px;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.rv-popover__content{
width: 126px;
}
}

80
src/styles/personal.scss

@ -1,44 +1,60 @@
.personal{
.cart{
.box{
.cover{
@include img-size(170px,170px)
}
.box{
height: 151px;
width: 100%;
height: 200px;
position: relative;
.bg-cover{
width: 100%;
height: 200px;
top: 0;
left: 0;
object-fit:cover;
position: absolute;
border-bottom-left-radius: 50px;
border-bottom-right-radius: 50px;
background-color: $white;
box-shadow: 8px 8px 20px 0px rgba(0, 0, 0, 0.1);
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
.price-tag{
padding: 0px 8px;
height: 18px;
display: flex;
align-items: center;
background: linear-gradient(114deg, #320D6D 0%, #8A4CED 108%);
color: $white;
border-radius: 50px;
font-size: 12px;
}
.avatar{
position: absolute;
left: calc(50% - 71px);
bottom: -71px;
.user-tag{
padding: 0px 8px 0px 0px;
height: 18px;
display: flex;
align-items: center;
background-color: #F1F1F1;
border-radius: 50px;
font-size: 12px;
.img{
@include img-size(142px,142px);
border-radius: 71px;
@include img-size(15px,15px);
border-radius:8px
}
}
}
.box-block{
display: block;
height: 71px;
width: 100%;
}
.timing-box{
width: 85px;
height: 20px;
border-radius: 20px;
background-color: #f1f1f1;
display: flex;
justify-content: center;
align-items: center;
}
.delete{
width: 22px;
height: 22px;
border-radius: 12px;
background-color: #f1f1f1;
color:#F96900;
}
.tag{
background: linear-gradient(104deg, #1DD0DF -2%, #1DD0DF -2%, #1BEDFF -2%, #14BDEB 108%);
padding: 5px 15px;
font-size: 12px;
border-radius: 20px;
}
}
}

44
src/styles/share.scss

@ -0,0 +1,44 @@
.personal{
.box{
width: 100%;
height: 200px;
position: relative;
.bg-cover{
width: 100%;
height: 200px;
top: 0;
left: 0;
object-fit:cover;
position: absolute;
border-bottom-left-radius: 50px;
border-bottom-right-radius: 50px;
}
.avatar{
position: absolute;
left: calc(50% - 71px);
bottom: -71px;
.img{
@include img-size(142px,142px);
border-radius: 71px;
}
}
}
.box-block{
display: block;
height: 71px;
width: 100%;
}
.tag{
background: linear-gradient(104deg, #1DD0DF -2%, #1DD0DF -2%, #1BEDFF -2%, #14BDEB 108%);
padding: 5px 15px;
font-size: 12px;
border-radius: 20px;
}
}

12
src/types/api.d.ts

@ -0,0 +1,12 @@
interface PerformSNonce {
address: string;
chainId: number;
}
interface PerformSignin {
address: string;
nonce: string;
signature: string;
}
export { PerformSNonce, PerformSignin }

2
src/types/index.ts

@ -0,0 +1,2 @@
export { StoreLocalStorageKey } from "./store.d";
export type { PerformSignin, PerformSNonce } from "./api";

6
src/types/store.d.ts

@ -0,0 +1,6 @@
enum StoreLocalStorageKey {
TOKEN = "MARKET_NFT_TOKEN",
ADDRESS = "MARKET_NFT_ADDRESS",
}
export { StoreLocalStorageKey };

14
src/utils/index.ts

@ -0,0 +1,14 @@
const splitAddress = (address: string, index?: number) => {
try {
let idx = index ? index : 5;
return (
address.substring(0, idx) +
"..." +
address.substring(address.length - idx, address.length)
);
} catch (error) {
return "";
}
};
export { splitAddress };

21
src/utils/sign/sign.ts

@ -0,0 +1,21 @@
const md5 = require('js-md5')
var signkey = process.env.REACT_APP_SIGN_KEY
var signGenerator = (data: any) => {
var keys = [];
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;

35
src/utils/sign/sort.ts

@ -0,0 +1,35 @@
/* eslint-disable no-loop-func */
var sortParam = (data: any) => {
var keys: any = [];
for (var key in data) {
keys.push(key);
}
var ptxt = "";
for (var i = 0; i < keys.length; i++) {
if (data[keys[i]] instanceof Array) {
if (i === 0) {
data[keys[i]].forEach((v: string, index: number) => {
if (index === 1) {
ptxt += keys[i] + "=" + v;
} else {
ptxt += "&" + keys[i] + "=" + v;
}
});
} else {
data[keys[i]].forEach((v: any) => {
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;

23
yarn.lock

@ -3078,6 +3078,11 @@ balanced-match@^1.0.0:
resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
batch@0.6.1:
version "0.6.1"
resolved "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
@ -3191,6 +3196,14 @@ buffer-from@^1.0.0:
resolved "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
builtin-modules@^3.1.0:
version "3.3.0"
resolved "https://registry.npmmirror.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
@ -5328,6 +5341,11 @@ identity-obj-proxy@^3.0.0:
dependencies:
harmony-reflect "^1.4.6"
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.0:
version "5.3.0"
resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
@ -6274,6 +6292,11 @@ jiti@^1.19.1:
resolved "https://registry.npmmirror.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==
js-md5@^0.8.3:
version "0.8.3"
resolved "https://registry.npmmirror.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.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"

Loading…
Cancel
Save