diff --git a/Front/vue-unilife/src/api/index.ts b/Front/vue-unilife/src/api/index.ts index 82f5fa6..74f74be 100644 --- a/Front/vue-unilife/src/api/index.ts +++ b/Front/vue-unilife/src/api/index.ts @@ -1,5 +1,11 @@ import userApi from './user'; +import postApi from './post'; +import resourceApi from './resource'; +import scheduleApi from './schedule'; export { - userApi + userApi, + postApi, + resourceApi, + scheduleApi }; diff --git a/Front/vue-unilife/src/api/resource.ts b/Front/vue-unilife/src/api/resource.ts new file mode 100644 index 0000000..da2f1d6 --- /dev/null +++ b/Front/vue-unilife/src/api/resource.ts @@ -0,0 +1,102 @@ +import { get, post, put, del } from './request'; + +// 资源类型定义 +export interface ResourceItem { + id: number; + title: string; + description?: string; + fileUrl: string; + fileSize: number; + fileType: string; + userId: number; + nickname: string; + avatar?: string; + categoryId: number; + categoryName: string; + downloadCount: number; + likeCount: number; + isLiked?: boolean; + createdAt: string; + updatedAt: string; +} + +export interface ResourceCategory { + id: number; + name: string; + description?: string; + icon?: string; +} + +export interface ResourceParams { + page?: number; + size?: number; + categoryId?: number; + keyword?: string; + userId?: number; +} + +export interface UploadResourceParams { + title: string; + description?: string; + categoryId: number; + file: File; +} + +export interface UpdateResourceParams { + title?: string; + description?: string; + categoryId?: number; +} + +// 资源API +export default { + // 获取资源列表 + getResources(params: ResourceParams = {}) { + return get<{ code: number; data: { total: number; list: ResourceItem[]; pages: number } }>('/resources', params); + }, + + // 获取资源详情 + getResourceDetail(id: number) { + return get<{ code: number; data: ResourceItem }>(`/resources/${id}`); + }, + + // 上传资源 + uploadResource(data: FormData) { + return post<{ code: number; data: { resourceId: number } }>('/resources', data); + }, + + // 更新资源信息 + updateResource(id: number, data: UpdateResourceParams) { + return put<{ code: number; message: string }>(`/resources/${id}`, data); + }, + + // 删除资源 + deleteResource(id: number) { + return del<{ code: number; message: string }>(`/resources/${id}`); + }, + + // 下载资源 + downloadResource(id: number) { + return get<{ code: number; data: { fileUrl: string; fileName: string; fileType: string } }>(`/resources/${id}/download`); + }, + + // 点赞/取消点赞资源 + likeResource(id: number) { + return post<{ code: number; message: string }>(`/resources/${id}/like`); + }, + + // 获取用户上传的资源列表 + getUserResources(userId: number) { + return get<{ code: number; data: { total: number; list: ResourceItem[]; pages: number } }>(`/resources/user/${userId}`); + }, + + // 获取当前用户上传的资源列表 + getMyResources(params: { page?: number; size?: number } = {}) { + return get<{ code: number; data: { total: number; list: ResourceItem[]; pages: number } }>('/resources/my', params); + }, + + // 获取资源分类 + getResourceCategories() { + return get<{ code: number; data: { list: ResourceCategory[]; total: number } }>('/categories'); + } +}; diff --git a/Front/vue-unilife/src/api/schedule.ts b/Front/vue-unilife/src/api/schedule.ts new file mode 100644 index 0000000..bfe2577 --- /dev/null +++ b/Front/vue-unilife/src/api/schedule.ts @@ -0,0 +1,145 @@ +import { get, post, put, del } from './request'; + +// 课程类型定义 +export interface CourseItem { + id: number; + userId: number; + name: string; + teacher?: string; + location?: string; + dayOfWeek: number; // 1-7 表示周一到周日 + startTime: string; // 格式: "08:00:00" + endTime: string; // 格式: "09:40:00" + startWeek: number; // 开始周次 + endWeek: number; // 结束周次 + color?: string; // 颜色,如 "#4CAF50" + status: number; // 状态(0-删除, 1-正常) + createdAt: string; + updatedAt: string; +} + +// 日程类型定义 +export interface ScheduleItem { + id: number; + userId: number; + title: string; + description?: string; + startTime: string; // 格式: "2023-05-10T14:00:00" + endTime: string; // 格式: "2023-05-10T16:00:00" + location?: string; + isAllDay: number; // 是否全天(0-否, 1-是) + reminder?: number; // 提醒时间(分钟) + color?: string; // 颜色,如 "#FF5722" + status: number; // 状态(0-删除, 1-正常) + createdAt: string; + updatedAt: string; +} + +export interface CreateCourseParams { + name: string; + teacher?: string; + location?: string; + dayOfWeek: number; + startTime: string; + endTime: string; + startWeek: number; + endWeek: number; + color?: string; +} + +export interface UpdateCourseParams extends Partial {} + +export interface CreateScheduleParams { + title: string; + description?: string; + startTime: string; + endTime: string; + location?: string; + isAllDay?: number; + reminder?: number; + color?: string; +} + +export interface UpdateScheduleParams extends Partial {} + +export interface ConflictCheckResult { + hasConflict: boolean; + conflictCount: number; +} + +// 课程表与日程管理API +export default { + // 课程相关API + + // 创建课程 + createCourse(data: CreateCourseParams) { + return post<{ code: number; data: { courseId: number } }>('/courses', data); + }, + + // 获取课程详情 + getCourseDetail(id: number) { + return get<{ code: number; data: CourseItem }>(`/courses/${id}`); + }, + + // 获取用户的所有课程 + getAllCourses() { + return get<{ code: number; data: { total: number; list: CourseItem[] } }>('/courses'); + }, + + // 获取用户在指定星期几的课程 + getCoursesByDay(dayOfWeek: number) { + return get<{ code: number; data: { total: number; list: CourseItem[] } }>(`/courses/day/${dayOfWeek}`); + }, + + // 更新课程 + updateCourse(id: number, data: UpdateCourseParams) { + return put<{ code: number; message: string }>(`/courses/${id}`, data); + }, + + // 删除课程 + deleteCourse(id: number) { + return del<{ code: number; message: string }>(`/courses/${id}`); + }, + + // 检查课程时间冲突 + checkCourseConflict(params: { dayOfWeek: number; startTime: string; endTime: string; excludeCourseId?: number }) { + return get<{ code: number; data: ConflictCheckResult }>('/courses/check-conflict', params); + }, + + // 日程相关API + + // 创建日程 + createSchedule(data: CreateScheduleParams) { + return post<{ code: number; data: { scheduleId: number } }>('/schedules', data); + }, + + // 获取日程详情 + getScheduleDetail(id: number) { + return get<{ code: number; data: ScheduleItem }>(`/schedules/${id}`); + }, + + // 获取用户的所有日程 + getAllSchedules() { + return get<{ code: number; data: { total: number; list: ScheduleItem[] } }>('/schedules'); + }, + + // 获取用户在指定时间范围内的日程 + getSchedulesByRange(params: { startTime: string; endTime: string }) { + return get<{ code: number; data: { total: number; list: ScheduleItem[] } }>('/schedules/range', params); + }, + + // 更新日程 + updateSchedule(id: number, data: UpdateScheduleParams) { + return put<{ code: number; message: string }>(`/schedules/${id}`, data); + }, + + // 删除日程 + deleteSchedule(id: number) { + return del<{ code: number; message: string }>(`/schedules/${id}`); + }, + + // 检查日程时间冲突 + checkScheduleConflict(params: { startTime: string; endTime: string; excludeScheduleId?: number }) { + return get<{ code: number; data: ConflictCheckResult }>('/schedules/check-conflict', params); + } +}; diff --git a/Front/vue-unilife/src/router/index.ts b/Front/vue-unilife/src/router/index.ts index 1169e27..91a9c1a 100644 --- a/Front/vue-unilife/src/router/index.ts +++ b/Front/vue-unilife/src/router/index.ts @@ -54,15 +54,30 @@ const routes: Array = [ { path: 'resources', // URL: /resources name: 'Resources', - component: () => import('../views/NotFound.vue'), // 占位符 + component: () => import('../views/resource/ResourceListView.vue'), meta: { title: '学习资源 - UniLife', requiresAuth: false } }, + // 资源详情 - 无需登录 + { + path: 'resource/:id', // URL: /resource/123 + name: 'ResourceDetail', + component: () => import('../views/resource/ResourceDetailView.vue'), + props: true, + meta: { title: '资源详情 - UniLife', requiresAuth: false } + }, // 课程表 - 无需登录 { path: 'courses', // URL: /courses name: 'Courses', - component: () => import('../views/NotFound.vue'), // 占位符 + component: () => import('../views/schedule/CourseTableView.vue'), meta: { title: '课程表 - UniLife', requiresAuth: false } + }, + // 日程管理 - 需要登录 + { + path: 'schedule', // URL: /schedule + name: 'Schedule', + component: () => import('../views/schedule/ScheduleView.vue'), + meta: { title: '日程管理 - UniLife', requiresAuth: true } } ] }, @@ -105,6 +120,12 @@ const routes: Array = [ component: () => import('../views/forum/MyPostsView.vue'), meta: { title: '我的帖子 - UniLife' } }, + { + path: 'resources', // URL: /personal/resources + name: 'MyResources', + component: () => import('../views/resource/MyResourcesView.vue'), + meta: { title: '我的资源 - UniLife' } + }, { path: 'messages', // URL: /personal/messages name: 'Messages', diff --git a/Front/vue-unilife/src/views/resource/MyResourcesView.vue b/Front/vue-unilife/src/views/resource/MyResourcesView.vue new file mode 100644 index 0000000..5a80359 --- /dev/null +++ b/Front/vue-unilife/src/views/resource/MyResourcesView.vue @@ -0,0 +1,507 @@ + + + + + diff --git a/Front/vue-unilife/src/views/resource/ResourceDetailView.vue b/Front/vue-unilife/src/views/resource/ResourceDetailView.vue new file mode 100644 index 0000000..7b5d744 --- /dev/null +++ b/Front/vue-unilife/src/views/resource/ResourceDetailView.vue @@ -0,0 +1,487 @@ + + + + + diff --git a/Front/vue-unilife/src/views/resource/ResourceListView.vue b/Front/vue-unilife/src/views/resource/ResourceListView.vue new file mode 100644 index 0000000..95e62a6 --- /dev/null +++ b/Front/vue-unilife/src/views/resource/ResourceListView.vue @@ -0,0 +1,499 @@ + + + + + diff --git a/Front/vue-unilife/src/views/schedule/CourseTableView.vue b/Front/vue-unilife/src/views/schedule/CourseTableView.vue new file mode 100644 index 0000000..72c7f99 --- /dev/null +++ b/Front/vue-unilife/src/views/schedule/CourseTableView.vue @@ -0,0 +1,753 @@ + + + + + diff --git a/Front/vue-unilife/src/views/schedule/ScheduleView.vue b/Front/vue-unilife/src/views/schedule/ScheduleView.vue new file mode 100644 index 0000000..e6311a9 --- /dev/null +++ b/Front/vue-unilife/src/views/schedule/ScheduleView.vue @@ -0,0 +1,839 @@ + + + + + diff --git a/Front/vue-unilife/src/views/schedule/components/ScheduleDetailDialog.vue b/Front/vue-unilife/src/views/schedule/components/ScheduleDetailDialog.vue new file mode 100644 index 0000000..ea79355 --- /dev/null +++ b/Front/vue-unilife/src/views/schedule/components/ScheduleDetailDialog.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/Front/vue-unilife/src/views/schedule/components/ScheduleDialog.vue b/Front/vue-unilife/src/views/schedule/components/ScheduleDialog.vue new file mode 100644 index 0000000..6248006 --- /dev/null +++ b/Front/vue-unilife/src/views/schedule/components/ScheduleDialog.vue @@ -0,0 +1,294 @@ + + + + + diff --git a/unilife-server/src/main/java/com/unilife/config/WebMvcConfig.java b/unilife-server/src/main/java/com/unilife/config/WebMvcConfig.java index 2484e46..7318f50 100644 --- a/unilife-server/src/main/java/com/unilife/config/WebMvcConfig.java +++ b/unilife-server/src/main/java/com/unilife/config/WebMvcConfig.java @@ -16,10 +16,19 @@ public class WebMvcConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor).addPathPatterns("/**") .excludePathPatterns( + // 用户登录注册相关 "/users/login", "/users/register", "/users/code", "/users/login/code", + + // 论坛相关 - 允许未登录用户访问帖子列表和帖子详情 + "/posts", // 帖子列表 + "/posts/*/", // 帖子详情 + "/posts/{id}", // 帖子详情(另一种匹配方式) + "/categories", // 分类列表 + + // Swagger文档相关 "/swagger-resources/**", "/v3/api-docs/**", "/doc.html", diff --git a/unilife-server/src/main/java/com/unilife/controller/PostController.java b/unilife-server/src/main/java/com/unilife/controller/PostController.java index 762264e..bb77645 100644 --- a/unilife-server/src/main/java/com/unilife/controller/PostController.java +++ b/unilife-server/src/main/java/com/unilife/controller/PostController.java @@ -83,4 +83,14 @@ public class PostController { } return postService.likePost(postId, userId); } + + @Operation(summary = "获取用户的帖子列表") + @GetMapping("/user/{userId}") + public Result getUserPosts( + @PathVariable("userId") Long userId, + @RequestParam(value = "page", defaultValue = "1") Integer page, + @RequestParam(value = "size", defaultValue = "10") Integer size, + @RequestParam(value = "sort", defaultValue = "latest") String sort) { + return postService.getUserPosts(userId, page, size, sort); + } } \ No newline at end of file diff --git a/unilife-server/src/main/java/com/unilife/mapper/PostMapper.java b/unilife-server/src/main/java/com/unilife/mapper/PostMapper.java index d6ff889..e83e5a3 100644 --- a/unilife-server/src/main/java/com/unilife/mapper/PostMapper.java +++ b/unilife-server/src/main/java/com/unilife/mapper/PostMapper.java @@ -79,4 +79,19 @@ public interface PostMapper { * @param id 帖子ID */ void decrementCommentCount(Long id); + + /** + * 获取指定用户的帖子列表 + * @param userId 用户ID + * @param sort 排序方式 + * @return 帖子列表 + */ + List getListByUserId(@Param("userId") Long userId, @Param("sort") String sort); + + /** + * 获取指定用户的帖子总数 + * @param userId 用户ID + * @return 帖子总数 + */ + Integer getCountByUserId(@Param("userId") Long userId); } \ No newline at end of file diff --git a/unilife-server/src/main/java/com/unilife/model/vo/PostVO.java b/unilife-server/src/main/java/com/unilife/model/vo/PostVO.java index a5addf0..6871382 100644 --- a/unilife-server/src/main/java/com/unilife/model/vo/PostVO.java +++ b/unilife-server/src/main/java/com/unilife/model/vo/PostVO.java @@ -34,11 +34,7 @@ public class PostVO { * 发布用户ID */ private Long userId; - - /** - * 发布用户昵称 - */ - private String nickname; + /** * 发布用户头像 diff --git a/unilife-server/src/main/java/com/unilife/service/PostService.java b/unilife-server/src/main/java/com/unilife/service/PostService.java index 15eed52..284d3cc 100644 --- a/unilife-server/src/main/java/com/unilife/service/PostService.java +++ b/unilife-server/src/main/java/com/unilife/service/PostService.java @@ -58,4 +58,14 @@ public interface PostService { * @return 结果 */ Result likePost(Long postId, Long userId); + + /** + * 获取用户的帖子列表 + * @param userId 用户ID + * @param page 页码 + * @param size 每页大小 + * @param sort 排序方式(latest-最新,hot-热门) + * @return 结果 + */ + Result getUserPosts(Long userId, Integer page, Integer size, String sort); } \ No newline at end of file diff --git a/unilife-server/src/main/java/com/unilife/service/impl/PostServiceImpl.java b/unilife-server/src/main/java/com/unilife/service/impl/PostServiceImpl.java index fe5cb68..31cecc7 100644 --- a/unilife-server/src/main/java/com/unilife/service/impl/PostServiceImpl.java +++ b/unilife-server/src/main/java/com/unilife/service/impl/PostServiceImpl.java @@ -108,7 +108,6 @@ public class PostServiceImpl implements PostService { .title(post.getTitle()) .content(post.getContent()) .userId(post.getUserId()) - .nickname(user != null ? user.getNickname() : "未知用户") .avatar(user != null ? user.getAvatar() : null) .categoryId(post.getCategoryId()) .categoryName(categoryName) // 使用从数据库查询到的真实分类名称 @@ -231,7 +230,7 @@ public class PostServiceImpl implements PostService { return Result.success(null, "删除成功"); } - + @Override public Result likePost(Long postId, Long userId) { // 获取帖子 @@ -255,6 +254,83 @@ public class PostServiceImpl implements PostService { return Result.success(null, "点赞成功"); } } + @Override + public Result getUserPosts(Long userId, Integer page, Integer size, String sort) { + // 参数校验 + if (userId == null) { + return Result.error(400, "用户ID不能为空"); + } + + // 检查用户是否存在 + User user = userMapper.getUserById(userId); + if (user == null) { + return Result.error(404, "用户不存在"); + } + + // 分页查询 + PageHelper.startPage(page, size); + List posts = postMapper.getListByUserId(userId, sort); + PageInfo pageInfo = new PageInfo<>(posts); + + // 获取分类信息 + List categoryIds = posts.stream() + .map(Post::getCategoryId) + .distinct() + .collect(Collectors.toList()); + + // 获取分类名称映射 + Map categoryMap; + if (!categoryIds.isEmpty()) { + // 获取所有分类,然后过滤出需要的分类 + List allCategories = categoryMapper.getList(null); + + // 过滤出匹配的分类 + List filteredCategories = allCategories.stream() + .filter(category -> categoryIds.contains(category.getId())) + .collect(Collectors.toList()); + + // 构建分类ID到名称的映射 + categoryMap = filteredCategories.stream() + .collect(Collectors.toMap(Category::getId, Category::getName)); + } else { + categoryMap = new HashMap<>(); + } + + // 转换为VO + List postVOs = posts.stream().map(post -> { + PostListVO vo = new PostListVO(); + BeanUtil.copyProperties(post, vo); + + // 填充分类名称 + String categoryName = categoryMap.getOrDefault(post.getCategoryId(), "未知分类"); + vo.setCategoryName(categoryName); + + // 获取作者信息 + User author = userMapper.getUserById(post.getUserId()); + if (author != null) { + vo.setNickname(author.getNickname()); + vo.setAvatar(author.getAvatar()); + } + + // 内容摘要 + if (StrUtil.isNotBlank(post.getContent())) { + String content = post.getContent() + .replaceAll("<[^>]*>", "") // 去除HTML标签 + .replaceAll("&[^;]+;", ""); // 去除HTML实体 + vo.setSummary(StrUtil.maxLength(content, 100)); + } + + return vo; + }).collect(Collectors.toList()); + + // 构建返回数据 + Map data = new HashMap<>(); + data.put("total", pageInfo.getTotal()); + data.put("pages", pageInfo.getPages()); + data.put("list", postVOs); + + return Result.success(data); + } /** * 生成摘要 diff --git a/unilife-server/src/main/resources/mappers/PostMapper.xml b/unilife-server/src/main/resources/mappers/PostMapper.xml index 90e2927..d2be627 100644 --- a/unilife-server/src/main/resources/mappers/PostMapper.xml +++ b/unilife-server/src/main/resources/mappers/PostMapper.xml @@ -108,4 +108,29 @@ SET comment_count = GREATEST(comment_count - 1, 0) WHERE id = #{id} + + + + \ No newline at end of file diff --git a/uploads/resources/2d6c8943-f0dc-4449-8c92-765f3af1bed1.pdf b/uploads/resources/2d6c8943-f0dc-4449-8c92-765f3af1bed1.pdf new file mode 100644 index 0000000..88029a2 Binary files /dev/null and b/uploads/resources/2d6c8943-f0dc-4449-8c92-765f3af1bed1.pdf differ