diff --git a/src/components/proview/type.tsx b/src/components/proview/type.tsx index 080be35..47577e6 100644 --- a/src/components/proview/type.tsx +++ b/src/components/proview/type.tsx @@ -1,5 +1,5 @@ import { ReactElement } from "react"; export type ReactChildrenType = { - children: ReactElement; + children: ReactElement | ReactElement[]; }; diff --git a/src/main.tsx b/src/main.tsx index ffa31f5..dc9d580 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -19,6 +19,7 @@ import "./index.less"; // sentry import * as Sentry from "@sentry/react"; import { BrowserTracing } from "@sentry/tracing"; +import SearchPage, { loader as SearchLoader } from "@routers/search"; if (!isdev && isrelease) { Sentry.init({ dsn: "https://086f27258cce4d28aacc8c2719a683fb@sentry.vtb.link/3", @@ -59,6 +60,12 @@ const router = createBrowserRouter([ path: "photo", element: , }, + { + //视频搜索展示页面 + path: "search", + element: , + loader: SearchLoader, + }, ], }, ], diff --git a/src/routers/layout/header.tsx b/src/routers/layout/header.tsx index c153dfb..b4a8bca 100644 --- a/src/routers/layout/header.tsx +++ b/src/routers/layout/header.tsx @@ -1,12 +1,14 @@ import styles from "./layout.module.less"; import LOGO from "./logo"; -import RouterNav from "./routernav"; import { useAppDispatch } from "@store/hooks"; import { useLocation } from "react-router-dom"; -import { useEffect, useMemo } from "react"; +import { memo, useEffect } from "react"; import { changeLoadingCauseByUrl } from "@store/loading"; import { routerNameToLoading } from "@utils/router"; import { styled } from "@mui/material"; +import Search from "./search"; +import { Yituo } from "./logo/modal"; +import RouterNav from "./routernav/index"; export default function Header() { const dispatch = useAppDispatch(), location = useLocation(); @@ -17,21 +19,32 @@ export default function Header() { }) ); }, [location.pathname]); - const JSXRes = useMemo( - () => ( - - + return ; +} + +const ReactiveHeader = memo(() => { + return ( +
+
+ - - ), - [] +
+ +
+ +
+
+ +
+
+ 敬请期待 +
+
+
); - return <>{JSXRes}; -} -const Header_header = styled("header")(({ theme }) => ({ +}); + +const Header_content = styled("div")(({ theme }) => ({ flexDirection: "row", - [theme.breakpoints.down("sm")]: { - flexDirection: "row-reverse", - justifyContent: "flex-end", - }, + justifyContent: "space-between", })); diff --git a/src/routers/layout/index.tsx b/src/routers/layout/index.tsx index 78c1011..7cb4eb8 100644 --- a/src/routers/layout/index.tsx +++ b/src/routers/layout/index.tsx @@ -1,10 +1,11 @@ import { Flipped, Flipper } from "react-flip-toolkit"; -import { Outlet } from "react-router-dom"; +import { Outlet, useMatch } from "react-router-dom"; import Header from "./header"; import Header_Nav from "./nav"; import { useAppSelector } from "@store/hooks"; import { selectNavMoreShowed } from "@store/device/index"; import styles from "./layout.module.less"; +import MidContent from "./midcontent"; export default function Layout() { const showed = useAppSelector(selectNavMoreShowed); return ( @@ -22,11 +23,7 @@ export default function Layout() { }} >
- - - +
@@ -36,3 +33,16 @@ export default function Layout() { ); } + +const NavContent = () => { + const isSearchPage = useMatch(`/search`) !== null; + return
{!isSearchPage && }
; +}; + +const OldNav = () => ( + + + +); diff --git a/src/routers/layout/layout.module.less b/src/routers/layout/layout.module.less index 06ea5b4..0a22a97 100644 --- a/src/routers/layout/layout.module.less +++ b/src/routers/layout/layout.module.less @@ -1,6 +1,5 @@ .container { position: relative; - } .nav { @@ -13,7 +12,6 @@ .header { max-width: 1440px; margin: 0 auto; - padding-top: 10px; display: flex; align-items: center; background-color: #fff; diff --git a/src/routers/layout/logo/index.tsx b/src/routers/layout/logo/index.tsx index 61129b2..dfab803 100644 --- a/src/routers/layout/logo/index.tsx +++ b/src/routers/layout/logo/index.tsx @@ -27,7 +27,7 @@ export default function LOGO() { handlerReadedNews(); }; return ( -
+

EOEfans-web端 - - {RouterList.map((item, index) => ( - - ))} - - - )} - - ); -} -function RouterItem(props: TabProps & { onClick: () => void }) { - return ( - - - - ); -} diff --git a/src/routers/layout/search/index.tsx b/src/routers/layout/search/index.tsx index 9cd32ff..fc390fd 100644 --- a/src/routers/layout/search/index.tsx +++ b/src/routers/layout/search/index.tsx @@ -1,9 +1,46 @@ import SearchSharpIcon from "@mui/icons-material/SearchSharp"; -import { Form } from "react-router-dom"; +import { Form, useNavigate } from "react-router-dom"; import styles from "./search.module.less"; import { useSearchFocus } from "@components/proview/searchFocus"; import { Flipped } from "react-flip-toolkit"; +import SearchIcon from "@mui/icons-material/Search"; +// todo 增加高级搜索以及手机端的搜索 export default function Search() { + const navigate = useNavigate(); + const handlerSubmit = () => { + const value = (document.getElementById("q") as HTMLInputElement).value; + navigate(`/search?tag=${value}`); + }; + return ( + <> +
+ { + if (event.key === "Enter") { + handlerSubmit(); + } + }} + /> + + +
+ + ); +} + +function Search_1() { const { focused, bind } = useSearchFocus(); return ( <> diff --git a/src/routers/layout/search/search.module.less b/src/routers/layout/search/search.module.less index 98dd97f..4302495 100644 --- a/src/routers/layout/search/search.module.less +++ b/src/routers/layout/search/search.module.less @@ -1,80 +1,93 @@ -.search-box { - position: relative; - z-index: 10; - margin: 0 8px; - min-width: 180px; - max-width: 500px; - width: 100%; +// .search-box { +// position: relative; +// z-index: 10; +// margin: 0 8px; +// min-width: 180px; +// max-width: 500px; +// width: 100%; - .search-form { - border-radius: 8px; - display: flex; - align-items: center; - height: 40px; - opacity: .9; - transition: background-color .3s; - background-color: #f1f2f3; - border: 1px solid #E3E5E7; +// .search-form { +// border-radius: 8px; +// display: flex; +// align-items: center; +// height: 40px; +// opacity: .9; +// transition: background-color .3s; +// background-color: #f1f2f3; +// border: 1px solid #E3E5E7; - &:hover { - background-color: #fff; - } +// &:hover { +// background-color: #fff; +// } - .search-content { - flex: 1; - display: flex; - align-items: center; - position: relative; - padding: 0 8px; - margin-right: 8px; - height: 32px; - border: 2px solid transparent; - border-radius: 6px; +// .search-content { +// flex: 1; +// display: flex; +// align-items: center; +// position: relative; +// padding: 0 8px; +// margin-right: 8px; +// height: 32px; +// border: 2px solid transparent; +// border-radius: 6px; - .search-input { - flex: 1; - overflow: hidden; - padding-right: 8px; - border: none; - background-color: transparent; - box-shadow: none; - font-size: 14px; - line-height: 20px; - color: #61666D; +// .search-input { +// flex: 1; +// overflow: hidden; +// padding-right: 8px; +// border: none; +// background-color: transparent; +// box-shadow: none; +// font-size: 14px; +// line-height: 20px; +// color: #61666D; - &:focus-visible { - outline: none; - } - } - } +// &:focus-visible { +// outline: none; +// } +// } +// } - .search-btn { - display: flex; - margin: 0; - padding: 0; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - border: none; - border-radius: 6px; - cursor: pointer; - transition: background-color .3s; +// .search-btn { +// display: flex; +// margin: 0; +// padding: 0; +// align-items: center; +// justify-content: center; +// width: 32px; +// height: 32px; +// border: none; +// border-radius: 6px; +// cursor: pointer; +// transition: background-color .3s; - &:hover { - background-color: #E3E5E7; - } - } - } +// &:hover { +// background-color: #E3E5E7; +// } +// } +// } + +// .form-active { +// border-bottom-left-radius: 0; +// border-bottom-right-radius: 0; +// background-color: #fff; - .form-active { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - background-color: #fff; +// .search-content { +// background-color: #f1f2f3; +// } +// } +// } + + +.search-box { + box-shadow: 0 1px 2px rgb(0 0 0 / 8%), 0 4px 12px rgb(0 0 0 / 5%); + border-radius: 40px; + border: 1px solid #DDD; + padding: 7px; + display: flex; - .search-content { - background-color: #f1f2f3; - } + .search-button { + border-radius: 40px; } } \ No newline at end of file diff --git a/src/routers/search/hooks.tsx b/src/routers/search/hooks.tsx new file mode 100644 index 0000000..99297d1 --- /dev/null +++ b/src/routers/search/hooks.tsx @@ -0,0 +1,11 @@ +import { useScreenSize } from "@components/proview/screenSize"; +import { VideoListType } from "@routers/video/masonry"; +import { useAppSelector } from "@store/hooks"; +import { selectSearchingState } from "@store/loading"; +export const useSearchLoading = () => useAppSelector(selectSearchingState); + +export const useSearchShowState = (data: VideoListType) => { + const loading = useSearchLoading(), + isEmpty = data.length < 1; + return { loading, isEmpty }; +}; diff --git a/src/routers/search/index.tsx b/src/routers/search/index.tsx new file mode 100644 index 0000000..d8ef118 --- /dev/null +++ b/src/routers/search/index.tsx @@ -0,0 +1,34 @@ +import { NormalVideoRes } from "@routers/video/masonry"; +import { + fetchVideohandler, + PickVideoRouterImageCardType, +} from "@routers/video/tools"; +import { nanoid } from "nanoid"; +import { useLoaderData } from "react-router-dom"; +import { useSearchShowState } from "./hooks"; + +type LoaderType = { + request: { + url: string; + }; +}; +export type PromiseType = Promise; +export type UnPromisify = T extends PromiseType ? U : never; +type LoaderReturnType = UnPromisify>; +export async function loader({ request }: LoaderType) { + const url = new URL(request.url), + tag = url.searchParams.get("tag"); + const data = await fetchVideohandler(1, { order: "score", q: `tag.${tag}` }); + return { data }; +} + +export default function SearchPage() { + const { data } = useLoaderData() as LoaderReturnType, + filterData = data.map((item) => ({ + ...PickVideoRouterImageCardType(item), + id: nanoid(4), + })); + const { loading, isEmpty } = useSearchShowState(filterData), + propsObj = { loading, isEmpty, data: filterData }; + return ; +} diff --git a/src/routers/video/hooks.tsx b/src/routers/video/hooks.tsx index 682e282..56dd2a1 100644 --- a/src/routers/video/hooks.tsx +++ b/src/routers/video/hooks.tsx @@ -1,3 +1,22 @@ +import { useScreenSize } from "@components/proview/screenSize"; import { useAppSelector } from "@store/hooks"; -import { selectVideoLoadingState } from "@store/loading/index"; +import { + selectVideoLoadingState, + useChangeLoading, +} from "@store/loading/index"; +import { VideoListType } from "./masonry"; export const useVideoLoading = () => useAppSelector(selectVideoLoadingState); + +export const useChangeVideoLoading = () => { + const { handerChangeLoading } = useChangeLoading("videoIsLoading"); + return { handerChangeLoading }; +}; + +export const useVideoShowState = (data: VideoListType) => { + const { lg, md, sm } = useScreenSize(), + rightLen = (lg ? (md ? (sm ? 0 : 2) : 4) : 6) + 1; + const loading = useVideoLoading(), + isEmpty = data.length < 1, + shouldNoShowHeroLine = data.length < rightLen + 1 + 10; + return { loading, rightLen, isEmpty, shouldNoShowHeroLine }; +}; diff --git a/src/routers/video/masonry.tsx b/src/routers/video/masonry.tsx index 6944d58..b4166e2 100644 --- a/src/routers/video/masonry.tsx +++ b/src/routers/video/masonry.tsx @@ -1,24 +1,20 @@ -import { useState, useEffect, FC, memo, ReactElement } from "react"; +import { useState, useEffect, FC, memo } from "react"; import { VideoRouterImageCardType, VideoRouterMasonryType } from "./videotype"; -import { SxProps, Theme, Unstable_Grid2 as Grid } from "@mui/material"; +import { Unstable_Grid2 as Grid } from "@mui/material"; import { VideoRouterImageCard } from "./item"; import { Skeleton } from "@mui/material"; import { nanoid } from "nanoid"; import styles from "./video.module.less"; import { fetchVideohandler, PickVideoRouterImageCardType } from "./tools"; -import { useAppDispatch } from "@store/hooks"; -import { changeLoading } from "@store/loading/index"; -import { useVideoLoading } from "./hooks"; +import { useChangeVideoLoading, useVideoShowState } from "./hooks"; import { useScreenSize } from "@components/proview/screenSize"; -type VideoListType = (VideoRouterImageCardType & { id: string })[]; +export type VideoListType = (VideoRouterImageCardType & { id: string })[]; /** * @description 该组件负责渲染视频图片的瀑布流 */ export default function VideoMasonry(props: VideoRouterMasonryType) { const [lists, setLists] = useState([]); - const dispatch = useAppDispatch(), - handerChangeLoading = (state: boolean) => - dispatch(changeLoading({ stateName: "videoIsLoading", Tostate: state })); + const { handerChangeLoading } = useChangeVideoLoading(); useEffect(() => { setLists([]); handerChangeLoading(true); @@ -29,7 +25,7 @@ export default function VideoMasonry(props: VideoRouterMasonryType) { ...lists, ...data.map((item, index) => { const itemRes = PickVideoRouterImageCardType(item); - if (index === data.length - 3) { + if (index === data.length - 5) { return { ...itemRes, id: nanoid(4), @@ -52,13 +48,21 @@ export default function VideoMasonry(props: VideoRouterMasonryType) { ); } +const LoadingItems: FC<{ length?: number }> = ({ length = 20 }) => ( + <> + {Array.from({ length }, (value, key) => ( + + + + ))} + +); + const VideoContent: FC<{ data: VideoListType }> = memo((props) => { - const { lg, md, sm } = useScreenSize(), - rightLen = (lg ? (md ? (sm ? 0 : 2) : 4) : 6) + 1; - const data = props.data, - loading = useVideoLoading(), - isEmpty = data.length < 1, - shouldNoShowHeroLine = data.length < rightLen + 1 + 10; + const { loading, rightLen, isEmpty, shouldNoShowHeroLine } = + useVideoShowState(props.data); + const { sm } = useScreenSize(); + const data = props.data; const heroItem = data.slice(0, 1), rightItem = data.slice(1, rightLen), resItems = shouldNoShowHeroLine ? data : data.slice(rightLen); @@ -100,11 +104,7 @@ const VideoContent: FC<{ data: VideoListType }> = memo((props) => { }} > {loading ? ( - Array.from({ length: rightLen - 1 }, (value, key) => ( - - - - )) + ) : shouldNoShowHeroLine ? ( <> ) : ( @@ -116,35 +116,42 @@ const VideoContent: FC<{ data: VideoListType }> = memo((props) => { )} - - {loading ? ( - Array.from({ length: 20 }, (value, key) => ( - - - - )) - ) : isEmpty ? ( - - ) : ( - resItems.map((item) => ) - )} - + ); }); + +export const NormalVideoRes: FC<{ + loading: boolean; + isEmpty: boolean; + data: VideoListType; +}> = ({ loading, isEmpty, data }) => ( + + {loading ? ( + + ) : isEmpty ? ( + + ) : ( + data.map((item) => ) + )} + +); + const EmptyItem = () => (
暂无数据
); diff --git a/src/store/loading/index.ts b/src/store/loading/index.ts index e26d5c9..86564b7 100644 --- a/src/store/loading/index.ts +++ b/src/store/loading/index.ts @@ -1,4 +1,5 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { useAppDispatch } from "@store/hooks"; import type { RootState } from ".."; @@ -11,6 +12,10 @@ export interface LoadingState { * @description 图片接口是否正在获取 */ photoIsloading: boolean; + /** + *@description 搜索接口是否正在获取 + */ + searchIsloading: boolean; } interface IchangeLoadingItem { stateName: keyof LoadingState; @@ -20,6 +25,7 @@ interface IchangeLoadingItem { const initialState: LoadingState = { videoIsLoading: true, photoIsloading: true, + searchIsloading: true, }; export const LoadingSlice = createSlice({ name: "loading", @@ -50,14 +56,23 @@ export const LoadingSlice = createSlice({ }, }, }); - -const selectLoadingState = (state: RootState, name: keyof LoadingState) => +type LoadingTypes = keyof LoadingState; +const selectLoadingState = (state: RootState, name: LoadingTypes) => state.loading[name]; export const { changeLoading, changeLoadingCauseByUrl } = LoadingSlice.actions; export const selectVideoLoadingState = (state: RootState) => selectLoadingState(state, "videoIsLoading"), selectPhotoLoadingState = (state: RootState) => - selectLoadingState(state, "photoIsloading"); + selectLoadingState(state, "photoIsloading"), + selectSearchingState = (state: RootState) => + selectLoadingState(state, "searchIsloading"); export default LoadingSlice.reducer; + +export const useChangeLoading = (name: LoadingTypes) => { + const dispatch = useAppDispatch(), + handerChangeLoading = (state: boolean) => + dispatch(changeLoading({ stateName: name, Tostate: state })); + return { handerChangeLoading }; +};