index.jsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. import styles from './styles.module.scss';
  2. // components
  3. import SidebarTrigger from '@ui/SidebarTrigger';
  4. import Headroom from 'react-headroom';
  5. import Logo from '@components/Logo';
  6. import AppBar from "@mui/material/AppBar";
  7. import {useSidebar} from "@contexts/sidebarContext";
  8. import {useThemeProvider} from "@contexts/themeContext";
  9. import {useEffect, useRef, useState} from 'react';
  10. import styled from 'styled-components/macro';
  11. import {Tab, Tabs} from "@mui/material";
  12. // import LINKS from "@constants/links";
  13. import Button from "@mui/material/Button";
  14. import {NavLink, useLocation, useNavigate} from "react-router-dom";
  15. import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
  16. import ToggleButton from "@mui/material/ToggleButton";
  17. import symbol from "@assets/symbol-defs.svg";
  18. import symbol_m from "@assets/symbol-mobile.svg";
  19. import {string} from "prop-types";
  20. import IconButton from "@mui/material/IconButton";
  21. import {animated,easings} from "@react-spring/web";
  22. import {useSpring} from "@react-spring/core";
  23. import {useWindowSize} from "react-use";
  24. import ResizeObserver from "resize-observer-polyfill";
  25. import {useSelector} from "react-redux";
  26. import {allTopNavs, getRoute, getRouteChain, getUserInfo, isLogin, isLogined} from "@features/mainSlice";
  27. import LazyImage from "@components/LazyImage";
  28. import {numFormatter} from "@utils/helpers";
  29. import {ROUTE_PATH} from "@constants/routes";
  30. const i18n=global.i18n;
  31. const StyledToggleGroup= styled(ToggleButtonGroup)`
  32. border-radius: 0px !important;
  33. gap: 12px;
  34. `;
  35. const StyledToggle=styled(ToggleButton)`
  36. gap: 12px;
  37. text-transform:none !important;
  38. border: none !important;
  39. border-radius: var(--border-radius) !important;
  40. padding: 0px !important;
  41. &:hover, &:focus ,&.Mui-selected, &.active {
  42. background-color: var(--nav-item-hover) !important;
  43. a {
  44. color: var(--header);
  45. font-weight: 700;
  46. .icon {
  47. fill:${props => props.fill};
  48. }
  49. }
  50. }
  51. a {
  52. display: flex;
  53. align-items: center;
  54. color: var(--text);
  55. padding: 12px !important;
  56. transition: all var(--transition);
  57. font-family: var(--heading-font);
  58. gap: 8px;
  59. .icon {
  60. display: flex;
  61. align-items: center;
  62. justify-content: center;
  63. width: 18px;
  64. height: 18px;
  65. fill: var(--text);
  66. }
  67. }
  68. `;
  69. const DesktopNavToggle=()=>{
  70. const LINKS=useSelector(allTopNavs);
  71. const links=LINKS.slice(0,4);
  72. const {pathname} = useLocation();
  73. const [alignment, setAlignment] = useState(pathname);
  74. const currRoute=useSelector(getRoute(pathname));
  75. const getRootRoute=(pathname)=>{
  76. let route="";
  77. if (currRoute){
  78. let clv=currRoute.lv;
  79. if (clv==null)return route;
  80. for (let i = 0; i < LINKS.length; i++) {
  81. let link=LINKS[i];
  82. let lv=link.lv;
  83. if (clv.startsWith(lv)){
  84. route=link.path;
  85. break;
  86. }
  87. }
  88. }
  89. return route;
  90. }
  91. useEffect(() => {
  92. setAlignment(getRootRoute(pathname));
  93. }, [pathname]);
  94. const handleChange = (event, newAlignment) => {
  95. // setAlignment(newAlignment);
  96. };
  97. return (
  98. <StyledToggleGroup
  99. value={alignment}
  100. exclusive
  101. onChange={handleChange}
  102. >
  103. {
  104. links.map((link, index) => (
  105. <StyledToggle value={link.path} fill={link.fill.split(";").pop()} key={link.title}>
  106. <NavLink className="h4" to={link.path}>
  107. <svg className={"icon"}>
  108. <use xlinkHref={`${symbol}#${link.icon}`} />
  109. </svg>
  110. {i18n.t(link.title)}
  111. </NavLink>
  112. </StyledToggle>
  113. ))
  114. }
  115. </StyledToggleGroup>
  116. )
  117. }
  118. const DesktopSignInUp=()=>{
  119. const navigate=useNavigate();
  120. const isLogin=useSelector(isLogined);
  121. return (
  122. <div className="d-flex align-items-center justify-content-end">
  123. <IconButton aria-label="search">
  124. <svg className={"ls-icon"} style={{width:'20px',height:'20px'}}>
  125. <use xlinkHref={`${symbol}#icon_Search`} />
  126. </svg>
  127. </IconButton>
  128. {
  129. !isLogin?(
  130. <>
  131. <Button className={`m-button ${styles.btn_sign_in}`} onClick={()=>{
  132. navigate(ROUTE_PATH.SIGN_IN);
  133. }}>{i18n.t('sign_in')}</Button>
  134. <Button className={`${styles.btn_sign_up}`} onClick={()=>{
  135. navigate(ROUTE_PATH.SIGN_UP);
  136. }}>{i18n.t('sign_up')}</Button>
  137. </>
  138. ):(
  139. <>
  140. </>
  141. )
  142. }
  143. <Button className="m-button">
  144. <svg className={"ls-icon"} style={{width:'20px',height:'20px'}}>
  145. <use xlinkHref={`${symbol}#icon_Chat`} />
  146. </svg>
  147. <div className="chat-green-dont"></div>
  148. </Button>
  149. <Button className="m-button" style={{marginLeft: '0.5rem',marginRight:'0.5rem'}}>
  150. <svg className={"ls-icon"} style={{width:'20px',height:'20px'}}>
  151. <use xlinkHref={`${symbol}#icon_Language`} />
  152. </svg>
  153. </Button>
  154. {
  155. isLogin?(<UserInfo />):(<></>)
  156. }
  157. </div>
  158. )
  159. }
  160. const DesktopNav = () => {
  161. const {open} = useSidebar();
  162. const navstyle=open?styles.nav_open:"";
  163. return (
  164. <div
  165. className={`d-flex align-items-center justify-content-between ${styles.header} ${navstyle}`}>
  166. <DesktopNavToggle />
  167. <DesktopSignInUp/>
  168. </div>
  169. );
  170. }
  171. const MobileSignInUp=()=>{
  172. const navigate=useNavigate();
  173. const isLogin=useSelector(isLogined);
  174. return (
  175. <div className="d-flex align-items-center justify-content-end">
  176. {
  177. !isLogin?(
  178. <>
  179. <Button className={`m-button ${styles.btn_sign_in} ${styles.btn_sign_in_mobile}`} onClick={()=>{
  180. navigate(ROUTE_PATH.SIGN_IN);
  181. }}>{i18n.t('sign_in')}</Button>
  182. <Button className={`${styles.btn_sign_up} ${styles.btn_sign_up_mobile}`} onClick={()=>{
  183. navigate(ROUTE_PATH.SIGN_UP);
  184. }}>{i18n.t('sign_up')}</Button>
  185. </>
  186. ):(
  187. <></>
  188. )
  189. }
  190. {/*<Button className="m-button">*/}
  191. {/* <svg className={"ls-icon"} style={{width:'1.4rem',height:'1.4rem'}}>*/}
  192. {/* <use xlinkHref={`${symbol}#icon_Language`} />*/}
  193. {/* </svg>*/}
  194. {/*</Button>*/}
  195. </div>
  196. );
  197. }
  198. const MobileNav=()=>{
  199. return (
  200. <div
  201. className={`d-flex align-items-center justify-content-between ${styles.header} ${styles.header_mobile}`}>
  202. <div className={`d-flex flex-row flex-1`} style={{maxWidth:"55%"}}>
  203. <SidebarTrigger/>
  204. <Logo size="sm"/>
  205. </div>
  206. <MobileSignInUp/>
  207. <UserInfo />
  208. </div>
  209. )
  210. }
  211. const UserInfo=()=>{
  212. const isLogin=useSelector(isLogined);
  213. const user=useSelector(getUserInfo);
  214. const navigate=useNavigate();
  215. return (
  216. <>
  217. {
  218. isLogin?(
  219. <div className={`d-flex align-items-center ${styles.user_info}`} onClick={()=>{
  220. navigate(ROUTE_PATH.SETTINGS);
  221. }}>
  222. <div className={styles.info}>
  223. <div className={styles.nick}>{user.NickName}</div>
  224. <div className={styles.score}>{global.DOLLAR+" "+numFormatter(user.Score)}</div>
  225. </div>
  226. <LazyImage wrapperClassName={styles.avatar} src={user.img} alt={"Head"}/>
  227. </div>
  228. ):(<></>)
  229. }
  230. </>
  231. )
  232. }
  233. const PageHeader = () => {
  234. const LINKS=useSelector(allTopNavs);
  235. const {initHeaderHeight} = useThemeProvider();
  236. const {pathname} = useLocation();
  237. const navigate=useNavigate();
  238. const barRef = useRef(null);
  239. const currRoute=useSelector(getRoute(pathname));
  240. const chain=useSelector(getRouteChain(pathname));
  241. const {isMobile,showHeader,setShowHeader} = useThemeProvider();
  242. const [showMHeader, setShowMHeader] = useState(true);
  243. let lastscroll=0;
  244. //body node
  245. const bodyNode = document.body;
  246. // 配置观察器选项
  247. const config = { childList: true, subtree: true }; // 观察子节点的变化,并包括子树中的所有节点
  248. // 创建一个 MutationObserver 实例并传入回调函数
  249. const observer = new MutationObserver((mutationsList, observer) => {
  250. // 迭代每一个变化
  251. for(const mutation of mutationsList) {
  252. if (mutation.type === 'childList') {
  253. // 如果是子节点变化
  254. mutation.addedNodes.forEach(node => {
  255. // console.log('一个节点被添加到了 body 中。');
  256. // 这里可以进行相应的操作
  257. // if (node.className==="progressier-install-banner"){
  258. // const root = document.querySelector('#root');
  259. // if (root){
  260. // root.classList.add("progressier_bar_open");
  261. // }
  262. // }
  263. });
  264. mutation.removedNodes.forEach(node => {
  265. // console.log('一个节点从 body 中移除了。');
  266. // 这里可以进行相应的操作
  267. // if (node.className==="progressier-install-banner"){
  268. // const root = document.querySelector('#root');
  269. // if (root){
  270. // root.classList.remove("progressier_bar_open");
  271. // }
  272. // }
  273. });
  274. }
  275. }
  276. });
  277. const checkNavActive=(lv)=>{
  278. if (currRoute&&currRoute.lv)return currRoute.lv.startsWith(lv);
  279. else return false;
  280. }
  281. const getHeaderHeight = () => {
  282. // console.log(document.querySelector('div.progressier-install-banner'))
  283. const header = document.querySelector('#appbar');
  284. if (header!=null)return `${header.offsetHeight || 0}px`
  285. else return '0px';
  286. }
  287. const handleScroll=()=>{
  288. if (!barRef.showHeader)return;
  289. let scrollTop = document.documentElement.scrollTop; //滚动条滚动高度
  290. let dt=scrollTop-lastscroll;
  291. lastscroll=scrollTop;
  292. let show=false;
  293. if (dt>20){
  294. show=false;
  295. setShowMHeader(show);
  296. }
  297. else if (dt<-20){
  298. show=true;
  299. setShowMHeader(show);
  300. }
  301. }
  302. const changeHeaderHeight=()=>{
  303. document.documentElement.style.setProperty('--header-active', getHeaderHeight());
  304. initHeaderHeight(getHeaderHeight());
  305. }
  306. const resizeObserver = new ResizeObserver(entries => {
  307. for (let entry of entries) {
  308. // 获取目标元素的高度
  309. const { height } = entry.contentRect;
  310. // 可在这里处理高度变化的逻辑
  311. changeHeaderHeight();
  312. }
  313. });
  314. useEffect(() => {
  315. // 这里的代码会在组件挂载时执行
  316. // console.log('Component mounted');
  317. window.addEventListener('scroll', handleScroll);
  318. observer.observe(bodyNode, config);
  319. if (barRef.current) {
  320. resizeObserver.observe(barRef.current);
  321. }
  322. return () => {
  323. // 可选的清除函数,当组件卸载时执行
  324. // console.log('Component unmounted');
  325. window.removeEventListener('scroll', handleScroll);
  326. if (barRef.current) {
  327. resizeObserver.unobserve(barRef.current);
  328. }
  329. observer.disconnect();
  330. };
  331. }, []);
  332. // }, [pathname]);
  333. useEffect(()=>{
  334. setShowMHeader(showHeader);
  335. barRef.showHeader=showHeader;
  336. },[showHeader]);
  337. useEffect(()=>{
  338. let len=chain.length;
  339. setShowHeader(len<=1);
  340. },[pathname]);
  341. const headerAni=useSpring({
  342. from: {transform: 'translateY(-100%) translateZ(0px)'},
  343. transform:showMHeader?'translateY(0%) translateZ(0px)':'translateY(-100%) translateZ(0px)',
  344. config: {
  345. easing: easings.easeOutBack,
  346. duration:300
  347. },
  348. });
  349. const changePath=(path)=>{
  350. navigate(path);
  351. }
  352. const renderHeaderRoom=()=>{
  353. const active=styles.active;
  354. return (
  355. <animated.div className={`${styles.header_room}`} style={headerAni}>
  356. <div className={`${styles['mobile-top-navigate-list']}`}>
  357. <button className={`${styles['mb-top-navigate-item']} ${pathname==='/'?active:''}`} onClick={()=>{
  358. changePath('/');
  359. }}>
  360. <svg className={"icon"}>
  361. <use xlinkHref={`${symbol_m}#icon_Home`} />
  362. </svg>
  363. <span>{i18n.t("links.home")}</span>
  364. </button>
  365. {
  366. LINKS.map((link, index) => (
  367. <button key={index} className={`${styles['mb-top-navigate-item']} ${checkNavActive(link.lv)?active:''}`} onClick={()=>{
  368. changePath(link.path);
  369. }}>
  370. <svg className={"icon"}>
  371. <use xlinkHref={`${symbol_m}#${link.icon}`} />
  372. </svg>
  373. <span>{i18n.t(link.title)}</span>
  374. </button>
  375. ))
  376. }
  377. </div>
  378. </animated.div>
  379. );
  380. }
  381. const renderMobile=()=>{
  382. return (
  383. <>
  384. <MobileNav />
  385. {
  386. renderHeaderRoom()
  387. }
  388. </>
  389. )
  390. }
  391. const renderDesktop=()=>{
  392. return (
  393. <DesktopNav/>
  394. );
  395. }
  396. return (
  397. <div id={'appbar'} ref={barRef} className={`${styles.appbar} ${isMobile()?styles.mobile:''} ${isMobile()&&!showHeader?styles.hide_header:''}`}>
  398. {
  399. isMobile()?renderMobile():renderDesktop()
  400. }
  401. </div>
  402. )
  403. }
  404. export default PageHeader