mirror of https://gitee.com/answerdev/answer.git
feat: Increase the skeleton screen loading effect
This commit is contained in:
parent
df191fa736
commit
0526bb95b6
|
@ -13,6 +13,7 @@ import {
|
|||
Empty,
|
||||
BaseUserCard,
|
||||
QueryGroup,
|
||||
QuestionListLoader,
|
||||
} from '@/components';
|
||||
import { useQuestionList } from '@/services';
|
||||
|
||||
|
@ -64,69 +65,73 @@ const QuestionList: FC<Props> = ({ source }) => {
|
|||
/>
|
||||
</div>
|
||||
<ListGroup className="rounded-0">
|
||||
{listData?.list?.map((li) => {
|
||||
return (
|
||||
<ListGroup.Item
|
||||
key={li.id}
|
||||
className="bg-transparent py-3 px-0 border-start-0 border-end-0">
|
||||
<h5 className="text-wrap text-break">
|
||||
<NavLink
|
||||
to={pathFactory.questionLanding(li.id, li.url_title)}
|
||||
className="link-dark">
|
||||
{li.title}
|
||||
{li.status === 2 ? ` [${t('closed')}]` : ''}
|
||||
</NavLink>
|
||||
</h5>
|
||||
<div className="d-flex flex-column flex-md-row align-items-md-center fs-14 mb-2 text-secondary">
|
||||
<div className="d-flex">
|
||||
<BaseUserCard
|
||||
data={li.operator}
|
||||
showAvatar={false}
|
||||
className="me-1"
|
||||
/>
|
||||
•
|
||||
<FormatTime
|
||||
time={li.operated_at}
|
||||
className="text-secondary ms-1"
|
||||
preFix={t(li.operation_type)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ms-0 ms-md-3 mt-2 mt-md-0">
|
||||
<span>
|
||||
<Icon name="hand-thumbs-up-fill" />
|
||||
<em className="fst-normal ms-1">{li.vote_count}</em>
|
||||
</span>
|
||||
<span
|
||||
className={`ms-3 ${
|
||||
li.accepted_answer_id >= 1 ? 'text-success' : ''
|
||||
}`}>
|
||||
<Icon
|
||||
name={
|
||||
li.accepted_answer_id >= 1
|
||||
? 'check-circle-fill'
|
||||
: 'chat-square-text-fill'
|
||||
}
|
||||
{isLoading ? (
|
||||
<QuestionListLoader />
|
||||
) : (
|
||||
listData?.list?.map((li) => {
|
||||
return (
|
||||
<ListGroup.Item
|
||||
key={li.id}
|
||||
className="bg-transparent py-3 px-0 border-start-0 border-end-0">
|
||||
<h5 className="text-wrap text-break">
|
||||
<NavLink
|
||||
to={pathFactory.questionLanding(li.id, li.url_title)}
|
||||
className="link-dark">
|
||||
{li.title}
|
||||
{li.status === 2 ? ` [${t('closed')}]` : ''}
|
||||
</NavLink>
|
||||
</h5>
|
||||
<div className="d-flex flex-column flex-md-row align-items-md-center fs-14 mb-2 text-secondary">
|
||||
<div className="d-flex">
|
||||
<BaseUserCard
|
||||
data={li.operator}
|
||||
showAvatar={false}
|
||||
className="me-1"
|
||||
/>
|
||||
<em className="fst-normal ms-1">{li.answer_count}</em>
|
||||
</span>
|
||||
<span className="summary-stat ms-3">
|
||||
<Icon name="eye-fill" />
|
||||
<em className="fst-normal ms-1">{li.view_count}</em>
|
||||
</span>
|
||||
•
|
||||
<FormatTime
|
||||
time={li.operated_at}
|
||||
className="text-secondary ms-1"
|
||||
preFix={t(li.operation_type)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ms-0 ms-md-3 mt-2 mt-md-0">
|
||||
<span>
|
||||
<Icon name="hand-thumbs-up-fill" />
|
||||
<em className="fst-normal ms-1">{li.vote_count}</em>
|
||||
</span>
|
||||
<span
|
||||
className={`ms-3 ${
|
||||
li.accepted_answer_id >= 1 ? 'text-success' : ''
|
||||
}`}>
|
||||
<Icon
|
||||
name={
|
||||
li.accepted_answer_id >= 1
|
||||
? 'check-circle-fill'
|
||||
: 'chat-square-text-fill'
|
||||
}
|
||||
/>
|
||||
<em className="fst-normal ms-1">{li.answer_count}</em>
|
||||
</span>
|
||||
<span className="summary-stat ms-3">
|
||||
<Icon name="eye-fill" />
|
||||
<em className="fst-normal ms-1">{li.view_count}</em>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="question-tags m-n1">
|
||||
{Array.isArray(li.tags)
|
||||
? li.tags.map((tag) => {
|
||||
return (
|
||||
<Tag key={tag.slug_name} className="m-1" data={tag} />
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
);
|
||||
})}
|
||||
<div className="question-tags m-n1">
|
||||
{Array.isArray(li.tags)
|
||||
? li.tags.map((tag) => {
|
||||
return (
|
||||
<Tag key={tag.slug_name} className="m-1" data={tag} />
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
</ListGroup.Item>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</ListGroup>
|
||||
{count <= 0 && !isLoading && <Empty />}
|
||||
<div className="mt-4 mb-2 d-flex justify-content-center">
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { FC, memo } from 'react';
|
||||
import { ListGroupItem } from 'react-bootstrap';
|
||||
|
||||
interface Props {
|
||||
count?: number;
|
||||
}
|
||||
|
||||
const Index: FC<Props> = ({ count = 10 }) => {
|
||||
const list = new Array(count).fill(0).map((v, i) => v + i);
|
||||
return (
|
||||
<>
|
||||
{list.map((v) => (
|
||||
<ListGroupItem
|
||||
className="bg-transparent py-3 px-0 border-start-0 border-end-0 placeholder-glow"
|
||||
key={v}>
|
||||
<div
|
||||
className="placeholder w-100 h5 align-top"
|
||||
style={{ height: '24px' }}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="placeholder w-75 d-block align-top mb-2"
|
||||
style={{ height: '21px' }}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="placeholder w-50 align-top"
|
||||
style={{ height: '24px' }}
|
||||
/>
|
||||
</ListGroupItem>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Index);
|
|
@ -0,0 +1,49 @@
|
|||
import { FC, memo } from 'react';
|
||||
import { Col, Card } from 'react-bootstrap';
|
||||
|
||||
interface Props {
|
||||
count?: number;
|
||||
}
|
||||
|
||||
const Index: FC<Props> = ({ count = 20 }) => {
|
||||
const list = new Array(count).fill(0).map((v, i) => v + i);
|
||||
return (
|
||||
<>
|
||||
{list.map((v) => (
|
||||
<Col
|
||||
key={v}
|
||||
xs={12}
|
||||
lg={3}
|
||||
md={4}
|
||||
sm={6}
|
||||
className="mb-4 placeholder-glow">
|
||||
<Card className="h-100">
|
||||
<Card.Body className="d-flex flex-column align-items-start">
|
||||
<div
|
||||
className="placeholder align-top w-25 mb-3"
|
||||
style={{ height: '24px' }}
|
||||
/>
|
||||
|
||||
<p
|
||||
className="placeholder fs-14 text-truncate-3 w-100"
|
||||
style={{ height: '42px' }}
|
||||
/>
|
||||
<div className="d-flex align-items-center">
|
||||
<div
|
||||
className="placeholder me-2"
|
||||
style={{ width: '80px', height: '31px' }}
|
||||
/>
|
||||
<span
|
||||
className="placeholder text-secondary fs-14 text-nowrap"
|
||||
style={{ width: '100px', height: '21px' }}
|
||||
/>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Index);
|
|
@ -30,6 +30,8 @@ import DiffContent from './DiffContent';
|
|||
import Customize from './Customize';
|
||||
import CustomizeTheme from './CustomizeTheme';
|
||||
import PageTags from './PageTags';
|
||||
import QuestionListLoader from './QuestionListLoader';
|
||||
import TagsLoader from './TagsLoader';
|
||||
|
||||
export {
|
||||
Avatar,
|
||||
|
@ -66,5 +68,7 @@ export {
|
|||
Customize,
|
||||
CustomizeTheme,
|
||||
PageTags,
|
||||
QuestionListLoader,
|
||||
TagsLoader,
|
||||
};
|
||||
export type { EditorRef, JSONSchema, UISchema };
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import { FC, memo } from 'react';
|
||||
|
||||
const Index: FC = () => {
|
||||
return (
|
||||
<div className="placeholder-glow">
|
||||
<div className="placeholder w-100 h1 mb-3" style={{ height: '34px' }} />
|
||||
|
||||
<div className="placeholder w-75 mb-3" style={{ height: '21px' }} />
|
||||
|
||||
<div
|
||||
className="placeholder w-50 d-block align-top mb-4"
|
||||
style={{ height: '24px' }}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="placeholder w-100 align-top"
|
||||
style={{ height: '450px', marginBottom: '2rem' }}
|
||||
/>
|
||||
|
||||
<div className="d-flex">
|
||||
<div
|
||||
className="placeholder align-top me-3 rounded"
|
||||
style={{ height: '38px', width: '120px' }}
|
||||
/>
|
||||
<div
|
||||
className="placeholder align-top rounded"
|
||||
style={{ height: '38px', width: '68px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="d-block d-md-flex flex-wrap mt-4 mb-3">
|
||||
<div
|
||||
className="placeholder mb-3 mb-md-0 me-4"
|
||||
style={{ height: '21px', width: '40%' }}
|
||||
/>
|
||||
<div
|
||||
style={{ minWidth: '196px', height: '24px' }}
|
||||
className="placeholder mb-3 me-4 mb-md-0 d-block d-md-none"
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{ minWidth: '196px', height: '24px' }}
|
||||
className="placeholder d-block d-md-none"
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{ minWidth: '196px', height: '40px' }}
|
||||
className="placeholder mb-3 me-4 mb-md-0 d-none d-md-block"
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{ minWidth: '196px', height: '40px' }}
|
||||
className="placeholder d-none d-md-block"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{[0, 1, 2].map((item, i) => (
|
||||
<div
|
||||
className={`border-bottom py-2 ${i === 0 ? 'border-top' : ''}`}
|
||||
key={item}>
|
||||
<div className="placeholder w-100 mb-1" style={{ height: '17px' }} />
|
||||
<div className="placeholder w-50" style={{ height: '17px' }} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="d-flex mt-2 mb-4">
|
||||
<div
|
||||
className="placeholder align-top me-4"
|
||||
style={{ height: '21px', width: '140px' }}
|
||||
/>
|
||||
<div
|
||||
className="placeholder align-top"
|
||||
style={{ height: '21px', width: '140px' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Index);
|
|
@ -17,11 +17,15 @@ const Index: FC<Props> = ({ id }) => {
|
|||
keyPrefix: 'related_question',
|
||||
});
|
||||
|
||||
const { data } = useSimilarQuestion({
|
||||
const { data, isLoading } = useSimilarQuestion({
|
||||
question_id: id,
|
||||
page_size: 5,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card.Header>{t('title')}</Card.Header>
|
||||
|
|
|
@ -4,5 +4,14 @@ import AnswerHead from './AnswerHead';
|
|||
import RelatedQuestions from './RelatedQuestions';
|
||||
import WriteAnswer from './WriteAnswer';
|
||||
import Alert from './Alert';
|
||||
import ContentLoader from './ContentLoader';
|
||||
|
||||
export { Question, Answer, AnswerHead, RelatedQuestions, WriteAnswer, Alert };
|
||||
export {
|
||||
Question,
|
||||
Answer,
|
||||
AnswerHead,
|
||||
RelatedQuestions,
|
||||
WriteAnswer,
|
||||
Alert,
|
||||
ContentLoader,
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
RelatedQuestions,
|
||||
WriteAnswer,
|
||||
Alert,
|
||||
ContentLoader,
|
||||
} from './components';
|
||||
|
||||
import './index.scss';
|
||||
|
@ -45,6 +46,7 @@ const Index = () => {
|
|||
const page = Number(urlSearch.get('page') || 0);
|
||||
const order = urlSearch.get('order') || '';
|
||||
const [question, setQuestion] = useState<QuestionDetailRes | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [answers, setAnswers] = useState<ListResult<AnswerItem>>({
|
||||
count: -1,
|
||||
list: [],
|
||||
|
@ -95,15 +97,20 @@ const Index = () => {
|
|||
};
|
||||
|
||||
const getDetail = async () => {
|
||||
const res = await questionDetail(qid);
|
||||
if (res) {
|
||||
// undo
|
||||
setUsers([
|
||||
res.user_info,
|
||||
res?.update_user_info,
|
||||
res?.last_answered_user_info,
|
||||
]);
|
||||
setQuestion(res);
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await questionDetail(qid);
|
||||
if (res) {
|
||||
setUsers([
|
||||
res.user_info,
|
||||
res?.update_user_info,
|
||||
res?.last_answered_user_info,
|
||||
]);
|
||||
setQuestion(res);
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -162,13 +169,17 @@ const Index = () => {
|
|||
{question?.operation?.operation_type && (
|
||||
<Alert data={question.operation} />
|
||||
)}
|
||||
<Question
|
||||
data={question}
|
||||
initPage={initPage}
|
||||
hasAnswer={answers.count > 0}
|
||||
isLogged={isLogged}
|
||||
/>
|
||||
{answers.count > 0 && (
|
||||
{isLoading ? (
|
||||
<ContentLoader />
|
||||
) : (
|
||||
<Question
|
||||
data={question}
|
||||
initPage={initPage}
|
||||
hasAnswer={answers.count > 0}
|
||||
isLogged={isLogged}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && answers.count > 0 && (
|
||||
<>
|
||||
<AnswerHead count={answers.count} order={order} />
|
||||
{answers?.list?.map((item) => {
|
||||
|
@ -188,7 +199,7 @@ const Index = () => {
|
|||
</>
|
||||
)}
|
||||
|
||||
{Math.ceil(answers.count / 15) > 1 && (
|
||||
{!isLoading && Math.ceil(answers.count / 15) > 1 && (
|
||||
<div className="d-flex justify-content-center answer-item pt-4">
|
||||
<Pagination
|
||||
currentPage={Number(page || 1)}
|
||||
|
@ -198,7 +209,7 @@ const Index = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{!question?.operation?.operation_type && (
|
||||
{!isLoading && !question?.operation?.operation_type && (
|
||||
<WriteAnswer
|
||||
visible={answers.count === 0}
|
||||
data={{
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { FC, memo } from 'react';
|
||||
import { ListGroupItem } from 'react-bootstrap';
|
||||
|
||||
interface Props {
|
||||
count?: number;
|
||||
}
|
||||
|
||||
const Index: FC<Props> = ({ count = 10 }) => {
|
||||
const list = new Array(count).fill(0).map((v, i) => v + i);
|
||||
return (
|
||||
<>
|
||||
{list.map((v) => (
|
||||
<ListGroupItem
|
||||
className="py-3 px-0 border-start-0 border-end-0 bg-transparent placeholder-glow"
|
||||
key={v}>
|
||||
<div className="mb-2">
|
||||
<div
|
||||
className="placeholder me-2"
|
||||
style={{ height: '25px', width: '30px' }}
|
||||
/>
|
||||
<div
|
||||
className="h5 mb-0 w-75 placeholder"
|
||||
style={{ height: '25px' }}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="placeholder w-50 h5 align-top mb-2"
|
||||
style={{ height: '21px' }}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="placeholder w-100 d-block align-top mb-2"
|
||||
style={{ height: '42px' }}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="placeholder w-25 align-top"
|
||||
style={{ height: '24px' }}
|
||||
/>
|
||||
</ListGroupItem>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Index);
|
|
@ -3,5 +3,6 @@ import SearchItem from './SearchItem';
|
|||
import Tips from './Tips';
|
||||
import Empty from './Empty';
|
||||
import SearchHead from './SearchHead';
|
||||
import ListLoader from './ListLoader';
|
||||
|
||||
export { Head, SearchItem, Tips, Empty, SearchHead };
|
||||
export { Head, SearchItem, Tips, Empty, SearchHead, ListLoader };
|
||||
|
|
|
@ -7,7 +7,14 @@ import { usePageTags } from '@/hooks';
|
|||
import { Pagination } from '@/components';
|
||||
import { useSearch } from '@/services';
|
||||
|
||||
import { Head, SearchHead, SearchItem, Tips, Empty } from './components';
|
||||
import {
|
||||
Head,
|
||||
SearchHead,
|
||||
SearchItem,
|
||||
Tips,
|
||||
Empty,
|
||||
ListLoader,
|
||||
} from './components';
|
||||
|
||||
const Index = () => {
|
||||
const { t } = useTranslation('translation');
|
||||
|
@ -38,9 +45,13 @@ const Index = () => {
|
|||
<Head data={extra} />
|
||||
<SearchHead sort={order} count={count} />
|
||||
<ListGroup className="rounded-0 mb-5">
|
||||
{list?.map((item) => {
|
||||
return <SearchItem key={item.object.id} data={item} />;
|
||||
})}
|
||||
{isLoading ? (
|
||||
<ListLoader />
|
||||
) : (
|
||||
list?.map((item) => {
|
||||
return <SearchItem key={item.object.id} data={item} />;
|
||||
})
|
||||
)}
|
||||
</ListGroup>
|
||||
|
||||
{!isLoading && !list?.length && <Empty />}
|
||||
|
|
|
@ -19,7 +19,7 @@ const Questions: FC = () => {
|
|||
const curTagName = routeParams.tagName || '';
|
||||
const [tagInfo, setTagInfo] = useState<any>({});
|
||||
const [tagFollow, setTagFollow] = useState<Type.FollowParams>();
|
||||
const { data: tagResp } = useTagInfo({ name: curTagName });
|
||||
const { data: tagResp, isLoading } = useTagInfo({ name: curTagName });
|
||||
const { data: followResp } = useFollow(tagFollow);
|
||||
const { data: synonymsRes } = useQuerySynonymsTags(tagInfo?.tag_id);
|
||||
const toggleFollow = () => {
|
||||
|
@ -73,37 +73,52 @@ const Questions: FC = () => {
|
|||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<Col xxl={7} lg={8} sm={12}>
|
||||
<div className="tag-box mb-5">
|
||||
<h3 className="mb-3">
|
||||
<Link
|
||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||
replace
|
||||
className="link-dark">
|
||||
{tagInfo.display_name}
|
||||
</Link>
|
||||
</h3>
|
||||
{isLoading ? (
|
||||
<div className="tag-box mb-5 placeholder-glow">
|
||||
<div className="mb-3 h3 placeholder" style={{ width: '120px' }} />
|
||||
<p
|
||||
className="placeholder w-100 d-block align-top"
|
||||
style={{ height: '24px' }}
|
||||
/>
|
||||
|
||||
<p className="text-break">
|
||||
{escapeRemove(tagInfo.excerpt) || t('no_desc')}
|
||||
<Link to={pathFactory.tagInfo(curTagName)} className="ms-1">
|
||||
[{t('more')}]
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div className="box-ft">
|
||||
{tagInfo.is_follower ? (
|
||||
<Button variant="primary" onClick={() => toggleFollow()}>
|
||||
{t('button_following')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={() => toggleFollow()}>
|
||||
{t('button_follow')}
|
||||
</Button>
|
||||
)}
|
||||
<div
|
||||
className="placeholder d-block align-top"
|
||||
style={{ height: '38px', width: '100px' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="tag-box mb-5">
|
||||
<h3 className="mb-3">
|
||||
<Link
|
||||
to={pathFactory.tagLanding(tagInfo.slug_name)}
|
||||
replace
|
||||
className="link-dark">
|
||||
{tagInfo.display_name}
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
<p className="text-break">
|
||||
{escapeRemove(tagInfo.excerpt) || t('no_desc')}
|
||||
<Link to={pathFactory.tagInfo(curTagName)} className="ms-1">
|
||||
[{t('more')}]
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div className="box-ft">
|
||||
{tagInfo.is_follower ? (
|
||||
<Button variant="primary" onClick={() => toggleFollow()}>
|
||||
{t('button_following')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={() => toggleFollow()}>
|
||||
{t('button_follow')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<QuestionList source="tag" />
|
||||
</Col>
|
||||
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { usePageTags } from '@/hooks';
|
||||
import { Tag, Pagination, QueryGroup } from '@/components';
|
||||
import { Tag, Pagination, QueryGroup, TagsLoader } from '@/components';
|
||||
import { formatCount } from '@/utils';
|
||||
import { useQueryTags, following } from '@/services';
|
||||
|
||||
|
@ -19,7 +19,11 @@ const Tags = () => {
|
|||
const sort = urlSearch.get('sort');
|
||||
|
||||
const pageSize = 20;
|
||||
const { data: tags, mutate } = useQueryTags({
|
||||
const {
|
||||
data: tags,
|
||||
mutate,
|
||||
isLoading,
|
||||
} = useQueryTags({
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...(searchTag ? { slug_name: searchTag } : {}),
|
||||
|
@ -69,39 +73,43 @@ const Tags = () => {
|
|||
|
||||
<Col className="mt-4" xxl={10} sm={12}>
|
||||
<Row>
|
||||
{tags?.list?.map((tag) => (
|
||||
<Col
|
||||
key={tag.slug_name}
|
||||
xs={12}
|
||||
lg={3}
|
||||
md={4}
|
||||
sm={6}
|
||||
className="mb-4">
|
||||
<Card className="h-100">
|
||||
<Card.Body className="d-flex flex-column align-items-start">
|
||||
<Tag className="mb-3" data={tag} />
|
||||
{isLoading ? (
|
||||
<TagsLoader />
|
||||
) : (
|
||||
tags?.list?.map((tag) => (
|
||||
<Col
|
||||
key={tag.slug_name}
|
||||
xs={12}
|
||||
lg={3}
|
||||
md={4}
|
||||
sm={6}
|
||||
className="mb-4">
|
||||
<Card className="h-100">
|
||||
<Card.Body className="d-flex flex-column align-items-start">
|
||||
<Tag className="mb-3" data={tag} />
|
||||
|
||||
<p className="fs-14 flex-fill text-break text-wrap text-truncate-3">
|
||||
{tag.original_text}
|
||||
</p>
|
||||
<div className="d-flex align-items-center">
|
||||
<Button
|
||||
className={`me-2 ${tag.is_follower ? 'active' : ''}`}
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
onClick={() => handleFollow(tag)}>
|
||||
{tag.is_follower
|
||||
? t('button_following')
|
||||
: t('button_follow')}
|
||||
</Button>
|
||||
<span className="text-secondary fs-14 text-nowrap">
|
||||
{formatCount(tag.question_count)} {t('tag_label')}
|
||||
</span>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
<p className="fs-14 flex-fill text-break text-wrap text-truncate-3">
|
||||
{tag.original_text}
|
||||
</p>
|
||||
<div className="d-flex align-items-center">
|
||||
<Button
|
||||
className={`me-2 ${tag.is_follower ? 'active' : ''}`}
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
onClick={() => handleFollow(tag)}>
|
||||
{tag.is_follower
|
||||
? t('button_following')
|
||||
: t('button_follow')}
|
||||
</Button>
|
||||
<span className="text-secondary fs-14 text-nowrap">
|
||||
{formatCount(tag.question_count)} {t('tag_label')}
|
||||
</span>
|
||||
</div>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
))
|
||||
)}
|
||||
</Row>
|
||||
<div className="d-flex justify-content-center">
|
||||
<Pagination
|
||||
|
|
|
@ -19,12 +19,18 @@ export const useQueryQuestionByTitle = (title) => {
|
|||
};
|
||||
|
||||
export const useQueryTags = (params) => {
|
||||
return useSWR<Type.ListResult>(
|
||||
const { data, error, mutate } = useSWR<Type.ListResult>(
|
||||
`/answer/api/v1/tags/page?${qs.stringify(params, {
|
||||
skipNulls: true,
|
||||
})}`,
|
||||
request.instance.get,
|
||||
);
|
||||
return {
|
||||
data,
|
||||
isLoading: !data && !error,
|
||||
error,
|
||||
mutate,
|
||||
};
|
||||
};
|
||||
|
||||
export const useQueryRevisions = (object_id: string | undefined) => {
|
||||
|
|
Loading…
Reference in New Issue