CourseInfoEdit.tsx 14 KB

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