Skip to content

Commit

Permalink
feat: ✨ 搜索
Browse files Browse the repository at this point in the history
  • Loading branch information
zhuba-Ahhh committed Oct 19, 2024
1 parent 02542dc commit c75b0b2
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 29 deletions.
27 changes: 22 additions & 5 deletions src/components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import { SearchIcon } from 'assets/svg';
import React, { ChangeEvent, useEffect, useState } from 'react';
import { http, truncateString } from 'utils';
import { debounce } from 'lodash-es'; // 导入 debounce 方法
import { debounce } from 'lodash-es';
import { useNavigate } from 'react-router-dom'; // 导入 debounce 方法

export type SearchInputProps = {
placeholder?: string; // 输入框的占位符文本
Expand Down Expand Up @@ -68,6 +69,7 @@ const SearchInput: React.FC<SearchInputProps> = ({
className = '',
style = {},
}) => {
const navigate = useNavigate();
const [suggestions, setSuggestions] = useState<Array<string>>([]);
const [books, setBooks] = useState<Array<string>>([]);
const [authors, setAuthors] = useState<Array<string>>([]);
Expand Down Expand Up @@ -102,7 +104,6 @@ const SearchInput: React.FC<SearchInputProps> = ({
};

useEffect(() => {
console.log(inputValue, 'inputValue');
const debouncedFetchSearch = debounce(() => {
if (inputValue) {
fetchSearchResults(inputValue);
Expand Down Expand Up @@ -137,19 +138,35 @@ const SearchInput: React.FC<SearchInputProps> = ({
placeholder={currentPlaceholder || placeholder}
onChange={handleInputChange} // 使用防抖后的函数
onFocus={() => setShowSuggestions(true)}
onBlur={() => setShowSuggestions(false)}
onBlur={() =>
setTimeout(() => {
setShowSuggestions(false);
}, 500)
}
value={inputValue}
style={{ width: '100%' }} // 输入框宽度100%
/>
<SearchIcon />
<div
className="cursor-pointer"
onClick={(e) => {
e.preventDefault();
console.log(1111);
}}
>
<SearchIcon />
</div>
</label>
{showSuggestions && (
<SuggestionsList
inputValue={inputValue}
suggestions={suggestions}
books={books}
authors={authors}
setInputValue={(newValue: string) => setInputValue(newValue)}
setInputValue={(newValue: string) => {
setInputValue(newValue);
newValue && navigate(`search?keyword=${decodeURIComponent(newValue)}`);
setShowSuggestions(false);
}}
/>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/utils/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const truncateString = (
maxLength: number = 12,
trailing: string = '...'
): string => {
if (str.length > maxLength) {
if (str?.length > maxLength) {
return str.substring(0, maxLength - trailing.length) + trailing;
} else {
return str;
Expand Down
74 changes: 51 additions & 23 deletions src/views/SearchView.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { http } from '../utils/request';
import { uuid } from 'zhuba-tools';
import { Author } from 'assets/svg';
import { COVER_BASE_URL } from 'common/const';
import { useLocation } from 'react-router-dom';

export interface SearchBookData {
datas?: Datas;
Expand Down Expand Up @@ -86,20 +87,27 @@ export interface RecDataList {
}

const SearchPage = () => {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const initialKeyword = queryParams.get('keyword') || ''; // 从路由参数获取初始值
const [searchResults, setSearchResults] = useState<List[]>([]);
const [loading, setLoading] = useState(false);
const [inputValue, setInputValue] = useState(initialKeyword); // 新增状态管理输入框值

const handleSearch = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const searchTerm = formData.get('search') as string;
useEffect(() => {
setInputValue(initialKeyword); // 初始加载时更新输入框值
}, [initialKeyword]);

if (!searchTerm.trim()) return;
useEffect(() => {
if (initialKeyword) {
fetchData(initialKeyword); // 值变化时请求数据
}
}, [initialKeyword]);

setLoading(true);
const fetchData = async (keyword: string) => {
try {
const response = await http.get<SearchBookData>(
`/search?keyword=${encodeURIComponent(searchTerm)}`
`/search?keyword=${encodeURIComponent(keyword)}`
);
console.log(' [ response ]-32-「views/SearchView.tsx」 ', response);
setSearchResults(response.datas?.list || []);
Expand All @@ -111,41 +119,58 @@ const SearchPage = () => {
}
};

const handleSearch = async (e?: React.FormEvent<HTMLFormElement>) => {
e && e?.preventDefault();
if (inputValue.trim()) {
setLoading(true);
fetchData(inputValue); // 点击搜索按钮时请求数据
}
};

return (
<div className="mx-auto p-8">
<div className="mx-auto p-8 bg-gray-100 rounded-lg shadow-md">
<form onSubmit={handleSearch} className="flex mb-8 justify-between w-full">
<input
type="text"
name="search"
placeholder="输入书名搜索"
className="input input-bordered w-full mr-8"
style={{ outlineOffset: 0 }}
className="input input-bordered w-full mr-4 p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSearch();
}
}}
/>
<button type="submit" className="btn px-6">
<button
type="submit"
className="btn px-6 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition duration-200"
>
搜索
</button>
</form>

{loading ? (
<p className="text-center">正在搜索...</p>
) : searchResults && searchResults?.length > 0 ? (
<p className="text-center text-blue-600">正在搜索...</p>
) : searchResults && searchResults.length > 0 ? (
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{searchResults.map((result) => (
<div
key={uuid()}
className="card card-compact hover:shadow-lg hover:rounded-lg transition-shadow duration-300 cursor-pointer"
className="card card-compact hover:shadow-lg hover:rounded-lg transition-shadow duration-300 cursor-pointer bg-white rounded-lg w-full sm:w-auto max-w-xs" // 添加了最大宽度限制
>
<img
src={`${COVER_BASE_URL}${result.coverUrl}`}
alt={result.name}
className="w-full h-64 object-contain mt-4" // 增加上边距
className="w-full h-64 object-cover rounded-t-lg"
/>
<div className="card-body flex-col items-start p-4">
<h2 className="text-xl font-semibold mb-2">
<span dangerouslySetInnerHTML={{ __html: result.name }} />
</h2>
<p>
{result.authorName} | {result.catePName} | {result.totalWord}
<p className="text-gray-700">
<span dangerouslySetInnerHTML={{ __html: result.authorName }} /> |
{result.catePName} | {result.totalWord}
</p>
<p
className="text-sm text-gray-600 line-clamp-4 mb-3"
Expand All @@ -156,24 +181,27 @@ const SearchPage = () => {
href={`/directory?id=${result.bookId}`}
className="text-blue-600 hover:underline mr-4 cursor-pointer"
>
查看详情
目录
</a>
<a
href={`/chapter?id=${result.bookId}&chapterId=${result.chapterId}`}
className="text-blue-600 hover:underline mr-4 cursor-pointer"
>
最新章节{result.chapterName}
最新{result.chapterName}
</a>
</div>
<div className="flex justify-between items-center w-full mt-3 ">
<div className="flex items-center space-x-1 text-gray-500">
<Author />
<p className="text-sm truncate">{result.authorName}</p>
<p
className="text-sm truncate"
dangerouslySetInnerHTML={{ __html: result.authorName }}
></p>
</div>
<span
role="button"
tabIndex={0}
className="badge badge-outline text-sm px-2 py-1 bg-secondary-100 text-secondary-700 rounded-full cursor-pointer"
className="badge badge-outline text-sm px-2 py-1 bg-blue-500 text-white rounded-full cursor-pointer hover:bg-blue-600 transition duration-200 overflow-hidden whitespace-nowrap text-ellipsis"
dangerouslySetInnerHTML={{ __html: result.keyword }}
></span>
</div>
Expand Down

0 comments on commit c75b0b2

Please sign in to comment.