course.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // /src/api/course.ts
  2. import type { LoaderFunctionArgs } from "react-router";
  3. import { get } from "../request";
  4. import type { IStudio, IUser } from "./Auth";
  5. import type { IChannel } from "./channel";
  6. export interface ICourseListApiResponse {
  7. article: string;
  8. title: string;
  9. level: string;
  10. children: number;
  11. }
  12. export interface ICourseDataRequest {
  13. id?: string; //课程ID
  14. title: string; //标题
  15. subtitle?: string; //副标题
  16. summary?: string; //副标题
  17. content?: string | null;
  18. sign_up_message?: string | null;
  19. cover?: string; //封面图片文件名
  20. teacher_id?: string; //UserID
  21. publicity: number; //类型-公开/内部
  22. anthology_id?: string; //文集ID
  23. channel_id?: string; //标准答案channel
  24. start_at?: string; //课程开始时间
  25. end_at?: string; //课程结束时间
  26. sign_up_start_at: string | null; //报名开始时间
  27. sign_up_end_at: string | null; //报名结束时间
  28. join: string;
  29. request_exp: string;
  30. number: number;
  31. }
  32. export type TCourseRole =
  33. | "owner"
  34. | "teacher"
  35. | "manager"
  36. | "assistant"
  37. | "student";
  38. export type TCourseJoinMode = "invite" | "manual" | "open";
  39. export type TCourseExpRequest = "none" | "begin-end" | "daily";
  40. export interface IMember {
  41. role: TCourseRole;
  42. status: TCourseMemberStatus;
  43. }
  44. export interface ICourseDataResponse {
  45. id: string; //课程ID
  46. title: string; //标题
  47. subtitle: string; //副标题
  48. summary?: string; //副标题
  49. sign_up_message?: string | null; //报名弹窗消息
  50. teacher?: IUser; //UserID
  51. course_count?: number; //课程数
  52. publicity: number; //类型-公开/内部
  53. anthology_id?: string; //文集ID
  54. anthology_title?: string; //文集标题
  55. anthology_owner?: IStudio; //文集拥有者
  56. channel_id: string; //标准答案ID
  57. channel_name?: string; //文集标题
  58. channel_owner?: IStudio; //文集拥有者
  59. studio?: IStudio; //课程拥有者
  60. start_at: string; //课程开始时间
  61. end_at: string; //课程结束时间
  62. sign_up_start_at: string; //报名开始时间
  63. sign_up_end_at: string; //报名结束时间
  64. content: string; //简介
  65. cover: string; //封面图片文件名
  66. cover_url?: string[]; //封面图片文件名
  67. member_count: number;
  68. join: TCourseJoinMode; //报名方式
  69. request_exp: TCourseExpRequest;
  70. my_status?: TCourseMemberStatus;
  71. my_status_id?: string;
  72. count_progressing?: number;
  73. number: number;
  74. members?: IMember[];
  75. my_role?: TCourseRole;
  76. created_at: string; //创建时间
  77. updated_at: string; //修改时间
  78. }
  79. export interface ICourseResponse {
  80. ok: boolean;
  81. message: string;
  82. data: ICourseDataResponse;
  83. }
  84. export interface ICourseListResponse {
  85. ok: boolean;
  86. message: string;
  87. data: {
  88. rows: ICourseDataResponse[];
  89. count: number;
  90. };
  91. }
  92. export interface ICourseCreateRequest {
  93. title: string;
  94. lang: string;
  95. studio: string;
  96. }
  97. export interface IAnthologyCreateRequest {
  98. title: string;
  99. lang: string;
  100. studio: string;
  101. }
  102. export interface ICourseNumberResponse {
  103. ok: boolean;
  104. message: string;
  105. data: {
  106. create: number;
  107. teach: number;
  108. study: number;
  109. };
  110. }
  111. export type TCourseMemberStatus =
  112. | "none" /*无*/
  113. | "normal" /*开放课程直接加入*/
  114. | "joined" /*开放课程已经加入*/
  115. | "applied" /**学生已经报名 管理员尚未审核 */
  116. | "canceled" /**学生取消报名 */
  117. | "agreed" /**学生/助教已经接受邀请 */
  118. | "disagreed" /**学生/助教已经拒绝邀请 */
  119. | "left" /**学生自己退出 */
  120. | "invited" /**管理员已经邀请学生加入 */
  121. | "revoked" /**管理员撤销邀请 */
  122. | "accepted" /**已经被管理员录取 */
  123. | "rejected" /**报名已经被管理员拒绝 */
  124. | "blocked"; /**被管理员清退 */
  125. export type TCourseMemberAction =
  126. | "join" /*加入自学课程*/
  127. | "apply" /**学生报名 */
  128. | "cancel" /**学生取消报名 */
  129. | "agree" /**学生/助教接受邀请 */
  130. | "disagree" /**学生/助教拒绝邀请 */
  131. | "leave" /**学生/助教自己退出 */
  132. | "invite" /**管理员邀请学生加入 */
  133. | "revoke" /**管理员撤销邀请 */
  134. | "accept" /**管理员录取 */
  135. | "reject" /**管理员拒绝 */
  136. | "block"; /**管理员清退 */
  137. interface IActionMap {
  138. action: TCourseMemberAction;
  139. status: TCourseMemberStatus;
  140. }
  141. export const actionMap = (action: TCourseMemberAction) => {
  142. const data: IActionMap[] = [
  143. { action: "join", status: "joined" },
  144. { action: "apply", status: "applied" },
  145. { action: "cancel", status: "canceled" },
  146. { action: "agree", status: "agreed" },
  147. { action: "disagree", status: "disagreed" },
  148. { action: "leave", status: "left" },
  149. { action: "invite", status: "invited" },
  150. { action: "revoke", status: "revoked" },
  151. { action: "accept", status: "accepted" },
  152. { action: "reject", status: "rejected" },
  153. { action: "block", status: "blocked" },
  154. ];
  155. const current = data.find((value) => value.action === action);
  156. return current?.status;
  157. };
  158. export interface ICourseMemberData {
  159. id?: string;
  160. user_id: string;
  161. course_id: string;
  162. course?: ICourseDataResponse;
  163. channel_id?: string;
  164. channel?: IChannel;
  165. role?: TCourseRole;
  166. operating?: "invite" | "sign_up";
  167. user?: IUser;
  168. editor?: IUser;
  169. status?: TCourseMemberStatus;
  170. created_at?: string;
  171. updated_at?: string;
  172. }
  173. export interface ICourseMemberResponse {
  174. ok: boolean;
  175. message: string;
  176. data: ICourseMemberData;
  177. }
  178. export interface ICourseMemberListResponse {
  179. ok: boolean;
  180. message: string;
  181. data: {
  182. rows: ICourseMemberData[];
  183. role: TCourseRole;
  184. count: number;
  185. };
  186. }
  187. export interface ICourseMemberDeleteResponse {
  188. ok: boolean;
  189. message: string;
  190. data: boolean;
  191. }
  192. export interface ICourseUser {
  193. role: TCourseRole;
  194. channel_id?: string | null;
  195. }
  196. export interface ICourseCurrUserResponse {
  197. ok: boolean;
  198. message: string;
  199. data: ICourseUser;
  200. }
  201. export interface IExerciseListData {
  202. user: IUser;
  203. wbw: number;
  204. translation: number;
  205. question: number;
  206. html: string;
  207. }
  208. export interface ICourseExerciseResponse {
  209. ok: boolean;
  210. message: string;
  211. data: {
  212. rows: IExerciseListData[];
  213. count: number;
  214. };
  215. }
  216. export const fetchCourse = (courseId: string): Promise<ICourseResponse> => {
  217. return get<ICourseResponse>(`/api/v2/course/${courseId}`);
  218. };
  219. export async function courseLoader({ params }: LoaderFunctionArgs) {
  220. const courseId = params.courseId;
  221. if (!courseId) {
  222. throw new Response("Missing courseId", { status: 400 });
  223. }
  224. const res = await fetchCourse(courseId);
  225. if (!res.ok) {
  226. throw new Response("Channel not found", { status: 404 });
  227. }
  228. return res.data;
  229. }