diff --git a/.env b/.env new file mode 100644 index 0000000..7485974 --- /dev/null +++ b/.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' diff --git a/config/webpackDevServer.config.js b/config/webpackDevServer.config.js index 52f4edf..9446ab0 100644 --- a/config/webpackDevServer.config.js +++ b/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 diff --git a/env.md b/env.md new file mode 100644 index 0000000..c1a5fe6 --- /dev/null +++ b/env.md @@ -0,0 +1,8 @@ +``` +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' diff --git a/package.json b/package.json index 16cb0af..9c87c96 100644 --- a/package.json +++ b/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", diff --git a/src/App.tsx b/src/App.tsx index db1924a..91a9b32 100644 --- a/src/App.tsx +++ b/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 ( diff --git a/src/api/axios_config.ts b/src/api/axios_config.ts new file mode 100644 index 0000000..a3039e9 --- /dev/null +++ b/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; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..ef6ae96 --- /dev/null +++ b/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 }) \ No newline at end of file diff --git a/src/api/service.ts b/src/api/service.ts new file mode 100644 index 0000000..23d02f6 --- /dev/null +++ b/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; \ No newline at end of file diff --git a/src/assets/iconfont/iconfont.css b/src/assets/iconfont/iconfont.css index b88bc1f..f7a7bc9 100644 --- a/src/assets/iconfont/iconfont.css +++ b/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"; } diff --git a/src/assets/iconfont/iconfont.js b/src/assets/iconfont/iconfont.js index 7d9d704..c8a5e75 100644 --- a/src/assets/iconfont/iconfont.js +++ b/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4379626='',function(c){var t=(t=document.getElementsByTagName("script"))[t.length-1],e=t.getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var o,i,n,l,a,s=function(t,e){e.parentNode.insertBefore(t,e)};if(e&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}o=function(){var t,e=document.createElement("div");e.innerHTML=c._iconfont_svg_string_4379626,(e=e.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",e=e,(t=document.body).firstChild?s(e,t.firstChild):t.appendChild(e))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),o()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(n=o,l=c.document,a=!1,h(),l.onreadystatechange=function(){"complete"==l.readyState&&(l.onreadystatechange=null,d())})}function d(){a||(a=!0,n())}function h(){try{l.documentElement.doScroll("left")}catch(t){return void setTimeout(h,50)}d()}}(window); \ No newline at end of file +window._iconfont_svg_string_4379626='',function(c){var t=(t=document.getElementsByTagName("script"))[t.length-1],e=t.getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var o,i,n,l,a,s=function(t,e){e.parentNode.insertBefore(t,e)};if(e&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}o=function(){var t,e=document.createElement("div");e.innerHTML=c._iconfont_svg_string_4379626,(e=e.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",e=e,(t=document.body).firstChild?s(e,t.firstChild):t.appendChild(e))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),o()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(n=o,l=c.document,a=!1,h(),l.onreadystatechange=function(){"complete"==l.readyState&&(l.onreadystatechange=null,d())})}function d(){a||(a=!0,n())}function h(){try{l.documentElement.doScroll("left")}catch(t){return void setTimeout(h,50)}d()}}(window); \ No newline at end of file diff --git a/src/assets/iconfont/iconfont.json b/src/assets/iconfont/iconfont.json index 43c3c57..1a8d423 100644 --- a/src/assets/iconfont/iconfont.json +++ b/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上传、导出", diff --git a/src/assets/iconfont/iconfont.ttf b/src/assets/iconfont/iconfont.ttf index 0a2e44e..1ce0667 100644 Binary files a/src/assets/iconfont/iconfont.ttf and b/src/assets/iconfont/iconfont.ttf differ diff --git a/src/assets/iconfont/iconfont.woff b/src/assets/iconfont/iconfont.woff index 50cc137..905d5ea 100644 Binary files a/src/assets/iconfont/iconfont.woff and b/src/assets/iconfont/iconfont.woff differ diff --git a/src/assets/iconfont/iconfont.woff2 b/src/assets/iconfont/iconfont.woff2 index 5c74464..f82e642 100644 Binary files a/src/assets/iconfont/iconfont.woff2 and b/src/assets/iconfont/iconfont.woff2 differ diff --git a/src/assets/tabbar/tabbar-3-o.png b/src/assets/tabbar/tabbar-3-o.png index 52ee5dc..8b3ef3e 100644 Binary files a/src/assets/tabbar/tabbar-3-o.png and b/src/assets/tabbar/tabbar-3-o.png differ diff --git a/src/assets/tabbar/tabbar-3.png b/src/assets/tabbar/tabbar-3.png index b7f37f4..98bd0b3 100644 Binary files a/src/assets/tabbar/tabbar-3.png and b/src/assets/tabbar/tabbar-3.png differ diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..3346303 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,3 @@ +interface Window { + ethereum?: any; +} \ No newline at end of file diff --git a/src/hooks/useConnectWallet.ts b/src/hooks/useConnectWallet.ts new file mode 100644 index 0000000..3abfd6a --- /dev/null +++ b/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 }; +} diff --git a/src/index.tsx b/src/index.tsx index b72d258..525aa1f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,6 +6,7 @@ import '~/styles/global.scss' const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); + root.render( diff --git a/src/pages/cart/index.tsx b/src/pages/cart/index.tsx deleted file mode 100644 index ce939fd..0000000 --- a/src/pages/cart/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Divider } from 'react-vant' -import '~/styles/cart.scss' - -const Cart = () => { - return ( -
-
购物车
-
- { - Array.from({ length: 5 }).map((_, index) => ( -
- -
-
The Unkown
-
-
ETH 2.25
-
- -
iamjackrider
-
-
-
Top Bid is By You
-
Time Remaining
-
-
- - -
00:02:30
-
-
- -
-
-
-
- )) - } -
-
- ) -} - -export default Cart \ No newline at end of file diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 4cf6fdd..92a5c29 100644 --- a/src/pages/home/index.tsx +++ b/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 = () => {
拍卖趋势
- push('/detail')} /> - - + { + Array.from({ length: 5 }).map((_, index) => ( +
+ push('/detail')} /> +
+
+
+
生肖唐彩-龙
+
+
+
铸造者
+
+ +
Filefast
+
+
+
+
售卖者
+
+ +
Filefast
+
+
+
+
+
+ + +
+
+
+
+ )) + }
售卖市场
diff --git a/src/pages/personal/index.tsx b/src/pages/personal/index.tsx index b707c37..e62ab24 100644 --- a/src/pages/personal/index.tsx +++ b/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 ( -
-
- -
- -
-
-
-
IamjackRider
-
-
开放我的资产
-
-
-
-
120K
-
ArtWorks
-
-
-
120K
-
Auctions
-
-
-
255 ETH
-
Earning
-
-
-
- - -
- {Array.from({ length: 10 }).map((_, index) => )} -
-
- -
- {Array.from({ length: 10 }).map((_, index) => )} +
+ + {/*
购物车
+
+ { + Array.from({ length: 5 }).map((_, index) => ( +
+ +
+
The Unkown
+
+
ETH 2.25
+
+ +
iamjackrider
+
+
+
Top Bid is By You
+
Time Remaining
+
+
+ + +
00:02:30
+
+
+ +
+
+
- - -
+ )) + } */} + {/*
*/}
) } -export default Person \ No newline at end of file +export default Personal \ No newline at end of file diff --git a/src/pages/share/index.tsx b/src/pages/share/index.tsx new file mode 100644 index 0000000..f9c35ea --- /dev/null +++ b/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 ( +
+
+ +
+ +
+
+
+
IamjackRider
+
+
开放我的资产
+
+
+
+
5
+
售卖作品
+
+
+
15
+
拍卖作品
+
+
+
255 FIL
+
收入
+
+
+
+ + + 我的NFT + 25 +
} + titleClass='fz-wb-550' + > +
+ {Array.from({ length: 10 }).map((_, index) => )} +
+ + + 我喜欢的NFT + 999+ +
} + titleClass='fz-wb-550' + > +
+ {Array.from({ length: 10 }).map((_, index) => )} +
+
+
+
+
+ ) +} + +export default Share \ No newline at end of file diff --git a/src/router/index.tsx b/src/router/index.tsx deleted file mode 100644 index c215086..0000000 --- a/src/router/index.tsx +++ /dev/null @@ -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 ( -
-
-
9527
-
setVisible(true)}> -
- -
-
-
- - { - tabbarData.includes(location.pathname) &&
- } -
- { - tabbarData.includes(location.pathname) && ( -
- { - tabbarData.map((item, index) => ( -
push(item)}> - - { - item === location.pathname && -
-
-
- } -
- )) - } -
- ) - } - -
- ); -} - -export default LayoutRouter \ No newline at end of file diff --git a/src/router/layout/ConnectButton.tsx b/src/router/layout/ConnectButton.tsx new file mode 100644 index 0000000..930d38e --- /dev/null +++ b/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(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 ( +
+ { + token ? ( + + {splitAddress(walletAddress)} + + } + > + + + ) : ( + + ) + } + + +
+ ) +} + +export default observer(ConnectButton) \ No newline at end of file diff --git a/src/router/layout/Navbar.tsx b/src/router/layout/Navbar.tsx new file mode 100644 index 0000000..45fbab2 --- /dev/null +++ b/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 ( +
+
9527
+ {/*
首页
*/} +
+ +
setVisible(true)}> +
+ +
+
+
+ ) +} + +export default Navbar \ No newline at end of file diff --git a/src/router/Notify.tsx b/src/router/layout/Notify.tsx similarity index 100% rename from src/router/Notify.tsx rename to src/router/layout/Notify.tsx diff --git a/src/router/router.tsx b/src/router/layout/RenderRouter.tsx similarity index 83% rename from src/router/router.tsx rename to src/router/layout/RenderRouter.tsx index 81bd74c..195b804 100644 --- a/src/router/router.tsx +++ b/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 (
@@ -22,4 +22,4 @@ const Router = () => { ) } -export default Router; +export default RenderRouter; diff --git a/src/router/layout/Tabbar.tsx b/src/router/layout/Tabbar.tsx new file mode 100644 index 0000000..c3c7770 --- /dev/null +++ b/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 ( +
+ { + tabbarData.map((item, index) => ( +
push(item)}> + + { + item === pathname && +
+
+
+ } +
+ )) + } +
+ ) +} + +export default Tabbar \ No newline at end of file diff --git a/src/router/layout/index.tsx b/src/router/layout/index.tsx new file mode 100644 index 0000000..22a3788 --- /dev/null +++ b/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(null); + + useEffect(() => { + // 在路由变化时将自定义滚动条滚动到顶部 + + }, [location.pathname]) + + return ( +
+ +
+ + { + tabbarData.includes(location.pathname) &&
+ } +
+ { + tabbarData.includes(location.pathname) && ( + + ) + } + +
+ ); +} + +export default LayoutRouter \ No newline at end of file diff --git a/src/router/routes.tsx b/src/router/routes.tsx index 9b6f43b..89cd197 100644 --- a/src/router/routes.tsx +++ b/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: , }, { - path: "/cart", - element: , + path: "/share", + element: , }, { path: "/personal", - element: , + element: , }, { path: "/detail", @@ -33,7 +33,7 @@ const routes = [ export const tabbarData = [ '/', '/product', - '/cart', + '/share', '/personal', ] diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..5e25ec8 --- /dev/null +++ b/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; diff --git a/src/styles/cart.scss b/src/styles/cart.scss deleted file mode 100644 index e4ddc9c..0000000 --- a/src/styles/cart.scss +++ /dev/null @@ -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; - } - - } - -} diff --git a/src/styles/home.scss b/src/styles/home.scss index e1196c4..804cb17 100644 --- a/src/styles/home.scss +++ b/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; } } \ No newline at end of file diff --git a/src/styles/layout.scss b/src/styles/layout.scss index 073565c..82a1699 100644 --- a/src/styles/layout.scss +++ b/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; + } + } \ No newline at end of file diff --git a/src/styles/personal.scss b/src/styles/personal.scss index a180f11..e4ddc9c 100644 --- a/src/styles/personal.scss +++ b/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; } -} \ No newline at end of file +} diff --git a/src/styles/share.scss b/src/styles/share.scss new file mode 100644 index 0000000..a180f11 --- /dev/null +++ b/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; + } + +} \ No newline at end of file diff --git a/src/types/api.d.ts b/src/types/api.d.ts new file mode 100644 index 0000000..5a86228 --- /dev/null +++ b/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 } diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..fabb6b8 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,2 @@ +export { StoreLocalStorageKey } from "./store.d"; +export type { PerformSignin, PerformSNonce } from "./api"; diff --git a/src/types/store.d.ts b/src/types/store.d.ts new file mode 100644 index 0000000..30dec6a --- /dev/null +++ b/src/types/store.d.ts @@ -0,0 +1,6 @@ +enum StoreLocalStorageKey { + TOKEN = "MARKET_NFT_TOKEN", + ADDRESS = "MARKET_NFT_ADDRESS", +} + +export { StoreLocalStorageKey }; diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..f820481 --- /dev/null +++ b/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 }; diff --git a/src/utils/sign/sign.ts b/src/utils/sign/sign.ts new file mode 100644 index 0000000..6f088ee --- /dev/null +++ b/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; \ No newline at end of file diff --git a/src/utils/sign/sort.ts b/src/utils/sign/sort.ts new file mode 100644 index 0000000..9dbfee3 --- /dev/null +++ b/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; diff --git a/yarn.lock b/yarn.lock index 6c13ae8..ecb7c8a 100644 --- a/yarn.lock +++ b/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"