DiscussionListCard.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import { useEffect, useRef, useState } from "react";
  2. import { Button, Space, Typography } from "antd";
  3. import { LinkOutlined } from "@ant-design/icons";
  4. import { get } from "../../request";
  5. import type { ICommentListResponse } from "../../api/Comment";
  6. import type { IComment } from "./DiscussionItem";
  7. import type { IAnswerCount } from "./DiscussionDrawer";
  8. import { type ActionType, ProList } from "@ant-design/pro-components";
  9. import { renderBadge } from "../channel/ChannelTable";
  10. import DiscussionCreate from "./DiscussionCreate";
  11. import User from "../auth/User";
  12. import type { IArticleListResponse } from "../../api/Article";
  13. import { useAppSelector } from "../../hooks";
  14. import { currentUser as _currentUser } from "../../reducers/current-user";
  15. import { CommentOutlinedIcon, TemplateOutlinedIcon } from "../../assets/icon";
  16. import type { ISentenceResponse } from "../../api/Corpus";
  17. import type { TDiscussionType } from "./Discussion";
  18. import { courseInfo } from "../../reducers/current-course";
  19. import { courseUser } from "../../reducers/course-user";
  20. import TimeShow from "../general/TimeShow";
  21. const { Paragraph } = Typography;
  22. interface IWidget {
  23. resId?: string;
  24. resType?: TResType;
  25. topicId?: string;
  26. userId?: string;
  27. changedAnswerCount?: IAnswerCount;
  28. type?: TDiscussionType;
  29. pageSize?: number;
  30. showStudent?: boolean; //在课程中是否显示学生discussions
  31. onSelect?: Function;
  32. onItemCountChange?: Function;
  33. onReply?: Function;
  34. onReady?: Function;
  35. }
  36. const DiscussionListCardWidget = ({
  37. resId,
  38. resType,
  39. topicId,
  40. userId,
  41. showStudent = false,
  42. onSelect,
  43. changedAnswerCount,
  44. type = "discussion",
  45. pageSize = 10,
  46. onItemCountChange,
  47. ___onReply,
  48. onReady,
  49. }: IWidget) => {
  50. const ref = useRef<ActionType | null>(null);
  51. const [activeKey, setActiveKey] = useState<React.Key | undefined>("active");
  52. const [activeNumber, setActiveNumber] = useState<number>(0);
  53. const [closeNumber, setCloseNumber] = useState<number>(0);
  54. const [count, setCount] = useState<number>(0);
  55. const [canCreate, setCanCreate] = useState(false);
  56. const course = useAppSelector(courseInfo);
  57. const myCourse = useAppSelector(courseUser);
  58. const user = useAppSelector(_currentUser);
  59. useEffect(() => {
  60. ref.current?.reload();
  61. }, [resId, resType]);
  62. useEffect(() => {
  63. console.log("changedAnswerCount", changedAnswerCount);
  64. ref.current?.reload();
  65. }, [changedAnswerCount]);
  66. if (
  67. typeof resId === "undefined" &&
  68. typeof topicId === "undefined" &&
  69. typeof userId === "undefined"
  70. ) {
  71. return (
  72. <Typography.Paragraph>
  73. 该资源尚未创建,不能发表讨论。
  74. </Typography.Paragraph>
  75. );
  76. }
  77. return (
  78. <>
  79. <ProList<IComment>
  80. rowKey="id"
  81. actionRef={ref}
  82. metas={{
  83. avatar: {
  84. render(_dom, entity, _index, _action, _schema) {
  85. return (
  86. <>
  87. <User {...entity.user} showName={false} />
  88. </>
  89. );
  90. },
  91. },
  92. title: {
  93. render(_dom, entity, index, _action, _schema) {
  94. return (
  95. <>
  96. <div>
  97. {entity.resId !== resId ? <LinkOutlined /> : <></>}
  98. <Button
  99. key={index}
  100. size="small"
  101. type="link"
  102. icon={
  103. entity.newTpl ? <TemplateOutlinedIcon /> : undefined
  104. }
  105. onClick={(event) => {
  106. if (typeof onSelect !== "undefined") {
  107. onSelect(event, entity);
  108. }
  109. }}
  110. >
  111. {entity.title}
  112. </Button>
  113. </div>
  114. <div>
  115. <TimeShow
  116. type="secondary"
  117. showIcon={false}
  118. createdAt={entity.createdAt}
  119. updatedAt={entity.updatedAt}
  120. />
  121. </div>
  122. </>
  123. );
  124. },
  125. },
  126. description: {
  127. dataIndex: "content",
  128. search: false,
  129. render(_dom, entity, index, _action, _schema) {
  130. const content = entity.summary ?? entity.content;
  131. return (
  132. <div key={index}>
  133. <Paragraph
  134. type="secondary"
  135. ellipsis={{
  136. rows: 2,
  137. expandable: true,
  138. onEllipsis: (ellipsis) => {
  139. console.log("Ellipsis changed:", ellipsis);
  140. },
  141. }}
  142. title={content}
  143. >
  144. {content}
  145. </Paragraph>
  146. </div>
  147. );
  148. },
  149. },
  150. actions: {
  151. render: (_text, row, index, _action) => [
  152. row.childrenCount ? (
  153. <Space key={index}>
  154. <CommentOutlinedIcon key={"icon"} />
  155. <span key={"count"}>{row.childrenCount}</span>
  156. </Space>
  157. ) : (
  158. <></>
  159. ),
  160. ],
  161. },
  162. }}
  163. request={async (params = {}, _sorter, _filter) => {
  164. let url: string = `/v2/discussion?type=${type}&res_type=${resType}&`;
  165. if (typeof topicId !== "undefined") {
  166. url += `view=question-by-topic&id=${topicId}`;
  167. } else if (typeof resId !== "undefined") {
  168. url += `view=question&id=${resId}`;
  169. } else if (typeof userId !== "undefined") {
  170. url += `view=topic-by-user`;
  171. } else {
  172. return {
  173. total: 0,
  174. succcess: false,
  175. };
  176. }
  177. const offset =
  178. ((params.current ? params.current : 1) - 1) *
  179. (params.pageSize ? params.pageSize : pageSize);
  180. url += `&limit=${params.pageSize}&offset=${offset}`;
  181. url += params.keyword ? "&search=" + params.keyword : "";
  182. url += activeKey ? "&status=" + activeKey : "";
  183. if (myCourse && course) {
  184. if (myCourse.role !== "student") {
  185. url += `&course=${course.courseId}`;
  186. }
  187. }
  188. if (showStudent) {
  189. url += `&show_student=true`;
  190. }
  191. console.info("DiscussionListCard api request", url);
  192. const res = await get<ICommentListResponse>(url);
  193. console.info("DiscussionListCard api response", res);
  194. setCount(res.data.active);
  195. setCanCreate(res.data.can_create);
  196. const items: IComment[] = res.data.rows.map((item, _id) => {
  197. return {
  198. id: item.id,
  199. resId: item.res_id,
  200. resType: item.res_type,
  201. type: item.type,
  202. user: item.editor,
  203. title: item.title,
  204. parent: item.parent,
  205. tplId: item.tpl_id,
  206. content: item.content,
  207. summary: item.summary,
  208. status: item.status,
  209. childrenCount: item.children_count,
  210. createdAt: item.created_at,
  211. updatedAt: item.updated_at,
  212. };
  213. });
  214. let topicTpl: IComment[] = [];
  215. if (
  216. activeKey !== "close" &&
  217. user?.roles?.includes("basic") === false
  218. ) {
  219. //获取channel模版
  220. let studioName: string | undefined;
  221. switch (resType) {
  222. case "sentence":
  223. const url = `/v2/sentence/${resId}`;
  224. console.info("api request", url);
  225. const sentInfo = await get<ISentenceResponse>(url);
  226. console.info("api response", sentInfo);
  227. studioName = sentInfo.data.studio.realName;
  228. break;
  229. }
  230. const urlTpl = `/v2/article?view=template&studio_name=${studioName}&subtitle=_template_discussion_topic_&content=true`;
  231. const resTpl = await get<IArticleListResponse>(urlTpl);
  232. if (resTpl.ok) {
  233. console.log("resTpl.data.rows", resTpl.data.rows);
  234. topicTpl = resTpl.data.rows
  235. .filter(
  236. (value) =>
  237. items.findIndex((old) => old.tplId === value.uid) === -1
  238. )
  239. .map((item, _index) => {
  240. return {
  241. tplId: item.uid,
  242. resId: resId,
  243. resType: resType,
  244. type: "discussion",
  245. user: item.editor
  246. ? item.editor
  247. : { id: "", userName: "", nickName: "" },
  248. title: item.title,
  249. parent: null,
  250. content: item.content,
  251. html: item.html,
  252. summary: item.summary ? item.summary : item._summary,
  253. status: "active",
  254. childrenCount: 0,
  255. newTpl: true,
  256. createdAt: item.created_at,
  257. updatedAt: item.updated_at,
  258. };
  259. });
  260. }
  261. }
  262. setActiveNumber(res.data.active);
  263. setCloseNumber(res.data.close);
  264. if (typeof onReady !== "undefined") {
  265. onReady();
  266. }
  267. return {
  268. total: res.data.count,
  269. succcess: true,
  270. data: [...topicTpl, ...items],
  271. };
  272. }}
  273. bordered
  274. pagination={{
  275. showQuickJumper: true,
  276. showSizeChanger: true,
  277. pageSize: pageSize,
  278. }}
  279. search={false}
  280. options={{
  281. search: false,
  282. }}
  283. toolbar={{
  284. menu: {
  285. activeKey,
  286. items: [
  287. {
  288. key: "active",
  289. label: (
  290. <span>
  291. active
  292. {renderBadge(activeNumber, activeKey === "active")}
  293. </span>
  294. ),
  295. },
  296. {
  297. key: "close",
  298. label: (
  299. <span>
  300. close
  301. {renderBadge(closeNumber, activeKey === "close")}
  302. </span>
  303. ),
  304. },
  305. ],
  306. onChange(key) {
  307. setActiveKey(key);
  308. ref.current?.reload();
  309. },
  310. },
  311. }}
  312. />
  313. {canCreate && resId && resType ? (
  314. <DiscussionCreate
  315. contentType="markdown"
  316. resId={resId}
  317. resType={resType}
  318. type={type}
  319. onCreated={(_e: IComment) => {
  320. if (typeof onItemCountChange !== "undefined") {
  321. onItemCountChange(count + 1);
  322. }
  323. ref.current?.reload();
  324. }}
  325. />
  326. ) : undefined}
  327. </>
  328. );
  329. };
  330. export default DiscussionListCardWidget;