mirror of https://github.com/fantasticit/think.git
feat: improve search
parent
c017d5d351
commit
b13dd26d9a
|
@ -1,47 +1,102 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Router from "next/router";
|
||||
import { Typography, Button, Modal, Input } from "@douyinfe/semi-ui";
|
||||
import { IconSearch } from "components/icons";
|
||||
import { IDocument } from "@think/domains";
|
||||
import { useRecentDocuments } from "data/document";
|
||||
import { useToggle } from "hooks/useToggle";
|
||||
import { searchDocument } from "services/document";
|
||||
import { Empty } from "components/empty";
|
||||
import { DataRender } from "components/data-render";
|
||||
import { LocaleTime } from "components/locale-time";
|
||||
import { DocumentStar } from "components/document/star";
|
||||
import { IconDocumentFill } from "components/icons/IconDocumentFill";
|
||||
import styles from "./index.module.scss";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Router from 'next/router';
|
||||
import { Typography, Button, Modal, Input, Spin } from '@douyinfe/semi-ui';
|
||||
import { IconSearch as SemiIconSearch } from '@douyinfe/semi-icons';
|
||||
import { IconSearch } from 'components/icons';
|
||||
import { IDocument } from '@think/domains';
|
||||
import { useRecentDocuments } from 'data/document';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
import { useAsyncLoading } from 'hooks/useAsyncLoading';
|
||||
import { searchDocument } from 'services/document';
|
||||
import { Empty } from 'components/empty';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { LocaleTime } from 'components/locale-time';
|
||||
import { DocumentStar } from 'components/document/star';
|
||||
import { IconDocumentFill } from 'components/icons/IconDocumentFill';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
||||
return (
|
||||
<div className={styles.itemsWrap}>
|
||||
{data.length ? (
|
||||
data.map((doc) => {
|
||||
return (
|
||||
<div className={styles.itemWrap} key={doc.id}>
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/wiki/[wikiId]/document/[documentId]',
|
||||
query: {
|
||||
wikiId: doc.wikiId,
|
||||
documentId: doc.id,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<a className={styles.item}>
|
||||
<div className={styles.leftWrap}>
|
||||
<IconDocumentFill style={{ marginRight: 12 }} />
|
||||
<div>
|
||||
<Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{ width: 180 }}
|
||||
>
|
||||
{doc.title}
|
||||
</Text>
|
||||
|
||||
<Text size="small" type="tertiary">
|
||||
创建者:
|
||||
{doc.createUser && doc.createUser.name} •{' '}
|
||||
<LocaleTime date={doc.updatedAt} timeago />
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.rightWrap}>
|
||||
<DocumentStar documentId={doc.id} />
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Empty message="暂无搜索结果" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Search = () => {
|
||||
const [visible, toggleVisible] = useToggle(false);
|
||||
const { data: recentDocs, loading, error } = useRecentDocuments();
|
||||
const [keyword, setKeyword] = useState("");
|
||||
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 data = useMemo(
|
||||
() => (searchDocs.length ? searchDocs : recentDocs),
|
||||
[searchDocs.length, recentDocs]
|
||||
);
|
||||
|
||||
const search = useCallback(() => {
|
||||
searchDocument(keyword).then((res) => {
|
||||
setSearchDocs(res);
|
||||
});
|
||||
}, [keyword]);
|
||||
setError(null);
|
||||
searchApi(keyword)
|
||||
.then((res) => {
|
||||
setSearchDocs(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
});
|
||||
}, [searchApi, keyword]);
|
||||
|
||||
useEffect(() => {
|
||||
const fn = () => {
|
||||
toggleVisible(false);
|
||||
};
|
||||
Router.events.on("routeChangeStart", fn);
|
||||
|
||||
Router.events.on('routeChangeStart', fn);
|
||||
|
||||
return () => {
|
||||
Router.events.off("routeChangeStart", fn);
|
||||
Router.events.off('routeChangeStart', fn);
|
||||
};
|
||||
}, []);
|
||||
}, [toggleVisible]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -56,68 +111,53 @@ export const Search = () => {
|
|||
title="文档搜索"
|
||||
footer={null}
|
||||
onCancel={toggleVisible}
|
||||
style={{
|
||||
maxWidth: '96vw',
|
||||
}}
|
||||
>
|
||||
<div style={{ paddingBottom: 24 }}>
|
||||
<Input
|
||||
placeholder={"搜索文档"}
|
||||
size="large"
|
||||
value={keyword}
|
||||
onChange={(val) => setKeyword(val)}
|
||||
onEnterPress={search}
|
||||
/>
|
||||
<DataRender
|
||||
loading={loading}
|
||||
error={error}
|
||||
normalContent={() => {
|
||||
return (
|
||||
<div className={styles.itemsWrap}>
|
||||
{data.length ? (
|
||||
data.map((doc) => {
|
||||
return (
|
||||
<div className={styles.itemWrap}>
|
||||
<Link
|
||||
href={{
|
||||
pathname: "/wiki/[wikiId]/document/[documentId]",
|
||||
query: {
|
||||
wikiId: doc.wikiId,
|
||||
documentId: doc.id,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<a className={styles.item}>
|
||||
<div className={styles.leftWrap}>
|
||||
<IconDocumentFill style={{ marginRight: 12 }} />
|
||||
<div>
|
||||
<Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
style={{ width: 180 }}
|
||||
>
|
||||
{doc.title}
|
||||
</Text>
|
||||
|
||||
<Text size="small" type="tertiary">
|
||||
创建者:
|
||||
{doc.createUser &&
|
||||
doc.createUser.name} •{" "}
|
||||
<LocaleTime date={doc.updatedAt} timeago />
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.rightWrap}>
|
||||
<DocumentStar documentId={doc.id} />
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Empty message="最近访问的文档会出现在此处" />
|
||||
)}
|
||||
<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>
|
||||
</Modal>
|
||||
</>
|
||||
|
|
Loading…
Reference in New Issue