Article.tsx 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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. }
  68. const ArticleWidget = ({
  69. type,
  70. id,
  71. book,
  72. para,
  73. channelId,
  74. articleId,
  75. anthologyId,
  76. courseId,
  77. exerciseId,
  78. userName,
  79. mode = "read",
  80. active = false,
  81. onArticleChange,
  82. onFinal,
  83. }: IWidgetArticle) => {
  84. const [articleData, setArticleData] = useState<IArticleDataResponse>();
  85. const [articleMode, setArticleMode] = useState<ArticleMode>();
  86. const [extra, setExtra] = useState(<></>);
  87. const [showSkeleton, setShowSkeleton] = useState(true);
  88. const [unauthorized, setUnauthorized] = useState(false);
  89. const channels = channelId?.split("_");
  90. useEffect(() => {
  91. /**
  92. * 由课本进入查询当前用户的权限和channel
  93. */
  94. if (
  95. type === "textbook" ||
  96. type === "exercise" ||
  97. type === "exercise-list"
  98. ) {
  99. if (typeof articleId !== "undefined") {
  100. const id = articleId.split("_");
  101. get<ICourseCurrUserResponse>(`/v2/course-curr?course_id=${id[0]}`).then(
  102. (response) => {
  103. console.log("course user", response);
  104. if (response.ok) {
  105. const it: ICourseUser = {
  106. channelId: response.data.channel_id,
  107. role: response.data.role,
  108. };
  109. store.dispatch(signIn(it));
  110. /**
  111. * redux发布课程信息
  112. */
  113. const ic: ITextbook = {
  114. courseId: id[0],
  115. articleId: id[1],
  116. };
  117. store.dispatch(refresh(ic));
  118. }
  119. }
  120. );
  121. }
  122. }
  123. }, [articleId, type]);
  124. useEffect(() => {
  125. setArticleMode(mode ? mode : "read");
  126. //发布mode变更
  127. console.log("发布mode变更", mode);
  128. store.dispatch(modeChange(mode as ArticleMode));
  129. }, [mode]);
  130. useEffect(() => {
  131. console.log("mode", mode, articleMode);
  132. if (!active) {
  133. return;
  134. }
  135. if (mode === articleMode) {
  136. return;
  137. }
  138. //发布mode变更
  139. //store.dispatch(modeChange(mode));
  140. if (
  141. (mode === "edit" && articleMode === "wbw") ||
  142. (mode === "wbw" && articleMode === "edit")
  143. ) {
  144. console.log("set mode", mode, articleMode);
  145. setArticleMode(mode ? mode : "read");
  146. return;
  147. }
  148. setArticleMode(mode ? mode : "read");
  149. if (typeof type !== "undefined") {
  150. let url = "";
  151. switch (type) {
  152. case "chapter":
  153. if (typeof articleId !== "undefined") {
  154. url = `/v2/corpus-chapter/${articleId}?mode=${mode}`;
  155. url += channelId ? `&channels=${channelId}` : "";
  156. }
  157. break;
  158. case "para":
  159. url = `/v2/corpus?view=para&book=${book}&par=${para}&mode=${mode}`;
  160. url += channelId ? `&channels=${channelId}` : "";
  161. break;
  162. case "article":
  163. if (typeof articleId !== "undefined") {
  164. url = `/v2/article/${articleId}?mode=${mode}`;
  165. url += channelId ? `&channel=${channelId}` : "";
  166. url += anthologyId ? `&anthology=${anthologyId}` : "";
  167. }
  168. break;
  169. case "textbook":
  170. if (typeof articleId !== "undefined") {
  171. url = `/v2/article/${articleId}?view=textbook&course=${courseId}&mode=${mode}`;
  172. }
  173. break;
  174. case "exercise":
  175. if (typeof articleId !== "undefined") {
  176. url = `/v2/article/${articleId}?mode=${mode}&course=${courseId}&exercise=${exerciseId}&user=${userName}`;
  177. setExtra(
  178. <ExerciseAnswer
  179. courseId={courseId}
  180. articleId={articleId}
  181. exerciseId={exerciseId}
  182. />
  183. );
  184. }
  185. break;
  186. case "exercise-list":
  187. if (typeof articleId !== "undefined") {
  188. url = `/v2/article/${articleId}?mode=${mode}&course=${courseId}&exercise=${exerciseId}`;
  189. setExtra(
  190. <ExerciseList
  191. courseId={courseId}
  192. articleId={articleId}
  193. exerciseId={exerciseId}
  194. />
  195. );
  196. }
  197. break;
  198. default:
  199. if (typeof articleId !== "undefined") {
  200. url = `/v2/corpus/${type}/${articleId}/${mode}?mode=${mode}`;
  201. url += channelId ? `&channel=${channelId}` : "";
  202. }
  203. break;
  204. }
  205. console.log("article url", url);
  206. setShowSkeleton(true);
  207. get<IArticleResponse>(url)
  208. .then((json) => {
  209. console.log("article", json);
  210. if (json.ok) {
  211. setArticleData(json.data);
  212. setShowSkeleton(false);
  213. setExtra(
  214. <TocTree
  215. treeData={json.data.toc?.map((item) => {
  216. const strTitle = item.title ? item.title : item.pali_title;
  217. const progress = item.progress?.map((item, id) => (
  218. <Tag key={id}>{Math.round(item * 100)}</Tag>
  219. ));
  220. return {
  221. key: `${item.book}-${item.paragraph}`,
  222. title: (
  223. <>
  224. <PaliText text={strTitle} />
  225. {progress}
  226. </>
  227. ),
  228. level: item.level,
  229. };
  230. })}
  231. onSelect={(keys: string[]) => {
  232. console.log(keys);
  233. if (
  234. typeof onArticleChange !== "undefined" &&
  235. keys.length > 0
  236. ) {
  237. onArticleChange(keys[0]);
  238. }
  239. }}
  240. />
  241. );
  242. switch (type) {
  243. case "chapter":
  244. if (typeof articleId === "string" && channelId) {
  245. const [book, para] = articleId?.split("-");
  246. post<IViewRequest, IViewStoreResponse>("/v2/view", {
  247. target_type: type,
  248. book: parseInt(book),
  249. para: parseInt(para),
  250. channel: channelId,
  251. mode: mode ? mode : "read",
  252. }).then((json) => {
  253. console.log("view", json.data);
  254. });
  255. }
  256. break;
  257. default:
  258. break;
  259. }
  260. } else {
  261. setShowSkeleton(false);
  262. setUnauthorized(true);
  263. message.error(json.message);
  264. }
  265. })
  266. .catch((e) => {
  267. console.error(e);
  268. });
  269. }
  270. }, [
  271. active,
  272. type,
  273. articleId,
  274. mode,
  275. articleMode,
  276. book,
  277. para,
  278. channelId,
  279. anthologyId,
  280. courseId,
  281. exerciseId,
  282. userName,
  283. ]);
  284. return (
  285. <div>
  286. {showSkeleton ? (
  287. <ArticleSkeleton />
  288. ) : unauthorized ? (
  289. <Result
  290. status="403"
  291. title="无权访问"
  292. subTitle="您无权访问该内容。您可能没有登录,或者内容的所有者没有给您所需的权限。"
  293. extra={<></>}
  294. />
  295. ) : (
  296. <ArticleView
  297. id={articleData?.uid}
  298. title={articleData?.title}
  299. subTitle={articleData?.subtitle}
  300. summary={articleData?.summary}
  301. content={articleData ? articleData.content : ""}
  302. html={articleData?.html}
  303. path={articleData?.path}
  304. created_at={articleData?.created_at}
  305. updated_at={articleData?.updated_at}
  306. channels={channels}
  307. type={type}
  308. articleId={articleId}
  309. />
  310. )}
  311. {extra}
  312. <Divider />
  313. <CommentListCard resId={articleData?.uid} resType="article" />
  314. </div>
  315. );
  316. };
  317. export default ArticleWidget;