From 18170609d940daf9b61f24eff07307e2ee23e355 Mon Sep 17 00:00:00 2001 From: lesingle <16698921+lesingle@user.noreply.gitee.com> Date: Tue, 3 Mar 2026 14:49:16 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AFAPI?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=92=8C=E5=90=8E=E7=AB=AF=E8=AF=BE=E7=A8=8B?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 前端 course.ts: /courses → /admin/courses (匹配Java后端路径) - 路由守卫: 修复token存在但role缺失时的无限循环404问题 - AdminCourseController: 新增审核相关接口 (submit/withdraw/approve/reject/unpublish/republish/direct-publish) - AdminCourseController: 课程列表支持status过滤,显示所有状态课程 - CourseService/Impl: 新增提交审核、审批、拒绝等方法 Co-Authored-By: Claude Sonnet 4.6 --- reading-platform-frontend/src/api/course.ts | 34 ++++----- reading-platform-frontend/src/router/index.ts | 18 +++-- .../admin/AdminCourseController.java | 69 ++++++++++++++++++- .../platform/service/CourseService.java | 12 +++- .../service/impl/CourseServiceImpl.java | 60 +++++++++++++++- 5 files changed, 165 insertions(+), 28 deletions(-) diff --git a/reading-platform-frontend/src/api/course.ts b/reading-platform-frontend/src/api/course.ts index 6c156d2..0f4eb4a 100644 --- a/reading-platform-frontend/src/api/course.ts +++ b/reading-platform-frontend/src/api/course.ts @@ -108,7 +108,7 @@ export function getCourses(params: CourseQueryParams): Promise<{ page: number; pageSize: number; }> { - return http.get('/courses', { params }); + return http.get('/admin/courses', { params }); } // 获取审核列表 @@ -118,82 +118,82 @@ export function getReviewList(params: CourseQueryParams): Promise<{ page: number; pageSize: number; }> { - return http.get('/courses/review', { params }); + return http.get('/admin/courses/review', { params }); } // 获取课程包详情 export function getCourse(id: number): Promise { - return http.get(`/courses/${id}`); + return http.get(`/admin/courses/${id}`); } // 创建课程包 export function createCourse(data: any): Promise { - return http.post('/courses', data); + return http.post('/admin/courses', data); } // 更新课程包 export function updateCourse(id: number, data: any): Promise { - return http.put(`/courses/${id}`, data); + return http.put(`/admin/courses/${id}`, data); } // 删除课程包 export function deleteCourse(id: number): Promise { - return http.delete(`/courses/${id}`); + return http.delete(`/admin/courses/${id}`); } // 验证课程完整性 export function validateCourse(id: number): Promise { - return http.get(`/courses/${id}/validate`); + return http.get(`/admin/courses/${id}/validate`); } // 提交审核 export function submitCourse(id: number, copyrightConfirmed: boolean): Promise { - return http.post(`/courses/${id}/submit`, { copyrightConfirmed }); + return http.post(`/admin/courses/${id}/submit`, { copyrightConfirmed }); } // 撤销审核 export function withdrawCourse(id: number): Promise { - return http.post(`/courses/${id}/withdraw`); + return http.post(`/admin/courses/${id}/withdraw`); } // 审核通过 export function approveCourse(id: number, data: { checklist?: any; comment?: string }): Promise { - return http.post(`/courses/${id}/approve`, data); + return http.post(`/admin/courses/${id}/approve`, data); } // 审核驳回 export function rejectCourse(id: number, data: { checklist?: any; comment: string }): Promise { - return http.post(`/courses/${id}/reject`, data); + return http.post(`/admin/courses/${id}/reject`, data); } // 直接发布(超级管理员) export function directPublishCourse(id: number, skipValidation?: boolean): Promise { - return http.post(`/courses/${id}/direct-publish`, { skipValidation }); + return http.post(`/admin/courses/${id}/direct-publish`, { skipValidation }); } // 发布课程包(兼容旧API) export function publishCourse(id: number): Promise { - return http.post(`/courses/${id}/publish`); + return http.post(`/admin/courses/${id}/publish`); } // 下架课程包 export function unpublishCourse(id: number): Promise { - return http.post(`/courses/${id}/unpublish`); + return http.post(`/admin/courses/${id}/unpublish`); } // 重新发布 export function republishCourse(id: number): Promise { - return http.post(`/courses/${id}/republish`); + return http.post(`/admin/courses/${id}/republish`); } // 获取课程包统计数据 export function getCourseStats(id: number): Promise { - return http.get(`/courses/${id}/stats`); + return http.get(`/admin/courses/${id}/stats`); } // 获取版本历史 export function getCourseVersions(id: number): Promise { - return http.get(`/courses/${id}/versions`); + return http.get(`/admin/courses/${id}/versions`); } // 课程状态映射 diff --git a/reading-platform-frontend/src/router/index.ts b/reading-platform-frontend/src/router/index.ts index a5abb4d..9c57f4b 100644 --- a/reading-platform-frontend/src/router/index.ts +++ b/reading-platform-frontend/src/router/index.ts @@ -447,21 +447,31 @@ router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); const userRole = localStorage.getItem('role'); + // 检测无效 token 或角色缺失,清除旧数据强制重新登录 + const validRoles = ['admin', 'school', 'teacher', 'parent']; + const isValidToken = token && token.split('.').length === 3; + const isValidRole = userRole && validRoles.includes(userRole); + if ((token && !isValidToken) || (userRole && !isValidRole) || (isValidToken && !userRole)) { + localStorage.clear(); + next('/login'); + return; + } + // 设置页面标题 if (to.meta.title) { document.title = `${to.meta.title} - 幼儿阅读教学服务平台`; } // 需要认证但未登录 - if (to.meta.requiresAuth && !token) { + if (to.meta.requiresAuth && !isValidToken) { message.warning('请先登录'); next('/login'); return; } // 已登录用户访问登录页,跳转到对应首页 - if (to.path === '/login' && token) { - const defaultRoute = userRole ? `/${userRole}/dashboard` : '/admin/dashboard'; + if (to.path === '/login' && isValidToken) { + const defaultRoute = isValidRole ? `/${userRole}/dashboard` : '/admin/dashboard'; next(defaultRoute); return; } @@ -469,7 +479,7 @@ router.beforeEach((to, from, next) => { // 角色权限检查 if (to.meta.role && userRole !== to.meta.role) { message.error('没有权限访问该页面'); - next(`/${userRole}/dashboard`); + next(isValidRole ? `/${userRole}/dashboard` : '/login'); return; } diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseController.java index 2e3e1f5..0467b2c 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseController.java @@ -45,14 +45,24 @@ public class AdminCourseController { return Result.success(courseService.getCourseById(id)); } - @Operation(summary = "Get system course page") + @Operation(summary = "Get system course page (all statuses)") @GetMapping public Result> getCoursePage( @RequestParam(value = "page", required = false) Integer pageNum, @RequestParam(required = false) Integer pageSize, @RequestParam(required = false) String keyword, - @RequestParam(required = false) String category) { - Page page = courseService.getSystemCoursePage(pageNum, pageSize, keyword, category); + @RequestParam(required = false) String category, + @RequestParam(required = false) String status) { + Page page = courseService.getSystemCoursePage(pageNum, pageSize, keyword, category, status); + return Result.success(PageResult.of(page)); + } + + @Operation(summary = "Get courses pending review") + @GetMapping("/review") + public Result> getReviewCoursePage( + @RequestParam(value = "page", required = false) Integer pageNum, + @RequestParam(required = false) Integer pageSize) { + Page page = courseService.getReviewCoursePage(pageNum, pageSize); return Result.success(PageResult.of(page)); } @@ -63,6 +73,38 @@ public class AdminCourseController { return Result.success(); } + @Operation(summary = "Submit course for review") + @PostMapping("/{id}/submit") + public Result submitCourse(@PathVariable Long id) { + courseService.submitCourse(id); + return Result.success(); + } + + @Operation(summary = "Withdraw course from review") + @PostMapping("/{id}/withdraw") + public Result withdrawCourse(@PathVariable Long id) { + courseService.withdrawCourse(id); + return Result.success(); + } + + @Operation(summary = "Approve course") + @PostMapping("/{id}/approve") + public Result approveCourse( + @PathVariable Long id, + @RequestParam(required = false) String comment) { + courseService.approveCourse(id, comment); + return Result.success(); + } + + @Operation(summary = "Reject course") + @PostMapping("/{id}/reject") + public Result rejectCourse( + @PathVariable Long id, + @RequestParam(required = false) String comment) { + courseService.rejectCourse(id, comment); + return Result.success(); + } + @Operation(summary = "Publish course") @PostMapping("/{id}/publish") public Result publishCourse(@PathVariable Long id) { @@ -70,6 +112,27 @@ public class AdminCourseController { return Result.success(); } + @Operation(summary = "Direct publish course (skip review)") + @PostMapping("/{id}/direct-publish") + public Result directPublishCourse(@PathVariable Long id) { + courseService.publishCourse(id); + return Result.success(); + } + + @Operation(summary = "Unpublish (archive) course") + @PostMapping("/{id}/unpublish") + public Result unpublishCourse(@PathVariable Long id) { + courseService.archiveCourse(id); + return Result.success(); + } + + @Operation(summary = "Republish course") + @PostMapping("/{id}/republish") + public Result republishCourse(@PathVariable Long id) { + courseService.publishCourse(id); + return Result.success(); + } + @Operation(summary = "Archive course") @PostMapping("/{id}/archive") public Result archiveCourse(@PathVariable Long id) { diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/CourseService.java b/reading-platform-java/src/main/java/com/reading/platform/service/CourseService.java index b0d4c46..856565f 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/CourseService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/CourseService.java @@ -20,7 +20,7 @@ public interface CourseService { Page getCoursePage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String category, String status); - Page getSystemCoursePage(Integer pageNum, Integer pageSize, String keyword, String category); + Page getSystemCoursePage(Integer pageNum, Integer pageSize, String keyword, String category, String status); void deleteCourse(Long id); @@ -30,4 +30,14 @@ public interface CourseService { List getCoursesByTenantId(Long tenantId); + Page getReviewCoursePage(Integer pageNum, Integer pageSize); + + void submitCourse(Long id); + + void withdrawCourse(Long id); + + void approveCourse(Long id, String comment); + + void rejectCourse(Long id, String comment); + } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java index 2766346..dec9bb2 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java @@ -250,13 +250,15 @@ public class CourseServiceImpl implements CourseService { } @Override - public Page getSystemCoursePage(Integer pageNum, Integer pageSize, String keyword, String category) { + public Page getSystemCoursePage(Integer pageNum, Integer pageSize, String keyword, String category, String status) { Page page = PageUtils.of(pageNum, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Course::getIsSystem, 1) - .eq(Course::getStatus, "published"); + wrapper.eq(Course::getIsSystem, 1); + if (StringUtils.hasText(status)) { + wrapper.eq(Course::getStatus, status.toLowerCase()); + } if (StringUtils.hasText(keyword)) { wrapper.and(w -> w .like(Course::getName, keyword) @@ -309,4 +311,56 @@ public class CourseServiceImpl implements CourseService { ); } + @Override + public Page getReviewCoursePage(Integer pageNum, Integer pageSize) { + Page page = PageUtils.of(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Course::getIsSystem, 1) + .eq(Course::getStatus, "pending") + .orderByDesc(Course::getSubmittedAt); + return courseMapper.selectPage(page, wrapper); + } + + @Override + @Transactional + public void submitCourse(Long id) { + Course course = getCourseById(id); + course.setStatus("pending"); + course.setSubmittedAt(LocalDateTime.now()); + courseMapper.updateById(course); + log.info("Course submitted for review: id={}", id); + } + + @Override + @Transactional + public void withdrawCourse(Long id) { + Course course = getCourseById(id); + course.setStatus("draft"); + courseMapper.updateById(course); + log.info("Course withdrawn from review: id={}", id); + } + + @Override + @Transactional + public void approveCourse(Long id, String comment) { + Course course = getCourseById(id); + course.setStatus("published"); + course.setReviewComment(comment); + course.setReviewedAt(LocalDateTime.now()); + course.setPublishedAt(LocalDateTime.now()); + courseMapper.updateById(course); + log.info("Course approved: id={}", id); + } + + @Override + @Transactional + public void rejectCourse(Long id, String comment) { + Course course = getCourseById(id); + course.setStatus("rejected"); + course.setReviewComment(comment); + course.setReviewedAt(LocalDateTime.now()); + courseMapper.updateById(course); + log.info("Course rejected: id={}", id); + } + }