CourseInfoEdit.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import { useState } from "react";
  2. import { useIntl } from "react-intl";
  3. import {
  4. ProForm,
  5. ProFormText,
  6. ProFormDateRangePicker,
  7. ProFormSelect,
  8. ProFormUploadButton,
  9. RequestOptionsType,
  10. ProFormDependency,
  11. } from "@ant-design/pro-components";
  12. import { message, Form } from "antd";
  13. import { get as getToken } from "../../reducers/current-user";
  14. import { API_HOST, get, put } from "../../request";
  15. import {
  16. ICourseDataRequest,
  17. ICourseDataResponse,
  18. ICourseResponse,
  19. } from "../../components/api/Course";
  20. import PublicitySelect from "../../components/studio/PublicitySelect";
  21. import { IUserListResponse } from "../../components/api/Auth";
  22. import MDEditor from "@uiw/react-md-editor";
  23. import { DefaultOptionType } from "antd/lib/select";
  24. import { UploadChangeParam, UploadFile } from "antd/es/upload/interface";
  25. import { IAttachmentResponse } from "../../components/api/Attachments";
  26. import { IAnthologyListResponse } from "../../components/api/Article";
  27. import { IApiResponseChannelList } from "../../components/api/Channel";
  28. interface IFormData {
  29. title: string;
  30. subtitle: string;
  31. summary?: string;
  32. content?: string;
  33. cover?: UploadFile<IAttachmentResponse>[];
  34. teacherId?: string;
  35. anthologyId?: string;
  36. channelId?: string;
  37. dateRange?: Date[];
  38. status: number;
  39. join: string;
  40. exp: string;
  41. }
  42. interface IWidget {
  43. studioName?: string;
  44. courseId?: string;
  45. onTitleChange?: Function;
  46. }
  47. const CourseInfoEditWidget = ({
  48. studioName,
  49. courseId,
  50. onTitleChange,
  51. }: IWidget) => {
  52. const intl = useIntl();
  53. const [teacherOption, setTeacherOption] = useState<DefaultOptionType[]>([]);
  54. const [currTeacher, setCurrTeacher] = useState<RequestOptionsType>();
  55. const [textbookOption, setTextbookOption] = useState<DefaultOptionType[]>([]);
  56. const [currTextbook, setCurrTextbook] = useState<RequestOptionsType>();
  57. const [channelOption, setChannelOption] = useState<DefaultOptionType[]>([]);
  58. const [currChannel, setCurrChannel] = useState<RequestOptionsType>();
  59. const [courseData, setCourseData] = useState<ICourseDataResponse>();
  60. return (
  61. <div>
  62. <ProForm<IFormData>
  63. formKey="course_edit"
  64. onFinish={async (values: IFormData) => {
  65. console.log("all data", values);
  66. let startAt: string, endAt: string;
  67. let _cover: string = "";
  68. if (typeof values.dateRange === "undefined") {
  69. startAt = "";
  70. endAt = "";
  71. } else if (
  72. typeof values.dateRange[0] === "string" &&
  73. typeof values.dateRange[1] === "string"
  74. ) {
  75. startAt = values.dateRange[0];
  76. endAt = values.dateRange[1];
  77. } else {
  78. startAt = courseData ? courseData.start_at : "";
  79. endAt = courseData ? courseData.end_at : "";
  80. }
  81. if (
  82. typeof values.cover === "undefined" ||
  83. values.cover.length === 0
  84. ) {
  85. _cover = "";
  86. } else if (typeof values.cover[0].response === "undefined") {
  87. _cover = values.cover[0].uid;
  88. } else {
  89. console.debug("upload ", values.cover[0].response);
  90. _cover = values.cover[0].response.data.name;
  91. }
  92. const res = await put<ICourseDataRequest, ICourseResponse>(
  93. `/v2/course/${courseId}`,
  94. {
  95. title: values.title, //标题
  96. subtitle: values.subtitle, //副标题
  97. summary: values.summary,
  98. content: values.content, //简介
  99. cover: _cover, //封面图片文件名
  100. teacher_id: values.teacherId, //UserID
  101. publicity: values.status, //类型-公开/内部
  102. anthology_id: values.anthologyId, //文集ID
  103. channel_id: values.channelId,
  104. start_at: startAt, //课程开始时间
  105. end_at: endAt, //课程结束时间
  106. join: values.join,
  107. request_exp: values.exp,
  108. }
  109. );
  110. console.log(res);
  111. if (res.ok) {
  112. message.success(intl.formatMessage({ id: "flashes.success" }));
  113. } else {
  114. message.error(res.message);
  115. }
  116. }}
  117. request={async () => {
  118. const res = await get<ICourseResponse>(`/v2/course/${courseId}`);
  119. console.log("course data", res.data);
  120. setCourseData(res.data);
  121. if (typeof onTitleChange !== "undefined") {
  122. onTitleChange(res.data.title);
  123. }
  124. console.log(res.data);
  125. if (res.data.teacher) {
  126. console.log("teacher", res.data.teacher);
  127. const teacher = {
  128. value: res.data.teacher.id,
  129. label: res.data.teacher.nickName,
  130. };
  131. setCurrTeacher(teacher);
  132. setTeacherOption([teacher]);
  133. const textbook = {
  134. value: res.data.anthology_id,
  135. label:
  136. res.data.anthology_owner?.nickName +
  137. "/" +
  138. res.data.anthology_title,
  139. };
  140. setCurrTextbook(textbook);
  141. setTextbookOption([textbook]);
  142. const channel = {
  143. value: res.data.channel_id,
  144. label:
  145. res.data.channel_owner?.nickName + "/" + res.data.channel_name,
  146. };
  147. setCurrChannel(channel);
  148. setChannelOption([channel]);
  149. }
  150. return {
  151. title: res.data.title,
  152. subtitle: res.data.subtitle,
  153. summary: res.data.summary,
  154. content: res.data.content,
  155. cover: res.data.cover
  156. ? [
  157. {
  158. uid: res.data.cover,
  159. name: "cover",
  160. thumbUrl:
  161. res.data.cover_url && res.data.cover_url.length > 1
  162. ? res.data.cover_url[1]
  163. : undefined,
  164. },
  165. ]
  166. : [],
  167. teacherId: res.data.teacher?.id,
  168. anthologyId: res.data.anthology_id,
  169. channelId: res.data.channel_id,
  170. dateRange:
  171. res.data.start_at && res.data.end_at
  172. ? [new Date(res.data.start_at), new Date(res.data.end_at)]
  173. : undefined,
  174. status: res.data.publicity,
  175. join: res.data.join,
  176. exp: res.data.request_exp,
  177. };
  178. }}
  179. >
  180. <ProForm.Group>
  181. <ProFormUploadButton
  182. name="cover"
  183. label="封面"
  184. max={1}
  185. fieldProps={{
  186. name: "file",
  187. listType: "picture-card",
  188. className: "avatar-uploader",
  189. headers: {
  190. Authorization: `Bearer ${getToken()}`,
  191. },
  192. onRemove: (file: UploadFile<any>): boolean => {
  193. console.log("remove", file);
  194. return true;
  195. },
  196. }}
  197. action={`${API_HOST}/api/v2/attachment`}
  198. extra="封面必须为正方形。最大512*512"
  199. onChange={(info: UploadChangeParam<UploadFile<any>>) => {}}
  200. />
  201. </ProForm.Group>
  202. <ProForm.Group>
  203. <ProFormText
  204. width="md"
  205. name="title"
  206. required
  207. label={intl.formatMessage({
  208. id: "forms.fields.title.label",
  209. })}
  210. rules={[
  211. {
  212. required: true,
  213. },
  214. ]}
  215. />
  216. <ProFormText
  217. width="md"
  218. name="subtitle"
  219. label={intl.formatMessage({
  220. id: "forms.fields.subtitle.label",
  221. })}
  222. />
  223. </ProForm.Group>
  224. <ProForm.Group>
  225. <ProFormSelect
  226. options={teacherOption}
  227. width="md"
  228. name="teacherId"
  229. label={intl.formatMessage({ id: "forms.fields.teacher.label" })}
  230. showSearch
  231. debounceTime={300}
  232. request={async ({ keyWords }) => {
  233. console.log("keyWord", keyWords);
  234. if (typeof keyWords === "undefined") {
  235. return currTeacher ? [currTeacher] : [];
  236. }
  237. const json = await get<IUserListResponse>(
  238. `/v2/user?view=key&key=${keyWords}`
  239. );
  240. const userList = json.data.rows.map((item) => {
  241. return {
  242. value: item.id,
  243. label: `${item.userName}-${item.nickName}`,
  244. };
  245. });
  246. console.log("json", userList);
  247. return userList;
  248. }}
  249. placeholder={intl.formatMessage({
  250. id: "forms.fields.teacher.label",
  251. })}
  252. />
  253. <ProFormDateRangePicker
  254. width="md"
  255. name="dateRange"
  256. label="课程区间"
  257. />
  258. </ProForm.Group>
  259. <ProForm.Group>
  260. <ProFormSelect
  261. options={textbookOption}
  262. width="md"
  263. name="anthologyId"
  264. label={intl.formatMessage({ id: "forms.fields.textbook.label" })}
  265. showSearch
  266. debounceTime={300}
  267. request={async ({ keyWords }) => {
  268. console.log("keyWord", keyWords);
  269. if (typeof keyWords === "undefined") {
  270. return currTextbook ? [currTextbook] : [];
  271. }
  272. const json = await get<IAnthologyListResponse>(
  273. `/v2/anthology?view=public`
  274. );
  275. const textbookList = json.data.rows.map((item) => {
  276. return {
  277. value: item.uid,
  278. label: `${item.studio.nickName}/${item.title}`,
  279. };
  280. });
  281. console.log("json", textbookList);
  282. return textbookList;
  283. }}
  284. />
  285. <ProFormSelect
  286. options={channelOption}
  287. width="md"
  288. name="channelId"
  289. label={"标准答案"}
  290. showSearch
  291. debounceTime={300}
  292. request={async ({ keyWords }) => {
  293. console.log("keyWord", keyWords);
  294. if (typeof keyWords === "undefined") {
  295. return currChannel ? [currChannel] : [];
  296. }
  297. const json = await get<IApiResponseChannelList>(
  298. `/v2/channel?view=studio&name=${studioName}`
  299. );
  300. const textbookList = json.data.rows.map((item) => {
  301. return {
  302. value: item.uid,
  303. label: `${item.studio.nickName}/${item.name}`,
  304. };
  305. });
  306. console.log("json", textbookList);
  307. return textbookList;
  308. }}
  309. />
  310. </ProForm.Group>
  311. <ProForm.Group>
  312. <PublicitySelect width="md" />
  313. <ProFormDependency name={["status"]}>
  314. {({ status }) => {
  315. const option = [
  316. {
  317. value: "invite",
  318. label: intl.formatMessage({
  319. id: "course.join.mode.invite.label",
  320. }),
  321. disabled: false,
  322. },
  323. {
  324. value: "manual",
  325. label: intl.formatMessage({
  326. id: "course.join.mode.manual.label",
  327. }),
  328. disabled: false,
  329. },
  330. {
  331. value: "open",
  332. label: intl.formatMessage({
  333. id: "course.join.mode.open.label",
  334. }),
  335. disabled: false,
  336. },
  337. ];
  338. if (status === 10) {
  339. option[1].disabled = true;
  340. option[2].disabled = true;
  341. } else {
  342. option[0].disabled = true;
  343. }
  344. return (
  345. <ProFormSelect
  346. options={option}
  347. width="md"
  348. name="join"
  349. allowClear={false}
  350. label="录取方式"
  351. />
  352. );
  353. }}
  354. </ProFormDependency>
  355. </ProForm.Group>
  356. <ProForm.Group>
  357. <ProFormDependency name={["join"]}>
  358. {({ join }) => {
  359. const option = [
  360. {
  361. value: "none",
  362. label: intl.formatMessage({
  363. id: "course.exp.request.none.label",
  364. }),
  365. disabled: false,
  366. },
  367. {
  368. value: "begin-end",
  369. label: intl.formatMessage({
  370. id: "course.exp.request.begin-end.label",
  371. }),
  372. disabled: false,
  373. },
  374. {
  375. value: "daily",
  376. label: intl.formatMessage({
  377. id: "course.exp.request.daily.label",
  378. }),
  379. disabled: false,
  380. },
  381. ];
  382. if (join === "open") {
  383. option[1].disabled = true;
  384. option[2].disabled = true;
  385. }
  386. return (
  387. <ProFormSelect
  388. tooltip="要求查看经验值,需要学生同意才会生效。"
  389. options={option}
  390. width="md"
  391. name="exp"
  392. label="查看学生经验值"
  393. allowClear={false}
  394. />
  395. );
  396. }}
  397. </ProFormDependency>
  398. </ProForm.Group>
  399. <ProForm.Group>
  400. <Form.Item
  401. name="content"
  402. label={intl.formatMessage({ id: "forms.fields.content.label" })}
  403. >
  404. <MDEditor />
  405. </Form.Item>
  406. </ProForm.Group>
  407. </ProForm>
  408. </div>
  409. );
  410. };
  411. export default CourseInfoEditWidget;