Article.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. import { useEffect, useState } from "react";
  2. import { Divider, message, Result, Tag } from "antd";
  3. import { get, post } from "../../request";
  4. import store from "../../store";
  5. import { IArticleDataResponse, IArticleResponse } from "../api/Article";
  6. import ArticleView from "./ArticleView";
  7. import { ICourseCurrUserResponse } from "../api/Course";
  8. import { ICourseUser, signIn } from "../../reducers/course-user";
  9. import { ITextbook, refresh } from "../../reducers/current-course";
  10. import ExerciseList from "./ExerciseList";
  11. import ExerciseAnswer from "../course/ExerciseAnswer";
  12. import "./article.css";
  13. import CommentListCard from "../comment/CommentListCard";
  14. import TocTree from "./TocTree";
  15. import PaliText from "../template/Wbw/PaliText";
  16. import ArticleSkeleton from "./ArticleSkeleton";
  17. import {
  18. IViewRequest,
  19. IViewStoreResponse,
  20. } from "../../pages/studio/recent/list";
  21. import { modeChange } from "../../reducers/article-mode";
  22. export type ArticleMode = "read" | "edit" | "wbw";
  23. export type ArticleType =
  24. | "article"
  25. | "chapter"
  26. | "para"
  27. | "cs-para"
  28. | "sent"
  29. | "sim"
  30. | "page"
  31. | "textbook"
  32. | "exercise"
  33. | "exercise-list"
  34. | "sent-original"
  35. | "sent-commentary"
  36. | "sent-nissaya"
  37. | "sent-translation"
  38. | "term";
  39. /**
  40. * 每种article type 对应的路由参数
  41. * article/id?anthology=id&channel=id1,id2&mode=ArticleMode
  42. * chapter/book-para?channel=id1,id2&mode=ArticleMode
  43. * para/book?par=para1,para2&channel=id1,id2&mode=ArticleMode
  44. * cs-para/book-para?channel=id1,id2&mode=ArticleMode
  45. * sent/id?channel=id1,id2&mode=ArticleMode
  46. * sim/id?channel=id1,id2&mode=ArticleMode
  47. * textbook/articleId?course=id&mode=ArticleMode
  48. * exercise/articleId?course=id&exercise=id&username=name&mode=ArticleMode
  49. * exercise-list/articleId?course=id&exercise=id&mode=ArticleMode
  50. * sent-original/id
  51. */
  52. interface IWidgetArticle {
  53. type?: ArticleType;
  54. id?: string;
  55. book?: string | null;
  56. para?: string | null;
  57. channelId?: string | null;
  58. articleId?: string;
  59. anthologyId?: string;
  60. courseId?: string;
  61. exerciseId?: string;
  62. userName?: string;
  63. mode?: ArticleMode | null;
  64. active?: boolean;
  65. onArticleChange?: Function;
  66. onFinal?: Function;
  67. onLoad?: Function;
  68. }
  69. const ArticleWidget = ({
  70. type,
  71. id,
  72. book,
  73. para,
  74. channelId,
  75. articleId,
  76. anthologyId,
  77. courseId,
  78. exerciseId,
  79. userName,
  80. mode = "read",
  81. active = false,
  82. onArticleChange,
  83. onFinal,
  84. onLoad,
  85. }: IWidgetArticle) => {
  86. const [articleData, setArticleData] = useState<IArticleDataResponse>();
  87. const [extra, setExtra] = useState(<></>);
  88. const [showSkeleton, setShowSkeleton] = useState(true);
  89. const [unauthorized, setUnauthorized] = useState(false);
  90. const channels = channelId?.split("_");
  91. useEffect(() => {
  92. /**
  93. * 由课本进入查询当前用户的权限和channel
  94. */
  95. if (
  96. type === "textbook" ||
  97. type === "exercise" ||
  98. type === "exercise-list"
  99. ) {
  100. if (typeof articleId !== "undefined") {
  101. const id = articleId.split("_");
  102. get<ICourseCurrUserResponse>(`/v2/course-curr?course_id=${id[0]}`).then(
  103. (response) => {
  104. console.log("course user", response);
  105. if (response.ok) {
  106. const it: ICourseUser = {
  107. channelId: response.data.channel_id,
  108. role: response.data.role,
  109. };
  110. store.dispatch(signIn(it));
  111. /**
  112. * redux发布课程信息
  113. */
  114. const ic: ITextbook = {
  115. courseId: id[0],
  116. articleId: id[1],
  117. };
  118. store.dispatch(refresh(ic));
  119. }
  120. }
  121. );
  122. }
  123. }
  124. }, [articleId, type]);
  125. useEffect(() => {
  126. //发布mode变更
  127. console.log("发布mode变更", mode);
  128. store.dispatch(modeChange(mode as ArticleMode));
  129. }, [mode]);
  130. const srcDataMode = mode === "edit" || mode === "wbw" ? "edit" : "read";
  131. useEffect(() => {
  132. console.log("srcDataMode", srcDataMode);
  133. if (!active) {
  134. return;
  135. }
  136. if (typeof type !== "undefined") {
  137. let url = "";
  138. switch (type) {
  139. case "chapter":
  140. if (typeof articleId !== "undefined") {
  141. url = `/v2/corpus-chapter/${articleId}?mode=${srcDataMode}`;
  142. url += channelId ? `&channels=${channelId}` : "";
  143. }
  144. break;
  145. case "para":
  146. const _book = book ? book : articleId;
  147. url = `/v2/corpus?view=para&book=${_book}&par=${para}&mode=${srcDataMode}`;
  148. url += channelId ? `&channels=${channelId}` : "";
  149. break;
  150. case "article":
  151. if (typeof articleId !== "undefined") {
  152. url = `/v2/article/${articleId}?mode=${srcDataMode}`;
  153. url += channelId ? `&channel=${channelId}` : "";
  154. url += anthologyId ? `&anthology=${anthologyId}` : "";
  155. }
  156. break;
  157. case "textbook":
  158. if (typeof articleId !== "undefined") {
  159. url = `/v2/article/${articleId}?view=textbook&course=${courseId}&mode=${srcDataMode}`;
  160. }
  161. break;
  162. case "exercise":
  163. if (typeof articleId !== "undefined") {
  164. url = `/v2/article/${articleId}?mode=${srcDataMode}&course=${courseId}&exercise=${exerciseId}&user=${userName}`;
  165. setExtra(
  166. <ExerciseAnswer
  167. courseId={courseId}
  168. articleId={articleId}
  169. exerciseId={exerciseId}
  170. />
  171. );
  172. }
  173. break;
  174. case "exercise-list":
  175. if (typeof articleId !== "undefined") {
  176. url = `/v2/article/${articleId}?mode=${srcDataMode}&course=${courseId}&exercise=${exerciseId}`;
  177. setExtra(
  178. <ExerciseList
  179. courseId={courseId}
  180. articleId={articleId}
  181. exerciseId={exerciseId}
  182. />
  183. );
  184. }
  185. break;
  186. default:
  187. if (typeof articleId !== "undefined") {
  188. url = `/v2/corpus/${type}/${articleId}/${srcDataMode}?mode=${srcDataMode}`;
  189. url += channelId ? `&channel=${channelId}` : "";
  190. }
  191. break;
  192. }
  193. console.log("article url", url);
  194. setShowSkeleton(true);
  195. get<IArticleResponse>(url)
  196. .then((json) => {
  197. console.log("article", json);
  198. if (json.ok) {
  199. setArticleData(json.data);
  200. setShowSkeleton(false);
  201. setExtra(
  202. <TocTree
  203. treeData={json.data.toc?.map((item) => {
  204. const strTitle = item.title ? item.title : item.pali_title;
  205. const progress = item.progress?.map((item, id) => (
  206. <Tag key={id}>{Math.round(item * 100)}</Tag>
  207. ));
  208. return {
  209. key: `${item.book}-${item.paragraph}`,
  210. title: (
  211. <>
  212. <PaliText text={strTitle} />
  213. {progress}
  214. </>
  215. ),
  216. level: item.level,
  217. };
  218. })}
  219. onSelect={(keys: string[]) => {
  220. console.log(keys);
  221. if (
  222. typeof onArticleChange !== "undefined" &&
  223. keys.length > 0
  224. ) {
  225. onArticleChange(keys[0]);
  226. }
  227. }}
  228. />
  229. );
  230. switch (type) {
  231. case "chapter":
  232. if (typeof articleId === "string" && channelId) {
  233. const [book, para] = articleId?.split("-");
  234. post<IViewRequest, IViewStoreResponse>("/v2/view", {
  235. target_type: type,
  236. book: parseInt(book),
  237. para: parseInt(para),
  238. channel: channelId,
  239. mode: srcDataMode,
  240. }).then((json) => {
  241. console.log("view", json.data);
  242. });
  243. }
  244. break;
  245. default:
  246. break;
  247. }
  248. if (typeof onLoad !== "undefined") {
  249. onLoad(json.data);
  250. }
  251. } else {
  252. setShowSkeleton(false);
  253. setUnauthorized(true);
  254. message.error(json.message);
  255. }
  256. })
  257. .catch((e) => {
  258. console.error(e);
  259. });
  260. }
  261. }, [
  262. active,
  263. type,
  264. articleId,
  265. srcDataMode,
  266. book,
  267. para,
  268. channelId,
  269. anthologyId,
  270. courseId,
  271. exerciseId,
  272. userName,
  273. ]);
  274. return (
  275. <div>
  276. {showSkeleton ? (
  277. <ArticleSkeleton />
  278. ) : unauthorized ? (
  279. <Result
  280. status="403"
  281. title="无权访问"
  282. subTitle="您无权访问该内容。您可能没有登录,或者内容的所有者没有给您所需的权限。"
  283. extra={<></>}
  284. />
  285. ) : (
  286. <ArticleView
  287. id={articleData?.uid}
  288. title={articleData?.title}
  289. subTitle={articleData?.subtitle}
  290. summary={articleData?.summary}
  291. content={articleData ? articleData.content : ""}
  292. html={articleData?.html}
  293. path={articleData?.path}
  294. created_at={articleData?.created_at}
  295. updated_at={articleData?.updated_at}
  296. channels={channels}
  297. type={type}
  298. articleId={articleId}
  299. />
  300. )}
  301. {extra}
  302. <Divider />
  303. <CommentListCard resId={articleData?.uid} resType="article" />
  304. </div>
  305. );
  306. };
  307. export default ArticleWidget;