CourseInfoEdit.tsx 15 KB

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