diff --git a/src/routers/layout/search/index.tsx b/src/routers/layout/search/index.tsx index f29a734..f94e7cb 100644 --- a/src/routers/layout/search/index.tsx +++ b/src/routers/layout/search/index.tsx @@ -3,11 +3,6 @@ import PCSearch from "./pc"; import PhoneSearch from "./phone"; // todo 增加高级搜索以及手机端的搜索 export default function Search() { - const navigate = useNavigate(); - const handlerSubmit = () => { - const value = (document.getElementById("q") as HTMLInputElement).value; - navigate(`/search?tag=${value}`); - }; return ( <>
diff --git a/src/routers/layout/search/pc/context.tsx b/src/routers/layout/search/pc/context.tsx new file mode 100644 index 0000000..e467a6b --- /dev/null +++ b/src/routers/layout/search/pc/context.tsx @@ -0,0 +1,8 @@ +import { createContext, useContext } from "react"; +type InputWrapperContextType = { + anchorEl: HTMLElement | null; +}; +export const InputWrapperContext = createContext({ + anchorEl: null, +}); +export const useGetInputWrapper = () => useContext(InputWrapperContext); diff --git a/src/routers/layout/search/pc/data.ts b/src/routers/layout/search/pc/data.ts new file mode 100644 index 0000000..1a9839c --- /dev/null +++ b/src/routers/layout/search/pc/data.ts @@ -0,0 +1,28 @@ +import { phoneMoreNameAndFaceUrls } from "../phone/url"; + +export type renderSuggestItemLists = { label: string; value: string }[]; + +//tag列 +export const pcSuggestTagLists: renderSuggestItemLists = [ + "莞儿睡不醒", + "露早GOGO", + "米诺高分少女", + "虞莫MOMO", + "柚恩不加糖", + "EOE组合", + "早莞在一起", + "莞柚引力", + "一莞米", + "虞舟唱莞", + "早柚", + "西米露", + "早有虞谋", + "米哈柚", + "虞香柚丝", + "米虞说的道理", + "虞米之乡", +].map((item) => ({ label: item, value: item })); + +//up列 +export const pcSuggestNameLists: renderSuggestItemLists = + phoneMoreNameAndFaceUrls; diff --git a/src/routers/layout/search/pc/index.tsx b/src/routers/layout/search/pc/index.tsx index 395e89c..09f4637 100644 --- a/src/routers/layout/search/pc/index.tsx +++ b/src/routers/layout/search/pc/index.tsx @@ -1,18 +1,14 @@ -import React, { useRef } from "react"; import { useSearchFocus } from "@components/proview/searchFocus"; -import { FC, useEffect } from "react"; +import { useEffect } from "react"; import SelectModeCom from "./selectModeNav"; import Search from "./search"; -export default function NormalSearch() { +export default function PCSearch() { const { focused, bind } = useSearchFocus(), { onBlur } = bind; - const contentRef = useRef(); useEffect(() => { if (focused) { - window.addEventListener("scroll", onBlur, { - once: true, - }); + window.addEventListener("scroll", onBlur, { once: true }); } }, [focused]); return ( diff --git a/src/routers/layout/search/pc/pc.module.less b/src/routers/layout/search/pc/pc.module.less index f07eef7..d828f32 100644 --- a/src/routers/layout/search/pc/pc.module.less +++ b/src/routers/layout/search/pc/pc.module.less @@ -51,7 +51,7 @@ span { padding-bottom: 2px; font-size: 14px; - font-weight: 400; + font-weight: 600; } span+input { @@ -70,4 +70,12 @@ border-radius: 40px; // transition: all 300ms ease; } +} + +.commonRounded { + border-radius: 40px; +} + +.suggestMaxHeight { + max-height: calc(100vh - 200px); } \ No newline at end of file diff --git a/src/routers/layout/search/pc/search.tsx b/src/routers/layout/search/pc/search.tsx index bf3129e..5abb2a1 100644 --- a/src/routers/layout/search/pc/search.tsx +++ b/src/routers/layout/search/pc/search.tsx @@ -1,22 +1,17 @@ import { useSearchFocus } from "@components/proview/searchFocus"; +import HighlightOffIcon from "@mui/icons-material/HighlightOff"; import { useURLParams } from "@utils/hooks/url"; import SearchIcon from "@mui/icons-material/Search"; import styles from "../search.module.less"; import pcStyles from "./pc.module.less"; import { useBackRef, useSearchMode } from "./hooks"; import { useNavigate } from "react-router-dom"; -import { FC, forwardRef, useRef } from "react"; +import React, { FC, forwardRef, ReactElement, useRef } from "react"; export default function Search() { - const inputRef = useRef(null); + const contentRef = useRef(null); const { focused, bind } = useSearchFocus(); - const { onFocus, onBlur } = bind; + const { onFocus } = bind; const { searchMode } = useSearchMode(); - const navigate = useNavigate(), - handlerSubmit = () => { - const value = inputRef.current!.value; - navigate(`/search?tag=${value}`); - onBlur(); - }; return ( <> {/* 包裹层 */} @@ -31,16 +26,39 @@ export default function Search() { className={`${styles["search-box"]} ${ focused ? styles["focus"] : "py-1" } duration-300 relative`} + ref={contentRef} > - {searchMode === "搜索tag" && ( - - )} - {searchMode === "搜索更多" && } + + {searchMode === "搜索tag" && } + {searchMode === "搜索更多" && } +
); } + +const CloseSvg: FC<{ onClick: () => void; style?: React.CSSProperties }> = ( + props +) => ( +
+ + + + +
+); + //通用按钮 const SearchButton: FC<{ onSubmit?: () => void }> = ({ onSubmit }) => { const { focused } = useSearchFocus(); @@ -61,30 +79,58 @@ const SearchButton: FC<{ onSubmit?: () => void }> = ({ onSubmit }) => { ); }; +import { usePopoverConfig } from "@utils/hooks/popover"; +import { + addPCNameHistory, + addPCTagHistory, + PCNameSuggest, + PCTagSuggest, +} from "./searchSuggest"; +import { InputWrapperContext } from "./context"; + //普通搜索 -const NormalSearch = forwardRef void }>( - function Search(props, ref) { - const [tag] = useURLParams(["tag"]); - return ( - <> - { - if (event.key === "Enter") { - props.onFinish(); - } - }} - /> - - - ); - } -); +const NormalSearch = () => { + const inputRef = useRef(null); + const { bind } = useSearchFocus(), + { onBlur } = bind; + const [tag] = useURLParams(["tag"]); + const { open, handleClick } = usePopoverConfig("target"); + const navigate = useNavigate(), + handlerNavigate = (value: string) => { + navigate(`/search?tag=${value}`); + onBlur(); + }, + handlerSubmit = () => { + const value = inputRef.current!.value; + handlerNavigate(value); + }, + handerHistorySubmit = (value: string) => { + handlerNavigate(value); + addPCTagHistory(value); + }; + return ( + <> + { + if (event.key === "Enter") { + handlerSubmit(); + } + }} + /> + + + + ); +}; //高级搜索 const AdvanceSearch = forwardRef(function Advance(props, ref) { @@ -94,15 +140,35 @@ const AdvanceSearch = forwardRef(function Advance(props, ref) { const nameRef = useRef(null), tagRef = useRef(null); const navigate = useNavigate(), + handlerNavigate = (tag: string, name: string) => { + navigate(`/search?tag=${tag}&name=${name}`); + onBlur(); + }, //@ts-ignore + getTagValue = () => tagRef.current!.getValue(), + //@ts-ignore + getNameValue = () => nameRef.current!.getValue(), handlerSubmit = () => { - //@ts-ignore - let tagValue = tagRef.current!.getValue(), - //@ts-ignore - nameValue = nameRef.current!.getValue(); - navigate(`/search?tag=${tagValue}&name=${nameValue}`); + navigate(`/search?tag=${getTagValue()}&name=${getNameValue()}`); onBlur(); + }, + handlerTagHistorySubmit = (tag: string) => { + handlerNavigate(tag, getNameValue()); + addPCTagHistory(tag); + }, + handlerNameHistorySubmit = (name: string) => { + handlerNavigate(getTagValue(), name); + addPCNameHistory(name); + }; + const tagSuggestOption = usePopoverConfig("target"), + nameSuggestOption = usePopoverConfig("target"), + tagItemCallback = (event: React.MouseEvent) => { + nameSuggestOption.handleClose(); + tagSuggestOption.handleClick(event); + }, + nameItemCallback = (event: React.MouseEvent) => { + tagSuggestOption.handleClose(); + nameSuggestOption.handleClick(event); }; - // tag.露早~name.L姓飞行玩家 return (
+ clickCallback={nameItemCallback} + > + + {/* 等待接口更新 */} {/* */} + clickCallback={tagItemCallback} + > + +
); }); @@ -131,22 +209,28 @@ type AdvanceItemProps = { title: string; inputDefaultValue: string; onSubmit: () => void; + children?: ReactElement | ReactElement[]; + clickCallback?: (event: React.MouseEvent) => void; }; const AdvanceSearchItem = forwardRef( (props, ref) => { const { inputRef, handerfocus } = useBackRef(ref); const { focused } = useSearchFocus(); - const inptValue = inputRef.current?.value, - showedTitle = focused ? props.title : inptValue ? inptValue : props.title; return ( <>
{ + handerfocus(); + if (props.clickCallback) { + props.clickCallback(event); + } + }} > - {showedTitle} + {props.title} ( } }} /> + {focused && ( + { + inputRef.current!.value = ""; + }} + style={{ + top: "50%", + right: "16px", + transform: "translateY(-12px)", + }} + /> + )} + + {props.children}
@@ -169,22 +267,26 @@ const AdvanceSearchLastItem = forwardRef< >((props, ref) => { const { inputRef, handerfocus } = useBackRef(ref); const { focused } = useSearchFocus(); - const inptValue = inputRef.current?.value, - showedTitle = focused ? props.title : inptValue ? inptValue : props.title; return ( <>
{ + handerfocus(); + if (props.clickCallback) { + props.clickCallback(event); + } + }} >
- {showedTitle} + {props.title} { if (event.key === "Enter") { @@ -192,6 +294,19 @@ const AdvanceSearchLastItem = forwardRef< } }} /> + {focused && ( + { + inputRef.current!.value = ""; + }} + style={{ + top: "50%", + right: "16px", + transform: "translateY(-12px)", + }} + /> + )} + {props.children}
diff --git a/src/routers/layout/search/pc/searchSuggest.tsx b/src/routers/layout/search/pc/searchSuggest.tsx new file mode 100644 index 0000000..ceb36b2 --- /dev/null +++ b/src/routers/layout/search/pc/searchSuggest.tsx @@ -0,0 +1,212 @@ +import { useSearchFocus } from "@components/proview/searchFocus"; +import HistoryToggleOffIcon from "@mui/icons-material/HistoryToggleOff"; +import TagIcon from "@mui/icons-material/Tag"; +import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew"; +import { Popper } from "@mui/material"; +import { Storage } from "@routers/layout/tools"; +import { useURLParams } from "@utils/hooks/url"; +import { FC, ReactElement, useEffect, useMemo, useState } from "react"; +import { useGetInputWrapper } from "./context"; +import styles from "./pc.module.less"; +import { + renderSuggestItemLists, + pcSuggestTagLists, + pcSuggestNameLists, +} from "./data"; +type PCTagSuggestProps = { + ClickCallback: (value: string) => void; +} & SuggestPopperProps; + +const PCTagStorage = new Storage("pcTagHistorys"); +export const addPCTagHistory = (value: string) => { + const oldHistory = PCTagStorage.getLocalStorage([]); + PCTagStorage.setLocalstorage([value, ...oldHistory.slice(0, 4)]); +}; +const usePCTagHistory = () => { + const local_history: renderSuggestItemLists = PCTagStorage.getLocalStorage( + [] + ).map((item) => ({ label: item, value: item })); + return local_history; +}; + +const useSuggestOption = (open: boolean) => { + const [fixedOpen, setOpen] = useState(false); + const { focused } = useSearchFocus(); + const handlerChangeOpen = useMemo(() => { + let flag = false; + return () => { + if (!flag) { + flag = true; + return setTimeout(() => { + setOpen(true); + }, 300); + } else { + setOpen(true); + } + }; + }, [focused]); + useEffect(() => { + //@ts-ignore + let setId; + if (focused && open) { + setId = handlerChangeOpen(); + } else { + clearTimeout(setId); + setOpen(false); + } + return () => { + //@ts-ignore + clearTimeout(setId); + }; + }, [focused, open]); + return [fixedOpen]; +}; + +export const PCTagSuggest: FC = ({ + open, + ClickCallback, +}) => { + const [tag] = useURLParams(["tag"]); + const history_tag = usePCTagHistory(); + const [fixedOpen] = useSuggestOption(open); + return ( + + + + 历史记录 + + } + dataList={history_tag} + feedbackMsg='暂无数据' + clickCallBack={ClickCallback} + activeValue={tag} + /> + + + tag + + } + dataList={pcSuggestTagLists} + clickCallBack={ClickCallback} + activeValue={tag} + /> + + ); +}; + +const PCNameStorage = new Storage("pcNameHistorys"); +export const addPCNameHistory = (value: string) => { + const oldHistory = PCNameStorage.getLocalStorage([]); + PCNameStorage.setLocalstorage([value, ...oldHistory.slice(0, 4)]); +}; +const usePCNameHistory = () => { + const local_history: renderSuggestItemLists = PCNameStorage.getLocalStorage( + [] + ).map((item) => ({ label: item, value: item })); + return local_history; +}; +export const PCNameSuggest: FC = ({ + open, + ClickCallback, +}) => { + const [name] = useURLParams(["name"]); + const history_name = usePCNameHistory(); + const [fixedOpen] = useSuggestOption(open); + return ( + + + + 历史记录 + + } + dataList={history_name} + feedbackMsg='暂无数据' + clickCallBack={ClickCallback} + activeValue={name} + /> + + + 作者 + + } + dataList={pcSuggestNameLists} + clickCallBack={ClickCallback} + activeValue={name} + /> + + ); +}; + +type SuggestPopperProps = { + open: boolean; + children?: ReactElement | ReactElement[]; +}; +const SuggestPopper: FC = (props) => { + const { anchorEl } = useGetInputWrapper(); + return ( + +
+ {props.children} +
+
+ ); +}; + +type SuggestItemProps = { + title: string | ReactElement; + dataList: renderSuggestItemLists; + feedbackMsg?: string; + activeValue: string; + clickCallBack: (value: string) => void; +}; +const SuggestItem: FC = ({ + title, + dataList, + clickCallBack, + activeValue, + feedbackMsg, +}) => { + const isActive = (value: string) => value === activeValue; + return ( +
+
+ {title} +
+
+ {dataList.map(({ label, value }, key) => ( + + ))} + {!dataList.length && ( +

+ {feedbackMsg} +

+ )} +
+
+ ); +}; diff --git a/src/routers/layout/search/phone/url.ts b/src/routers/layout/search/phone/url.ts index f365dbb..a8fdf56 100644 --- a/src/routers/layout/search/phone/url.ts +++ b/src/routers/layout/search/phone/url.ts @@ -19,8 +19,9 @@ export const idolNameAndFaceUrls: NameAndFaceUrlsType = [ ]; export const lubozuFaceUrl = `https://i0.hdslb.com/bfs/face/4fb5beac7b6eff7981897176430df514b7201556.jpg@240w_240h_1c_1s.webp`; - +const eoeGroupFaceUrl = `https://i0.hdslb.com/bfs/face/f0ac506bbfa4e4ce09729d424d28d2383e721ade.jpg@240w_240h_1c_1s.webp`; export const phoneMoreNameAndFaceUrls: NameAndFaceUrlsType = [ { label: "录播组", url: lubozuFaceUrl, value: "哎呀米诺录播组" }, ...idolNameAndFaceUrls, + { label: "EOE组合", url: eoeGroupFaceUrl, value: "EOE组合" }, ]; diff --git a/src/routers/search/index.tsx b/src/routers/search/index.tsx index 5769292..7fd2c8c 100644 --- a/src/routers/search/index.tsx +++ b/src/routers/search/index.tsx @@ -44,7 +44,7 @@ export default function SearchPage() { setLists([]); handerChangeLoading(true); function handlerSetList( - datas: UnPromisify>, + datas: UnPromisify>["data"], nextPage: number ) { setLists((lists) => [ @@ -62,15 +62,18 @@ export default function SearchPage() { return { ...itemRes, id: nanoid(4) }; }), ]); + handerChangeLoading(false); } const fetchHandler = async (page: number = 1) => { - const data = await fetchVideohandler(page, { + const { data, canceledByAxios } = await fetchVideohandler(page, { order, q: `tag.${tagParam}~name.${nameParam}`, }); - handlerSetList(data, page + 1); + if (!canceledByAxios) { + handlerSetList(data, page + 1); + } }; - fetchHandler(1).then(() => handerChangeLoading(false)); + fetchHandler(1); }, loaderData); const { loading, isEmpty } = useSearchShowState(lists), propsObj = { loading, isEmpty, data: lists }; diff --git a/src/routers/video/masonry.tsx b/src/routers/video/masonry.tsx index 9743efa..f6d7490 100644 --- a/src/routers/video/masonry.tsx +++ b/src/routers/video/masonry.tsx @@ -20,7 +20,7 @@ export default function VideoMasonry(props: VideoRouterMasonryType) { handerChangeLoading(true); // 在内部定义fetchHandler,保证拿到的是同步的 const fetchHandler = async (page: number = 1) => { - const data = await fetchVideohandler(page, props); + const { data, canceledByAxios } = await fetchVideohandler(page, props); setLists((lists) => [ ...lists, ...data.map((item, index) => { @@ -38,8 +38,11 @@ export default function VideoMasonry(props: VideoRouterMasonryType) { return { ...itemRes, id: nanoid(4) }; }), ]); + if (!canceledByAxios) { + handerChangeLoading(false); + } }; - fetchHandler(1).then(() => handerChangeLoading(false)); + fetchHandler(1); }, [props]); return (
diff --git a/src/routers/video/tools.ts b/src/routers/video/tools.ts index ee6cd2d..8322c5e 100644 --- a/src/routers/video/tools.ts +++ b/src/routers/video/tools.ts @@ -2,9 +2,18 @@ import { VideoRouterMasonryType, VideoRouterImageCardType } from "./videotype"; import { fetchVideos } from "@utils/fetch/index"; import message from "@components/message"; import { Pick } from "@utils/index"; +import { RFetchVideoRes } from "@utils/fetch/fetchtype"; -export const fetchVideohandler = async ( - page: number = 1, +type fetchVideohandlerType = ( + page: number, + props: VideoRouterMasonryType +) => Promise<{ + data: RFetchVideoRes["data"]["result"]; + canceledByAxios: boolean; +}>; + +export const fetchVideohandler: fetchVideohandlerType = async ( + page = 1, props: VideoRouterMasonryType ) => { const res = await fetchVideos({ @@ -16,11 +25,14 @@ export const fetchVideohandler = async ( if (res.code !== 0) { res.code === 400 && message.info("参数错误,请尝试其他tag"); res.code === 500 && message.info(res.message); - return []; + if (res.code === 403) { + return { data: [], canceledByAxios: true }; + } + return { data: [], canceledByAxios: false }; } else if (res.data.result.length < 1) { message.info("没有更多数据了,请尝试其他tag"); } - return res.data.result; + return { data: res.data.result, canceledByAxios: false }; }; export function PickVideoRouterImageCardType< diff --git a/src/utils/hooks/match.ts b/src/utils/hooks/match.ts index 252268d..95a9eca 100644 --- a/src/utils/hooks/match.ts +++ b/src/utils/hooks/match.ts @@ -10,14 +10,6 @@ export const useScreenMatchSize = (size: Breakpoint) => { const matchSize = useMediaQuery(theme.breakpoints.down(size)); return matchSize; }; -//个人主页头像大小 -`@240w_240h_1c_1s.webp`; -//移动平台首页图片大小 -`@480w_270h_1c`; -//电脑平台首页图片大小 -`@672w_378h_1c_!web-search-common-cover`; -//首页up头像大小 -`@96w_96h_1s.webp`; export const useBodyScrollHide = (hidden: boolean) => { useEffect(() => { diff --git a/src/utils/hooks/popover.ts b/src/utils/hooks/popover.ts index 46a054c..6744e65 100644 --- a/src/utils/hooks/popover.ts +++ b/src/utils/hooks/popover.ts @@ -1,10 +1,16 @@ import React from "react"; -export function usePopoverConfig() { +export function usePopoverConfig( + target: "target" | "currentTaarget" = "currentTaarget" +) { const [anchorEl, setAnchorEl] = React.useState(null); - const handleClick = (event: React.MouseEvent) => { - //@ts-ignore - setAnchorEl(event.currentTarget); + const handleClick = (event: React.MouseEvent) => { + if (target === "currentTaarget") { + setAnchorEl(event.currentTarget); + } else { + //@ts-ignore + setAnchorEl(event.target); + } }, handleClose = () => { setAnchorEl(null);