parent
14a5be4f00
commit
58cf24abd3
@ -0,0 +1,30 @@
|
||||
import style from './index.module.css';
|
||||
import Journal from './widget/Journal';
|
||||
import NoData from './widget/NoData';
|
||||
import Single from './widget/Single';
|
||||
|
||||
interface IProps {
|
||||
journalData: JournalInfo[];
|
||||
songData: SongInfo[];
|
||||
className?: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function SearchDropdown({ journalData, songData, className, onClose }: IProps) {
|
||||
const haveData = journalData.length || songData.length;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative w-747px h-fit min-h-162px max-h-747px rounded-10px mt-10px pt-24px pb-40px pl-36px pr-18px border-1px border-#e1e1e1 bg-base z-11 overflow-auto ${style.dropdownScrollbar} ${className}`}
|
||||
>
|
||||
{haveData ? (
|
||||
<div className="h-fit">
|
||||
{!!songData.length && <Single data={songData} />}
|
||||
{!!journalData.length && <Journal data={journalData} onClose={onClose} />}
|
||||
</div>
|
||||
) : (
|
||||
<NoData className="w-full h-160px" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
interface IProps {
|
||||
className?: string;
|
||||
onInput: (value: string) => void;
|
||||
onFocus: () => void;
|
||||
}
|
||||
|
||||
import IconSearch from './widget/IconSearch';
|
||||
|
||||
export default function SearchInput({ className, onInput, onFocus }: IProps) {
|
||||
return (
|
||||
<div className={`relative w-208px h-38px border-1px border-#fff ${className}`}>
|
||||
<input
|
||||
className="w-full h-full px-45px text-13px leading-38px text-#000/95 bg-transparent outline-none rounded-47px"
|
||||
onInput={(e: any) => onInput(e.target.value)}
|
||||
onFocus={onFocus}
|
||||
style={{ boxShadow: '0px 6px 34px 0px rgba(0, 0, 0, 0.1)' }}
|
||||
/>
|
||||
|
||||
<IconSearch className="absolute top-8px left-16px" />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import Dropdown from './Dropdown';
|
||||
import Input from './Input';
|
||||
|
||||
import { apiSearchJournal, apiSearchSong } from '@/services';
|
||||
|
||||
interface IProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Search({ className }: IProps) {
|
||||
const [value, setValue] = useState<string>('');
|
||||
const [journalData, setJournalData] = useState<JournalInfo[]>([]);
|
||||
const [songData, setSongData] = useState<SongInfo[]>([]);
|
||||
const [showDropDown, setShowDropDown] = useState<boolean>(false);
|
||||
const searchRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleInput: (value: string) => void = (value) => {
|
||||
setShowDropDown(!!value.trim());
|
||||
|
||||
if (value.trim()) {
|
||||
setValue(value);
|
||||
handleSearch(value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async (keyword: string) => {
|
||||
const [journalRes, songRes] = await Promise.all([
|
||||
apiSearchJournal({ keyword, pageNum: 1, pageSize: 20 }),
|
||||
apiSearchSong({ keyword, pageNum: 1, pageSize: 20 }),
|
||||
]);
|
||||
|
||||
if (journalRes.code === 200) {
|
||||
setJournalData(journalRes.data.rows);
|
||||
} else {
|
||||
setJournalData([]);
|
||||
}
|
||||
|
||||
if (songRes.code === 200) {
|
||||
setSongData(songRes.data.rows);
|
||||
} else {
|
||||
setSongData([]);
|
||||
}
|
||||
};
|
||||
|
||||
// input 聚焦时,展示下拉框
|
||||
const handleInputFocus = () => {
|
||||
if (value.trim()) setShowDropDown(true);
|
||||
};
|
||||
|
||||
const handleClickOutside = (e: Event) => {
|
||||
if (searchRef.current && !searchRef.current.contains(e.target as Node)) {
|
||||
setShowDropDown(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={searchRef} className={`w-208px h-38px ${className}`}>
|
||||
<Input onInput={debounce((value) => handleInput(value), 200)} onFocus={handleInputFocus} />
|
||||
|
||||
{showDropDown && (
|
||||
<Dropdown journalData={journalData} songData={songData} onClose={() => setShowDropDown(false)} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
.dropdownScrollbar::-webkit-scrollbar {
|
||||
width: 4px; /* 滚动条宽度 */
|
||||
}
|
||||
|
||||
/* 滚动条轨道透明度 */
|
||||
.dropdownScrollbar::-webkit-scrollbar-track {
|
||||
margin: 24px 0 40px 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 滚动条滑块颜色 */
|
||||
.dropdownScrollbar::-webkit-scrollbar-thumb {
|
||||
border-radius: 5px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
/* 滚动条滑块 hover */
|
||||
.dropdownScrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
export default function IconSearch({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 22 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.9999 4.1189C7.17804 4.1189 4.0791 7.21785 4.0791 11.0397C4.0791 14.8616 7.17804 17.9606 10.9999 17.9606C12.5637 17.9606 14.0064 17.4418 15.1654 16.5669L16.7472 18.1487C17.0157 18.4172 17.451 18.4172 17.7195 18.1487C17.988 17.8803 17.988 17.445 17.7195 17.1765L16.1763 15.6333C17.2616 14.4113 17.9207 12.8024 17.9207 11.0397C17.9207 7.21785 14.8218 4.1189 10.9999 4.1189ZM5.4541 11.0397C5.4541 7.97724 7.93744 5.4939 10.9999 5.4939C14.0624 5.4939 16.5457 7.97724 16.5457 11.0397C16.5457 14.1022 14.0624 16.5856 10.9999 16.5856C7.93744 16.5856 5.4541 14.1022 5.4541 11.0397Z"
|
||||
fill="black"
|
||||
fillOpacity="0.95"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { MiniJournalCardList } from '@/components';
|
||||
|
||||
interface IProps {
|
||||
data: JournalInfo[];
|
||||
className?: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function Journal({ data, className, onClose }: IProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<p className="mt-30px">期刊</p>
|
||||
<MiniJournalCardList list={data} className="w-full mt-18px" onClick={onClose} />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
interface IProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function NoData({ className }: IProps) {
|
||||
return (
|
||||
<div className={`flex justify-center items-center ${className}`}>
|
||||
<p className="text-#000/70 text-14px leading-21px">没有搜索到内容</p>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { SongCardList } from '@/components';
|
||||
|
||||
interface IProps {
|
||||
data: SongInfo[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Single({ data, className }: IProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<p>单曲</p>
|
||||
<SongCardList
|
||||
className="w-full mt-6px ml-[-16px]"
|
||||
clickType="playPush"
|
||||
songList={data}
|
||||
listInfo={{ type: 'collectSingle', id: null }}
|
||||
collectType="playing"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in new issue