CourseMemberList.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import { useIntl } from "react-intl";
  2. import { Dropdown, Tag, message } from "antd";
  3. import { ActionType, ProList } from "@ant-design/pro-components";
  4. import { get } from "../../request";
  5. import AddMember from "./AddMember";
  6. import { useEffect, useRef, useState } from "react";
  7. import {
  8. ICourseDataResponse,
  9. ICourseMemberData,
  10. ICourseMemberListResponse,
  11. ICourseResponse,
  12. TCourseMemberAction,
  13. TCourseMemberStatus,
  14. actionMap,
  15. } from "../api/Course";
  16. import { ItemType } from "antd/lib/menu/hooks/useItems";
  17. import User, { IUser } from "../auth/User";
  18. import { getStatusColor, managerCanDo } from "./RolePower";
  19. import { ISetStatus, setStatus } from "./UserAction";
  20. import { IChannel } from "../channel/Channel";
  21. import CourseInvite from "./CourseInvite";
  22. interface IRoleTag {
  23. title: string;
  24. color: string;
  25. }
  26. export interface ICourseMember {
  27. sn?: number;
  28. id?: string;
  29. userId: string;
  30. user?: IUser;
  31. name?: string;
  32. tag?: IRoleTag[];
  33. image: string;
  34. role?: string;
  35. channel?: IChannel;
  36. startExp?: number;
  37. endExp?: number;
  38. currentExp?: number;
  39. expByDay?: number;
  40. status?: TCourseMemberStatus;
  41. }
  42. interface IWidget {
  43. courseId?: string;
  44. onSelect?: Function;
  45. }
  46. const CourseMemberListWidget = ({ courseId, onSelect }: IWidget) => {
  47. const intl = useIntl(); //i18n
  48. const [canManage, setCanManage] = useState(false);
  49. const [course, setCourse] = useState<ICourseDataResponse>();
  50. const ref = useRef<ActionType>();
  51. useEffect(() => {
  52. if (courseId) {
  53. const url = `/v2/course/${courseId}`;
  54. console.debug("course url", url);
  55. get<ICourseResponse>(url)
  56. .then((json) => {
  57. console.debug("course data", json.data);
  58. if (json.ok) {
  59. setCourse(json.data);
  60. }
  61. })
  62. .catch((e) => console.error(e));
  63. }
  64. }, [courseId]);
  65. return (
  66. <>
  67. <ProList<ICourseMember>
  68. actionRef={ref}
  69. search={{
  70. filterType: "light",
  71. }}
  72. onItem={(record: ICourseMember, index: number) => {
  73. return {
  74. onClick: (event) => {
  75. // 点击行
  76. if (typeof onSelect !== "undefined") {
  77. onSelect(record);
  78. }
  79. },
  80. };
  81. }}
  82. metas={{
  83. title: {
  84. dataIndex: "name",
  85. search: false,
  86. },
  87. avatar: {
  88. render(dom, entity, index, action, schema) {
  89. return <User {...entity.user} showName={false} />;
  90. },
  91. editable: false,
  92. },
  93. description: {
  94. dataIndex: "desc",
  95. search: false,
  96. render(dom, entity, index, action, schema) {
  97. return (
  98. <div>
  99. {"channel:"}
  100. {entity.channel?.name ?? "未绑定"}
  101. </div>
  102. );
  103. },
  104. },
  105. subTitle: {
  106. search: false,
  107. render: (
  108. dom: React.ReactNode,
  109. entity: ICourseMember,
  110. index: number
  111. ) => {
  112. return (
  113. <Tag>
  114. {intl.formatMessage({
  115. id: `auth.role.${entity.role}`,
  116. })}
  117. </Tag>
  118. );
  119. },
  120. },
  121. actions: {
  122. search: false,
  123. render: (text, row, index, action) => {
  124. const statusColor = getStatusColor(row.status);
  125. const actions: TCourseMemberAction[] = [
  126. "invite",
  127. "revoke",
  128. "accept",
  129. "reject",
  130. "block",
  131. ];
  132. /*
  133. const undo = {
  134. key: "undo",
  135. label: "撤销上次操作",
  136. disabled: !canUndo,
  137. };
  138. */
  139. const items: ItemType[] = actions.map((item) => {
  140. return {
  141. key: item,
  142. label: intl.formatMessage({
  143. id: `course.member.status.${item}.button`,
  144. }),
  145. disabled: !managerCanDo(
  146. item,
  147. course?.start_at,
  148. course?.end_at,
  149. course?.join,
  150. row.status,
  151. course?.sign_up_start_at,
  152. course?.sign_up_end_at
  153. ),
  154. };
  155. });
  156. return [
  157. <span style={{ color: statusColor }}>
  158. {intl.formatMessage({
  159. id: `course.member.status.${row.status}.label`,
  160. })}
  161. </span>,
  162. canManage ? (
  163. <Dropdown.Button
  164. key={index}
  165. type="link"
  166. menu={{
  167. items,
  168. onClick: (e) => {
  169. console.debug("click", e);
  170. const currAction = e.key as TCourseMemberAction;
  171. if (actions.includes(currAction)) {
  172. const newStatus = actionMap(currAction);
  173. if (newStatus) {
  174. const actionParam: ISetStatus = {
  175. courseMemberId: row.id,
  176. message: intl.formatMessage(
  177. {
  178. id: `course.member.status.${currAction}.message`,
  179. },
  180. { user: row.user?.nickName }
  181. ),
  182. status: newStatus,
  183. onSuccess: (data: ICourseMemberData) => {
  184. message.success(
  185. intl.formatMessage({ id: "flashes.success" })
  186. );
  187. ref.current?.reload();
  188. },
  189. };
  190. setStatus(actionParam);
  191. }
  192. }
  193. },
  194. }}
  195. >
  196. <></>
  197. </Dropdown.Button>
  198. ) : (
  199. <></>
  200. ),
  201. ];
  202. },
  203. },
  204. role: {
  205. // 自己扩展的字段,主要用于筛选,不在列表中显示
  206. title: "角色",
  207. valueType: "select",
  208. valueEnum: {
  209. all: {
  210. text: intl.formatMessage({
  211. id: "forms.fields.publicity.all.label",
  212. }),
  213. status: "Default",
  214. },
  215. student: {
  216. text: intl.formatMessage({
  217. id: "auth.role.student",
  218. }),
  219. status: "Default",
  220. },
  221. assistant: {
  222. text: intl.formatMessage({
  223. id: "auth.role.assistant",
  224. }),
  225. status: "Success",
  226. },
  227. },
  228. },
  229. }}
  230. request={async (params = {}, sorter, filter) => {
  231. console.log(params, sorter, filter);
  232. let url = `/v2/course-member?view=course&id=${courseId}`;
  233. const offset =
  234. ((params.current ? params.current : 1) - 1) *
  235. (params.pageSize ? params.pageSize : 20);
  236. url += `&limit=${params.pageSize}&offset=${offset}`;
  237. if (
  238. typeof params.keyword !== "undefined" &&
  239. params.keyword.trim() !== ""
  240. ) {
  241. url += "&search=" + params.keyword;
  242. }
  243. console.info("api request", url);
  244. const res = await get<ICourseMemberListResponse>(url);
  245. if (res.ok) {
  246. console.debug("api response", res.data);
  247. if (res.data.role === "owner" || res.data.role === "manager") {
  248. setCanManage(true);
  249. }
  250. const items: ICourseMember[] = res.data.rows.map((item, id) => {
  251. const member: ICourseMember = {
  252. sn: id + 1,
  253. id: item.id,
  254. userId: item.user_id,
  255. user: item.user,
  256. name: item.user?.nickName,
  257. role: item.role,
  258. status: item.status,
  259. channel: item.channel,
  260. tag: [],
  261. image: "",
  262. };
  263. return member;
  264. });
  265. console.log(items);
  266. return {
  267. total: res.data.count,
  268. succcess: true,
  269. data: items,
  270. };
  271. } else {
  272. console.error(res.message);
  273. return {
  274. total: 0,
  275. succcess: false,
  276. data: [],
  277. };
  278. }
  279. }}
  280. rowKey="id"
  281. bordered
  282. pagination={{
  283. showQuickJumper: true,
  284. showSizeChanger: true,
  285. }}
  286. options={{
  287. search: true,
  288. }}
  289. toolBarRender={() => [
  290. <CourseInvite
  291. courseId={courseId}
  292. onCreated={() => {
  293. ref.current?.reload();
  294. }}
  295. />,
  296. ]}
  297. />
  298. </>
  299. );
  300. };
  301. export default CourseMemberListWidget;