AnthologyDetail.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { useState, useEffect } from "react";
  2. import { Space, Typography, message } from "antd";
  3. import { get } from "../../request";
  4. import type {
  5. IAnthologyDataResponse,
  6. IAnthologyResponse,
  7. } from "../../api/Article";
  8. import type { IAnthologyData } from "./AnthologyCard";
  9. import StudioName from "../auth/Studio";
  10. import TimeShow from "../general/TimeShow";
  11. import Marked from "../general/Marked";
  12. import AnthologyTocTree from "../anthology/AnthologyTocTree";
  13. import { useIntl } from "react-intl";
  14. const { Title, Text, Paragraph } = Typography;
  15. interface Props {
  16. aid?: string;
  17. channels?: string[];
  18. visible?: boolean;
  19. onArticleClick?: (anthologyId: string, id: string, target: string) => void;
  20. onTitle?: (title: string) => void;
  21. onLoading?: (loading: boolean) => void;
  22. onError?: (error: unknown, message?: string) => void;
  23. }
  24. const AnthologyDetailWidget = ({
  25. aid,
  26. channels,
  27. visible = true,
  28. onArticleClick,
  29. onLoading,
  30. onTitle,
  31. onError,
  32. }: Props) => {
  33. const [data, setData] = useState<IAnthologyData>();
  34. const intl = useIntl();
  35. useEffect(() => {
  36. if (!aid) return;
  37. let active = true;
  38. const fetchData = async () => {
  39. try {
  40. onLoading?.(true);
  41. const res = await get<IAnthologyResponse>(`/v2/anthology/${aid}`);
  42. if (!active) return;
  43. if (!res.ok) {
  44. message.error(res.message);
  45. onError?.(res.data, res.message);
  46. return;
  47. }
  48. const item: IAnthologyDataResponse = res.data;
  49. const parsed: IAnthologyData = {
  50. id: item.uid,
  51. title: item.title,
  52. subTitle: item.subtitle,
  53. summary: item.summary,
  54. articles: [],
  55. studio: item.studio,
  56. created_at: item.created_at,
  57. updated_at: item.updated_at,
  58. };
  59. setData(parsed);
  60. onTitle?.(item.title);
  61. } catch (err) {
  62. if (active) {
  63. console.error(err);
  64. onError?.(err);
  65. }
  66. } finally {
  67. if (active && onLoading) {
  68. onLoading(false);
  69. }
  70. }
  71. };
  72. fetchData();
  73. return () => {
  74. active = false;
  75. };
  76. }, [aid, onError, onLoading, onTitle]);
  77. if (!visible || !data) return null;
  78. return (
  79. <div style={{ padding: 12 }}>
  80. <Title level={4}>{data.title}</Title>
  81. <Text type="secondary">{data.subTitle}</Text>
  82. <Paragraph>
  83. <Space>
  84. <StudioName data={data.studio} />
  85. <TimeShow updatedAt={data.updated_at} />
  86. </Space>
  87. </Paragraph>
  88. <Paragraph>
  89. <Marked text={data.summary} />
  90. </Paragraph>
  91. <Title level={5}>
  92. {intl.formatMessage({ id: "labels.table-of-content" })}
  93. </Title>
  94. <AnthologyTocTree
  95. anthologyId={aid}
  96. channels={channels}
  97. onClick={(anthologyId, id, target) =>
  98. onArticleClick?.(anthologyId, id, target)
  99. }
  100. />
  101. </div>
  102. );
  103. };
  104. export default AnthologyDetailWidget;