feat: improve search

pull/8/head
fantasticit 2022-03-12 10:24:34 +08:00
parent c017d5d351
commit b13dd26d9a
1 changed files with 127 additions and 87 deletions

View File

@ -1,83 +1,33 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import Link from "next/link"; import Link from 'next/link';
import Router from "next/router"; import Router from 'next/router';
import { Typography, Button, Modal, Input } from "@douyinfe/semi-ui"; import { Typography, Button, Modal, Input, Spin } from '@douyinfe/semi-ui';
import { IconSearch } from "components/icons"; import { IconSearch as SemiIconSearch } from '@douyinfe/semi-icons';
import { IDocument } from "@think/domains"; import { IconSearch } from 'components/icons';
import { useRecentDocuments } from "data/document"; import { IDocument } from '@think/domains';
import { useToggle } from "hooks/useToggle"; import { useRecentDocuments } from 'data/document';
import { searchDocument } from "services/document"; import { useToggle } from 'hooks/useToggle';
import { Empty } from "components/empty"; import { useAsyncLoading } from 'hooks/useAsyncLoading';
import { DataRender } from "components/data-render"; import { searchDocument } from 'services/document';
import { LocaleTime } from "components/locale-time"; import { Empty } from 'components/empty';
import { DocumentStar } from "components/document/star"; import { DataRender } from 'components/data-render';
import { IconDocumentFill } from "components/icons/IconDocumentFill"; import { LocaleTime } from 'components/locale-time';
import styles from "./index.module.scss"; import { DocumentStar } from 'components/document/star';
import { IconDocumentFill } from 'components/icons/IconDocumentFill';
import styles from './index.module.scss';
const { Text } = Typography; const { Text } = Typography;
export const Search = () => { const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
const [visible, toggleVisible] = useToggle(false);
const { data: recentDocs, loading, error } = useRecentDocuments();
const [keyword, setKeyword] = useState("");
const [searchDocs, setSearchDocs] = useState<IDocument[]>([]);
const data = useMemo(
() => (searchDocs.length ? searchDocs : recentDocs),
[searchDocs.length, recentDocs]
);
const search = useCallback(() => {
searchDocument(keyword).then((res) => {
setSearchDocs(res);
});
}, [keyword]);
useEffect(() => {
const fn = () => {
toggleVisible(false);
};
Router.events.on("routeChangeStart", fn);
return () => {
Router.events.off("routeChangeStart", fn);
};
}, []);
return (
<>
<Button
type="tertiary"
theme="borderless"
icon={<IconSearch />}
onClick={toggleVisible}
/>
<Modal
visible={visible}
title="文档搜索"
footer={null}
onCancel={toggleVisible}
>
<div style={{ paddingBottom: 24 }}>
<Input
placeholder={"搜索文档"}
size="large"
value={keyword}
onChange={(val) => setKeyword(val)}
onEnterPress={search}
/>
<DataRender
loading={loading}
error={error}
normalContent={() => {
return ( return (
<div className={styles.itemsWrap}> <div className={styles.itemsWrap}>
{data.length ? ( {data.length ? (
data.map((doc) => { data.map((doc) => {
return ( return (
<div className={styles.itemWrap}> <div className={styles.itemWrap} key={doc.id}>
<Link <Link
href={{ href={{
pathname: "/wiki/[wikiId]/document/[documentId]", pathname: '/wiki/[wikiId]/document/[documentId]',
query: { query: {
wikiId: doc.wikiId, wikiId: doc.wikiId,
documentId: doc.id, documentId: doc.id,
@ -97,8 +47,7 @@ export const Search = () => {
<Text size="small" type="tertiary"> <Text size="small" type="tertiary">
{doc.createUser && {doc.createUser && doc.createUser.name} {' '}
doc.createUser.name} {" "}
<LocaleTime date={doc.updatedAt} timeago /> <LocaleTime date={doc.updatedAt} timeago />
</Text> </Text>
</div> </div>
@ -112,12 +61,103 @@ export const Search = () => {
); );
}) })
) : ( ) : (
<Empty message="最近访问的文档会出现在此处" /> <Empty message="暂无搜索结果" />
)} )}
</div> </div>
); );
}} };
export const Search = () => {
const [visible, toggleVisible] = useToggle(false);
const { data: recentDocs } = useRecentDocuments();
const [searchApi, loading] = useAsyncLoading(searchDocument, 10);
const [keyword, setKeyword] = useState('');
const [error, setError] = useState(null);
const [searchDocs, setSearchDocs] = useState<IDocument[]>([]);
const search = useCallback(() => {
setError(null);
searchApi(keyword)
.then((res) => {
setSearchDocs(res);
})
.catch((err) => {
setError(err);
});
}, [searchApi, keyword]);
useEffect(() => {
const fn = () => {
toggleVisible(false);
};
Router.events.on('routeChangeStart', fn);
return () => {
Router.events.off('routeChangeStart', fn);
};
}, [toggleVisible]);
return (
<>
<Button
type="tertiary"
theme="borderless"
icon={<IconSearch />}
onClick={toggleVisible}
/> />
<Modal
visible={visible}
title="文档搜索"
footer={null}
onCancel={toggleVisible}
style={{
maxWidth: '96vw',
}}
>
<div style={{ paddingBottom: 24 }}>
<div>
<Input
placeholder={'搜索文档'}
size="large"
value={keyword}
onChange={(val) => {
setSearchDocs([]);
setKeyword(val);
}}
onEnterPress={search}
suffix={
<SemiIconSearch
onClick={search}
style={{ cursor: 'pointer' }}
/>
}
showClear
/>
</div>
<div style={{ maxHeight: '70vh', overflow: 'auto' }}>
<DataRender
loading={loading}
loadingContent={
<div
style={{
paddingTop: 30,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Spin />
</div>
}
error={error}
normalContent={() => <List data={searchDocs} />}
/>
<div style={{ marginTop: 16 }}>
<Text type="tertiary">访</Text>
<List data={recentDocs} />
</div>
</div>
</div> </div>
</Modal> </Modal>
</> </>