Compare commits

..

2 Commits

Author SHA1 Message Date
lesingle
01897a7ecc Merge branch 'main' of http://8.148.151.56:3000/tonytech/kindergarten_java 2026-03-03 14:50:42 +08:00
lesingle
18170609d9 fix: 修复前端API路径和后端课程管理接口
- 前端 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 <noreply@anthropic.com>
2026-03-03 14:49:16 +08:00
5 changed files with 165 additions and 28 deletions

View File

@ -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<any> {
return http.get(`/courses/${id}`);
return http.get(`/admin/courses/${id}`);
}
// 创建课程包
export function createCourse(data: any): Promise<any> {
return http.post('/courses', data);
return http.post('/admin/courses', data);
}
// 更新课程包
export function updateCourse(id: number, data: any): Promise<any> {
return http.put(`/courses/${id}`, data);
return http.put(`/admin/courses/${id}`, data);
}
// 删除课程包
export function deleteCourse(id: number): Promise<any> {
return http.delete(`/courses/${id}`);
return http.delete(`/admin/courses/${id}`);
}
// 验证课程完整性
export function validateCourse(id: number): Promise<ValidationResult> {
return http.get(`/courses/${id}/validate`);
return http.get(`/admin/courses/${id}/validate`);
}
// 提交审核
export function submitCourse(id: number, copyrightConfirmed: boolean): Promise<any> {
return http.post(`/courses/${id}/submit`, { copyrightConfirmed });
return http.post(`/admin/courses/${id}/submit`, { copyrightConfirmed });
}
// 撤销审核
export function withdrawCourse(id: number): Promise<any> {
return http.post(`/courses/${id}/withdraw`);
return http.post(`/admin/courses/${id}/withdraw`);
}
// 审核通过
export function approveCourse(id: number, data: { checklist?: any; comment?: string }): Promise<any> {
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<any> {
return http.post(`/courses/${id}/reject`, data);
return http.post(`/admin/courses/${id}/reject`, data);
}
// 直接发布(超级管理员)
export function directPublishCourse(id: number, skipValidation?: boolean): Promise<any> {
return http.post(`/courses/${id}/direct-publish`, { skipValidation });
return http.post(`/admin/courses/${id}/direct-publish`, { skipValidation });
}
// 发布课程包兼容旧API
export function publishCourse(id: number): Promise<any> {
return http.post(`/courses/${id}/publish`);
return http.post(`/admin/courses/${id}/publish`);
}
// 下架课程包
export function unpublishCourse(id: number): Promise<any> {
return http.post(`/courses/${id}/unpublish`);
return http.post(`/admin/courses/${id}/unpublish`);
}
// 重新发布
export function republishCourse(id: number): Promise<any> {
return http.post(`/courses/${id}/republish`);
return http.post(`/admin/courses/${id}/republish`);
}
// 获取课程包统计数据
export function getCourseStats(id: number): Promise<any> {
return http.get(`/courses/${id}/stats`);
return http.get(`/admin/courses/${id}/stats`);
}
// 获取版本历史
export function getCourseVersions(id: number): Promise<any[]> {
return http.get(`/courses/${id}/versions`);
return http.get(`/admin/courses/${id}/versions`);
}
// 课程状态映射

View File

@ -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;
}

View File

@ -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<PageResult<Course>> getCoursePage(
@RequestParam(value = "page", required = false) Integer pageNum,
@RequestParam(required = false) Integer pageSize,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String category) {
Page<Course> page = courseService.getSystemCoursePage(pageNum, pageSize, keyword, category);
@RequestParam(required = false) String category,
@RequestParam(required = false) String status) {
Page<Course> page = courseService.getSystemCoursePage(pageNum, pageSize, keyword, category, status);
return Result.success(PageResult.of(page));
}
@Operation(summary = "Get courses pending review")
@GetMapping("/review")
public Result<PageResult<Course>> getReviewCoursePage(
@RequestParam(value = "page", required = false) Integer pageNum,
@RequestParam(required = false) Integer pageSize) {
Page<Course> 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<Void> submitCourse(@PathVariable Long id) {
courseService.submitCourse(id);
return Result.success();
}
@Operation(summary = "Withdraw course from review")
@PostMapping("/{id}/withdraw")
public Result<Void> withdrawCourse(@PathVariable Long id) {
courseService.withdrawCourse(id);
return Result.success();
}
@Operation(summary = "Approve course")
@PostMapping("/{id}/approve")
public Result<Void> 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<Void> 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<Void> 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<Void> directPublishCourse(@PathVariable Long id) {
courseService.publishCourse(id);
return Result.success();
}
@Operation(summary = "Unpublish (archive) course")
@PostMapping("/{id}/unpublish")
public Result<Void> unpublishCourse(@PathVariable Long id) {
courseService.archiveCourse(id);
return Result.success();
}
@Operation(summary = "Republish course")
@PostMapping("/{id}/republish")
public Result<Void> republishCourse(@PathVariable Long id) {
courseService.publishCourse(id);
return Result.success();
}
@Operation(summary = "Archive course")
@PostMapping("/{id}/archive")
public Result<Void> archiveCourse(@PathVariable Long id) {

View File

@ -20,7 +20,7 @@ public interface CourseService {
Page<Course> getCoursePage(Long tenantId, Integer pageNum, Integer pageSize, String keyword, String category, String status);
Page<Course> getSystemCoursePage(Integer pageNum, Integer pageSize, String keyword, String category);
Page<Course> getSystemCoursePage(Integer pageNum, Integer pageSize, String keyword, String category, String status);
void deleteCourse(Long id);
@ -30,4 +30,14 @@ public interface CourseService {
List<Course> getCoursesByTenantId(Long tenantId);
Page<Course> 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);
}

View File

@ -250,13 +250,15 @@ public class CourseServiceImpl implements CourseService {
}
@Override
public Page<Course> getSystemCoursePage(Integer pageNum, Integer pageSize, String keyword, String category) {
public Page<Course> getSystemCoursePage(Integer pageNum, Integer pageSize, String keyword, String category, String status) {
Page<Course> page = PageUtils.of(pageNum, pageSize);
LambdaQueryWrapper<Course> 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<Course> getReviewCoursePage(Integer pageNum, Integer pageSize) {
Page<Course> page = PageUtils.of(pageNum, pageSize);
LambdaQueryWrapper<Course> 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);
}
}