diff --git a/Front/vue-unilife/src/api/post.ts b/Front/vue-unilife/src/api/post.ts index 885d969..c39c0aa 100644 --- a/Front/vue-unilife/src/api/post.ts +++ b/Front/vue-unilife/src/api/post.ts @@ -4,7 +4,8 @@ import { get, post as httpPost, put, del } from './request'; export interface PostItem { id: number; title: string; - content: string; + summary?: string; // Added: for post list item summary + content: string; // Existing: for post detail content userId: number; nickname: string; avatar: string; @@ -15,6 +16,13 @@ export interface PostItem { commentCount: number; createdAt: string; updatedAt: string; + isLiked?: boolean; // Added: for post detail, if current user liked it +} + +export interface CategoryItem { + id: number; + name: string; + description?: string; } export interface CreatePostParams { @@ -26,8 +34,8 @@ export interface CreatePostParams { // API方法 export default { // 获取帖子列表 - getPosts(params: { page?: number; size?: number; category?: number; sort?: string }) { - return get<{ code: number; data: { total: number; list: PostItem[]; pages: number } }>('/posts', params); + getPosts(params: { pageNum?: number; pageSize?: number; categoryId?: number; sort?: string }) { + return get<{ code: number; data: { total: number; list: PostItem[]; pages: number; pageNum: number; pageSize: number } }>('/posts', params); }, // 获取帖子详情 @@ -59,5 +67,10 @@ export default { getUserPosts(userId?: number) { const params = userId ? { userId } : {}; return get<{ code: number; data: { total: number; list: PostItem[]; pages: number } }>('/posts', params); + }, + + // 获取所有帖子分类 + getCategories() { + return get<{ code: number; message: string; data: { list: CategoryItem[], total: number } }>('/categories'); } }; \ No newline at end of file diff --git a/Front/vue-unilife/src/api/user.ts b/Front/vue-unilife/src/api/user.ts index e2f0ec0..355d8a5 100644 --- a/Front/vue-unilife/src/api/user.ts +++ b/Front/vue-unilife/src/api/user.ts @@ -2,16 +2,20 @@ import { get, post, put } from './request'; // 用户接口类型定义 export interface UserInfo { + id: number; username: string; email: string; avatar?: string; - gender?: number; bio?: string; + gender?: number; birthday?: string; studentId?: string; department?: string; major?: string; grade?: string; + points?: number; + role?: number; + isVerified?: number; } export interface LoginParams { @@ -23,7 +27,6 @@ export interface RegisterParams { email: string; password: string; username?: string; - nickname?: string; studentId?: string; department?: string; major?: string; @@ -44,7 +47,9 @@ export interface UpdateProfileParams { username?: string; bio?: string; gender?: number; - birthday?: string; + department?: string; + major?: string; + grade?: string; } export interface UpdatePasswordParams { diff --git a/Front/vue-unilife/src/layouts/PersonalLayout.vue b/Front/vue-unilife/src/layouts/PersonalLayout.vue index de2e9ec..6dbd618 100644 --- a/Front/vue-unilife/src/layouts/PersonalLayout.vue +++ b/Front/vue-unilife/src/layouts/PersonalLayout.vue @@ -2,18 +2,19 @@ import { ref, onMounted } from 'vue'; import { useRouter, useRoute } from 'vue-router'; import { useUserStore } from '../stores'; +import { HomeFilled, User, Document, Message, Setting, ArrowLeft } from '@element-plus/icons-vue'; const router = useRouter(); const route = useRoute(); const userStore = useUserStore(); -// 侧边栏菜单项 +// 侧边栏菜单项 - 个人中心相关 const menuItems = [ - { name: 'Home', title: '个人主页', icon: 'home' }, - { name: 'AccountManager', title: '账号管理', icon: 'user' }, - { name: 'Posts', title: '我的帖子', icon: 'document' }, - { name: 'Messages', title: '消息中心', icon: 'message' }, - { name: 'Settings', title: '设置', icon: 'setting' } + { name: 'PersonalHome', title: '个人主页', icon: HomeFilled, path: '/personal/home' }, + { name: 'AccountManager', title: '账号管理', icon: User, path: '/personal/account' }, + { name: 'MyPosts', title: '我的帖子', icon: Document, path: '/personal/posts' }, + { name: 'Messages', title: '消息中心', icon: Message, path: '/personal/messages' }, + { name: 'Settings', title: '设置', icon: Setting, path: '/personal/settings' } ]; // 当前激活的菜单项 @@ -22,13 +23,18 @@ const activeIndex = ref(0); // 设置激活的菜单项 const setActive = (index: number) => { activeIndex.value = index; - router.push({ name: menuItems[index].name }); + router.push(menuItems[index].path); +}; + +// 返回论坛首页 +const backToForum = () => { + router.push('/'); }; // 根据当前路由设置激活的菜单项 onMounted(() => { - const currentRouteName = route.name as string; - const index = menuItems.findIndex(item => item.name === currentRouteName); + const currentPath = route.path; + const index = menuItems.findIndex(item => currentPath.includes(item.path)); if (index !== -1) { activeIndex.value = index; } @@ -37,99 +43,141 @@ onMounted(() => { if (userStore.isLoggedIn) { userStore.fetchUserInfo(); } else { - // 未登录则跳转到登录页 - router.push('/login'); + // 未登录则跳转到登录页,并保存当前路径用于登录后跳转回来 + router.push({ path: '/login', query: { redirect: route.fullPath } }); } }); - -// 退出登录 -const logout = () => { - userStore.logout(); - router.push('/login'); -}; diff --git a/Front/vue-unilife/src/layouts/PublicLayout.vue b/Front/vue-unilife/src/layouts/PublicLayout.vue new file mode 100644 index 0000000..0ecb911 --- /dev/null +++ b/Front/vue-unilife/src/layouts/PublicLayout.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/Front/vue-unilife/src/router/index.ts b/Front/vue-unilife/src/router/index.ts index d64bed5..4e1dccf 100644 --- a/Front/vue-unilife/src/router/index.ts +++ b/Front/vue-unilife/src/router/index.ts @@ -5,6 +5,7 @@ import { useUserStore } from '../stores'; // 布局 import BaseLayout from '../layouts/BaseLayout.vue'; import PersonalLayout from '../layouts/PersonalLayout.vue'; +import PublicLayout from '../layouts/PublicLayout.vue'; // 页面 import Login from '../views/Login.vue'; @@ -14,87 +15,122 @@ import NotFound from '../views/NotFound.vue'; // 路由配置 const routes: Array = [ + // 公共页面 - 使用PublicLayout布局 { path: '/', - component: BaseLayout, + component: PublicLayout, children: [ + // 论坛首页 - 无需登录 { - path: '', - redirect: '/login' + path: '', // 网站根路径 / + name: 'Forum', + component: () => import('../views/forum/PostListView.vue'), + meta: { title: '论坛广场 - UniLife', requiresAuth: false } + }, + // 帖子详情 - 无需登录 + { + path: 'post/:id', // URL: /post/123 + name: 'PostDetail', + component: () => import('../views/forum/PostDetailView.vue'), + props: true, + meta: { title: '帖子详情 - UniLife', requiresAuth: false } }, + // 发布帖子 - 需要登录 + { + path: 'create-post', // URL: /create-post + name: 'CreatePost', + component: () => import('../views/forum/CreatePostView.vue'), + meta: { title: '发布帖子 - UniLife', requiresAuth: true } + }, + // 编辑帖子 - 需要登录 + { + path: 'edit-post/:id', // URL: /edit-post/123 + name: 'EditPost', + component: () => import('../views/forum/CreatePostView.vue'), + props: true, + meta: { title: '编辑帖子 - UniLife', requiresAuth: true } + }, + // 学习资源 - 无需登录 + { + path: 'resources', // URL: /resources + name: 'Resources', + component: () => import('../views/NotFound.vue'), // 占位符 + meta: { title: '学习资源 - UniLife', requiresAuth: false } + }, + // 课程表 - 无需登录 + { + path: 'courses', // URL: /courses + name: 'Courses', + component: () => import('../views/NotFound.vue'), // 占位符 + meta: { title: '课程表 - UniLife', requiresAuth: false } + } + ] + }, + + // 认证相关页面 - 使用BaseLayout布局 + { + path: '/', + component: BaseLayout, + children: [ { - path: 'login', + path: 'login', // URL: /login name: 'Login', component: Login, - meta: { - title: '登录 - UniLife学生论坛', - requiresAuth: false - } + meta: { title: '登录/注册 - UniLife', requiresAuth: false } } ] }, + + // 个人中心页面 - 使用PersonalLayout布局 { path: '/personal', component: PersonalLayout, - meta: { - requiresAuth: true - }, + meta: { requiresAuth: true }, children: [ { - path: '', - name: 'Home', + path: 'home', // URL: /personal/home + name: 'PersonalHome', component: Home, - meta: { - title: '个人主页 - UniLife学生论坛', - requiresAuth: true - } + meta: { title: '个人主页 - UniLife' } }, { - path: 'account', + path: 'account', // URL: /personal/account name: 'AccountManager', component: AccountManager, - meta: { - title: '账号管理 - UniLife学生论坛', - requiresAuth: true - } + meta: { title: '账号管理 - UniLife' } }, - // 其他个人中心页面可以在这里添加 { - path: 'posts', - name: 'Posts', - component: () => import('../views/NotFound.vue'), // 暂时使用NotFound页面 - meta: { - title: '我的帖子 - UniLife学生论坛', - requiresAuth: true - } + path: 'posts', // URL: /personal/posts + name: 'MyPosts', + component: () => import('../views/NotFound.vue'), // 占位符 + meta: { title: '我的帖子 - UniLife' } }, { - path: 'messages', + path: 'messages', // URL: /personal/messages name: 'Messages', - component: () => import('../views/NotFound.vue'), // 暂时使用NotFound页面 - meta: { - title: '消息中心 - UniLife学生论坛', - requiresAuth: true - } + component: () => import('../views/NotFound.vue'), // 占位符 + meta: { title: '消息中心 - UniLife' } }, { - path: 'settings', + path: 'settings', // URL: /personal/settings name: 'Settings', - component: () => import('../views/NotFound.vue'), // 暂时使用NotFound页面 - meta: { - title: '设置 - UniLife学生论坛', - requiresAuth: true - } + component: () => import('../views/NotFound.vue'), // 占位符 + meta: { title: '设置 - UniLife' } + }, + // 默认重定向到个人主页 + { + path: '', + redirect: '/personal/home' } ] }, + + // Catch-all 404 { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound, - meta: { - title: '页面不存在 - UniLife学生论坛' - } + meta: { title: '页面不存在 - UniLife' } } ]; @@ -105,23 +141,21 @@ const router = createRouter({ // 全局前置守卫 router.beforeEach((to, from, next) => { - // 设置页面标题 document.title = to.meta.title as string || 'UniLife学生论坛'; + const userStore = useUserStore(); + const isLoggedIn = userStore.isLoggedIn; - // 检查是否需要登录权限 - if (to.matched.some(record => record.meta.requiresAuth)) { - const userStore = useUserStore(); - - // 如果需要登录但用户未登录,重定向到登录页 - if (!userStore.isLoggedIn) { - next({ - path: '/login', - query: { redirect: to.fullPath } - }); - } else { - next(); - } + // 如果路由需要认证但用户未登录 + if (to.matched.some(record => record.meta.requiresAuth) && !isLoggedIn) { + next({ + name: 'Login', + query: { redirect: to.fullPath } // 保存原始路径用于登录后重定向 + }); + } else if ((to.name === 'Login') && isLoggedIn) { + // 如果用户已登录但尝试访问登录页面,重定向到论坛首页 + next({ name: 'Forum' }); } else { + // 正常导航 next(); } }); diff --git a/Front/vue-unilife/src/stores/postStore.ts b/Front/vue-unilife/src/stores/postStore.ts new file mode 100644 index 0000000..da0a0cb --- /dev/null +++ b/Front/vue-unilife/src/stores/postStore.ts @@ -0,0 +1,135 @@ +import { defineStore } from 'pinia'; +import postApi from '@/api/post'; +import type { PostItem, CategoryItem } from '@/api/post'; +import { ElMessage } from 'element-plus'; + +export interface PostState { + posts: PostItem[]; + currentPost: PostItem | null; + loading: boolean; + error: string | null; + currentPage: number; + pageSize: number; + totalPosts: number; + totalPages: number; + + categories: CategoryItem[]; + loadingCategories: boolean; + errorCategories: string | null; + selectedCategoryId: number | null; +} + +export const usePostStore = defineStore('post', { + state: (): PostState => ({ + posts: [], + currentPost: null, + loading: false, + error: null, + currentPage: 1, + pageSize: 10, + totalPosts: 0, + totalPages: 0, + + categories: [], + loadingCategories: false, + errorCategories: null, + selectedCategoryId: null, + }), + actions: { + async fetchPosts(params: { pageNum?: number; pageSize?: number } = {}) { + this.loading = true; + this.error = null; + try { + const pageNum = params.pageNum || this.currentPage; + const pageSize = params.pageSize || this.pageSize; + const categoryId = this.selectedCategoryId; // Use the stored selectedCategoryId + + const apiParams: { pageNum: number; pageSize: number; categoryId?: number } = { pageNum, pageSize }; + if (categoryId !== null) { + apiParams.categoryId = categoryId; + } + + const response = await postApi.getPosts(apiParams); + + if (response && response.data && Array.isArray(response.data.list)) { + this.posts = response.data.list; + this.totalPosts = response.data.total; + this.totalPages = response.data.pages; + this.currentPage = response.data.pageNum; // Ensure backend returns this + this.pageSize = response.data.pageSize; // Ensure backend returns this + } else { + console.error('Unexpected response structure for posts:', response); + this.posts = []; + this.totalPosts = 0; + this.totalPages = 0; + // Do not reset currentPage and pageSize here unless intended, + // as they might be needed for subsequent fetches if only list is malformed. + throw new Error('帖子数据格式不正确'); + } + } catch (error: any) { + this.error = error.message || '获取帖子列表失败'; + // Potentially keep existing posts if fetch fails, or clear them: + // this.posts = []; + // this.totalPosts = 0; + // this.totalPages = 0; + } finally { + this.loading = false; + } + }, + + async fetchPostDetail(id: number) { + this.loading = true; + this.error = null; + this.currentPost = null; + try { + const response = await postApi.getPostDetail(id); + // The API returns code 200 for success, and post data is directly in response.data + if (response && response.code === 200 && response.data) { + this.currentPost = response.data; + } else { + // Construct a more informative error or use a default + const errorMessage = response?.message || (response?.data?.toString() ? `错误: ${response.data.toString()}` : '获取帖子详情失败'); + console.error('Failed to fetch post detail:', response); + throw new Error(errorMessage); + } + } catch (error: any) { + // Ensure this.error is set from error.message, and ElMessage shows this.error or a default. + this.error = error.message || '加载帖子详情时发生未知错误'; + ElMessage.error(this.error); + } finally { + this.loading = false; + } + }, + + async fetchCategories() { + this.loadingCategories = true; + this.errorCategories = null; + try { + const response = await postApi.getCategories(); + // response.data is an object like { total: number, list: CategoryItem[] } + // We need to access the 'list' property for the actual categories array. + if (response && response.data && Array.isArray(response.data.list)) { + this.categories = response.data.list; + } else { + // Handle cases where the structure is not as expected, though API seems to return it correctly. + console.error('Unexpected response structure for categories:', response); + this.categories = []; // Default to empty array to prevent further errors + throw new Error('分类数据格式不正确'); + } + this.loadingCategories = false; + } catch (error: any) { + this.errorCategories = error.message || '获取分类失败'; + } finally { + this.loadingCategories = false; + } + }, + + async selectCategory(categoryId: number | null) { + if (this.selectedCategoryId !== categoryId) { + this.selectedCategoryId = categoryId; + this.currentPage = 1; + await this.fetchPosts(); + } + } + } +}); diff --git a/Front/vue-unilife/src/stores/user.ts b/Front/vue-unilife/src/stores/user.ts index 283b22e..a215cb8 100644 --- a/Front/vue-unilife/src/stores/user.ts +++ b/Front/vue-unilife/src/stores/user.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia'; import { ref } from 'vue'; import userApi from '../api/user'; -import type { UserInfo } from '../api/user'; +import type { UserInfo, UpdatePasswordParams } from '../api/user'; import { ElMessage } from 'element-plus'; export const useUserStore = defineStore('user', () => { @@ -123,19 +123,31 @@ export const useUserStore = defineStore('user', () => { username?: string; bio?: string; gender?: number; - birthday?: string; + department?: string; + major?: string; + grade?: string; }) => { try { loading.value = true; - const res = await userApi.updateProfile(data); + const params: any = {}; + if (data.username !== undefined) params.username = data.username; + if (data.bio !== undefined) params.bio = data.bio; + if (data.gender !== undefined) params.gender = data.gender; + if (data.department !== undefined) params.department = data.department; + if (data.major !== undefined) params.major = data.major; + if (data.grade !== undefined) params.grade = data.grade; + + const res = await userApi.updateProfile(params); if (res.code === 200) { // 更新本地用户信息 if (userInfo.value) { - userInfo.value = { - ...userInfo.value, - ...data - }; + if (data.username !== undefined) userInfo.value.username = data.username; + if (data.bio !== undefined) userInfo.value.bio = data.bio; + if (data.gender !== undefined) userInfo.value.gender = data.gender; + if (data.department !== undefined) userInfo.value.department = data.department; + if (data.major !== undefined) userInfo.value.major = data.major; + if (data.grade !== undefined) userInfo.value.grade = data.grade; } ElMessage.success('个人资料更新成功'); @@ -151,11 +163,18 @@ export const useUserStore = defineStore('user', () => { } }; - // 更新密码 - const updatePassword = async (code: string, newPassword: string) => { + // 更新用户密码 + const updatePassword = async (data: { + newPassword?: string; + code?: string; + }) => { try { loading.value = true; - const res = await userApi.updatePassword({ code, newPassword }); + const params: UpdatePasswordParams = {}; + if (data.newPassword) params.newPassword = data.newPassword; + if (data.code) params.code = data.code; + + const res = await userApi.updatePassword(params); if (res.code === 200) { ElMessage.success('密码修改成功'); diff --git a/Front/vue-unilife/src/views/AccountManager.vue b/Front/vue-unilife/src/views/AccountManager.vue index 0eb8694..8a0ab20 100644 --- a/Front/vue-unilife/src/views/AccountManager.vue +++ b/Front/vue-unilife/src/views/AccountManager.vue @@ -1,10 +1,180 @@ + + - - diff --git a/Front/vue-unilife/src/views/Login.vue b/Front/vue-unilife/src/views/Login.vue index 496c9cd..5bae166 100644 --- a/Front/vue-unilife/src/views/Login.vue +++ b/Front/vue-unilife/src/views/Login.vue @@ -49,7 +49,9 @@ const handlePasswordLogin = async () => { if (success) { // 只有当 store 返回 true 时才视为成功 ElMessage.success('登录成功'); // 统一在组件中提示 - router.push('/personal'); + // 获取重定向URL,如果有的话跳转到这个URL,否则跳转到论坛首页 + const redirectUrl = router.currentRoute.value.query.redirect as string || '/'; + router.push(redirectUrl); } else { } } catch (error: any) { @@ -67,7 +69,9 @@ const handleCodeLogin = async () => { if (success) { ElMessage.success('登录成功'); // 统一在组件中提示 - router.push('/personal'); + // 获取重定向URL,如果有的话跳转到这个URL,否则跳转到论坛首页 + const redirectUrl = router.currentRoute.value.query.redirect as string || '/'; + router.push(redirectUrl); } else { // 依赖拦截器或 store 显示具体失败信息 // ElMessage.error('验证码错误或登录失败'); @@ -90,7 +94,8 @@ const handleRegister = async () => { if (success) { ElMessage.success('注册成功,已自动登录'); // 统一在组件中提示 - router.push('/personal'); + // 注册成功后跳转到论坛首页 + router.push('/'); } else { } } catch (error: any) { diff --git a/Front/vue-unilife/src/views/forum/CreatePostView.vue b/Front/vue-unilife/src/views/forum/CreatePostView.vue new file mode 100644 index 0000000..c5a8548 --- /dev/null +++ b/Front/vue-unilife/src/views/forum/CreatePostView.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/Front/vue-unilife/src/views/forum/PostDetailView.vue b/Front/vue-unilife/src/views/forum/PostDetailView.vue new file mode 100644 index 0000000..7d14c72 --- /dev/null +++ b/Front/vue-unilife/src/views/forum/PostDetailView.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/Front/vue-unilife/src/views/forum/PostListView.vue b/Front/vue-unilife/src/views/forum/PostListView.vue new file mode 100644 index 0000000..e330400 --- /dev/null +++ b/Front/vue-unilife/src/views/forum/PostListView.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/Front/vue-unilife/vite.config.ts b/Front/vue-unilife/vite.config.ts index eae1f08..9223397 100644 --- a/Front/vue-unilife/vite.config.ts +++ b/Front/vue-unilife/vite.config.ts @@ -1,8 +1,13 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' +import path from 'path'; // https://vite.dev/config/ export default defineConfig({ plugins: [vue()], - + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + } + } }) 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 171eacc..762264e 100644 --- a/unilife-server/src/main/java/com/unilife/controller/PostController.java +++ b/unilife-server/src/main/java/com/unilife/controller/PostController.java @@ -42,7 +42,7 @@ public class PostController { @Operation(summary = "获取帖子列表") @GetMapping public Result getPostList( - @RequestParam(value = "category", required = false) Long categoryId, + @RequestParam(value = "categoryId", required = false) Long categoryId, @RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "size", defaultValue = "10") Integer size, @RequestParam(value = "sort", defaultValue = "latest") String sort) { diff --git a/unilife-server/src/main/java/com/unilife/mapper/UserMapper.java b/unilife-server/src/main/java/com/unilife/mapper/UserMapper.java index 29f2ffa..4b4e363 100644 --- a/unilife-server/src/main/java/com/unilife/mapper/UserMapper.java +++ b/unilife-server/src/main/java/com/unilife/mapper/UserMapper.java @@ -10,6 +10,7 @@ import java.util.Date; public interface UserMapper { void insert(User user); User findByEmail(String email); + User findByUsername(String username); void updateLoginInfo(@Param("userId") Long userId, @Param("ipLocation") String ipLocation, @Param("loginTime") Date loginTime); diff --git a/unilife-server/src/main/java/com/unilife/model/dto/UpdateProfileDTO.java b/unilife-server/src/main/java/com/unilife/model/dto/UpdateProfileDTO.java index 325da91..5e47dcc 100644 --- a/unilife-server/src/main/java/com/unilife/model/dto/UpdateProfileDTO.java +++ b/unilife-server/src/main/java/com/unilife/model/dto/UpdateProfileDTO.java @@ -11,7 +11,8 @@ import lombok.NoArgsConstructor; @AllArgsConstructor @NoArgsConstructor public class UpdateProfileDTO { - private String nickname; + private String username; // Added back to allow username updates + // private String nickname; // Removed as per user request and frontend changes private String bio; private Byte gender; private String department; 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 8806c46..fe5cb68 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 @@ -11,6 +11,7 @@ import com.unilife.mapper.PostMapper; import com.unilife.mapper.UserMapper; import com.unilife.model.dto.CreatePostDTO; import com.unilife.model.dto.UpdatePostDTO; +import com.unilife.model.entity.Category; import com.unilife.model.entity.Post; import com.unilife.model.entity.User; import com.unilife.model.vo.PostListVO; @@ -91,8 +92,15 @@ public class PostServiceImpl implements PostService { User user = userMapper.getUserById(post.getUserId()); // 获取分类信息 - // 注意:这里假设已经创建了CategoryMapper接口,实际开发中需要先创建 - // Category category = categoryMapper.getById(post.getCategoryId()); + Category category = categoryMapper.getById(post.getCategoryId()); + String categoryName = category != null ? category.getName() : "未知分类"; + + // 查询用户是否点赞过该帖子 + boolean isLiked = false; + if (userId != null) { + Boolean liked = postLikeMapper.isLiked(postId, userId); + isLiked = liked != null && liked; + } // 构建返回数据 PostVO postVO = PostVO.builder() @@ -103,11 +111,11 @@ public class PostServiceImpl implements PostService { .nickname(user != null ? user.getNickname() : "未知用户") .avatar(user != null ? user.getAvatar() : null) .categoryId(post.getCategoryId()) - .categoryName("未知分类") // 实际开发中应该从category对象获取 + .categoryName(categoryName) // 使用从数据库查询到的真实分类名称 .viewCount(post.getViewCount() + 1) // 已经增加了浏览次数 .likeCount(post.getLikeCount()) .commentCount(post.getCommentCount()) - .isLiked(false) // 实际开发中应该查询用户是否点赞 + .isLiked(isLiked) // 设置已查询的点赞状态 .createdAt(post.getCreatedAt()) .updatedAt(post.getUpdatedAt()) .build(); @@ -130,6 +138,21 @@ public class PostServiceImpl implements PostService { // 获取分页信息 PageInfo pageInfo = new PageInfo<>(posts); + + // 收集所有帖子的分类 ID + List categoryIds = posts.stream() + .map(Post::getCategoryId) + .distinct() + .collect(Collectors.toList()); + + // 批量获取分类信息 + Map categoryMap = new HashMap<>(); + if (!categoryIds.isEmpty()) { + categoryIds.forEach(id -> { + Category category = categoryMapper.getById(id); + categoryMap.put(id, category != null ? category.getName() : "未知分类"); + }); + } // 转换为VO List postListVOs = posts.stream().map(post -> { @@ -142,7 +165,7 @@ public class PostServiceImpl implements PostService { .nickname(user != null ? user.getNickname() : "未知用户") .avatar(user != null ? user.getAvatar() : null) .categoryId(post.getCategoryId()) - .categoryName("未知分类") + .categoryName(categoryMap.getOrDefault(post.getCategoryId(), "未知分类")) .viewCount(post.getViewCount()) .likeCount(post.getLikeCount()) .commentCount(post.getCommentCount()) diff --git a/unilife-server/src/main/java/com/unilife/service/impl/UserServiceImpl.java b/unilife-server/src/main/java/com/unilife/service/impl/UserServiceImpl.java index e4701d7..49fbb52 100644 --- a/unilife-server/src/main/java/com/unilife/service/impl/UserServiceImpl.java +++ b/unilife-server/src/main/java/com/unilife/service/impl/UserServiceImpl.java @@ -33,8 +33,6 @@ import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; - - import java.time.Duration; import java.time.LocalDateTime; import java.util.Date; @@ -339,24 +337,49 @@ public class UserServiceImpl implements UserService { @Override public Result updateUserProfile(Long userId, UpdateProfileDTO profileDTO) { - // 检查用户是否存在 - User user = userMapper.getUserById(userId); - if (user == null) { + User currentUser = userMapper.getUserById(userId); // Changed from findById to getUserById based on UserMapper.java + if (currentUser == null) { return Result.error(404, "用户不存在"); } - // 更新用户信息 - user.setNickname(profileDTO.getNickname()); - user.setBio(profileDTO.getBio()); - user.setGender(profileDTO.getGender()); - user.setDepartment(profileDTO.getDepartment()); - user.setMajor(profileDTO.getMajor()); - user.setGrade(profileDTO.getGrade()); + // 检查用户名是否更改以及是否重复 + if (StringUtils.isNotEmpty(profileDTO.getUsername()) && !profileDTO.getUsername().equals(currentUser.getUsername())) { + User existingUserWithNewUsername = userMapper.findByUsername(profileDTO.getUsername()); + if (existingUserWithNewUsername != null) { + return Result.error(409, "用户名已被占用,请选择其他用户名"); // 409 Conflict + } + currentUser.setUsername(profileDTO.getUsername()); + } - // 保存更新 - userMapper.updateUserProfile(user); + // 更新用户信息 + // 注意:这里应该只更新profileDTO中存在的字段,且要考虑空值情况 + // if (StringUtils.isNotEmpty(profileDTO.getNickname())) { // Commented out as nickname is removed from DTO + // user.setNickname(profileDTO.getNickname()); + // } + if (StringUtils.isNotEmpty(profileDTO.getBio())) { + currentUser.setBio(profileDTO.getBio()); // Changed user to currentUser + } + if (profileDTO.getGender() != null) { + currentUser.setGender(profileDTO.getGender()); // Changed user to currentUser + } + if (StringUtils.isNotEmpty(profileDTO.getDepartment())) { + currentUser.setDepartment(profileDTO.getDepartment()); // Changed user to currentUser + } + if (StringUtils.isNotEmpty(profileDTO.getMajor())) { + currentUser.setMajor(profileDTO.getMajor()); // Changed user to currentUser + } + if (StringUtils.isNotEmpty(profileDTO.getGrade())) { + currentUser.setGrade(profileDTO.getGrade()); // Changed user to currentUser + } - return Result.success(null, "个人资料更新成功"); + try { + userMapper.updateUserProfile(currentUser); // Call void method + log.info("用户 {} 的个人资料更新成功", userId); + return Result.success("个人资料更新成功"); + } catch (Exception e) { + log.error("用户 {} 的个人资料更新时发生数据库错误: {}", userId, e.getMessage()); + return Result.error(500, "个人资料更新失败,服务器内部错误"); + } } @Override diff --git a/unilife-server/src/main/resources/mappers/PostMapper.xml b/unilife-server/src/main/resources/mappers/PostMapper.xml index 42837c5..90e2927 100644 --- a/unilife-server/src/main/resources/mappers/PostMapper.xml +++ b/unilife-server/src/main/resources/mappers/PostMapper.xml @@ -32,8 +32,9 @@ + + UPDATE users SET login_ip = #{ipLocation}, @@ -107,13 +113,13 @@ UPDATE users - SET nickname = #{nickname}, + SET username = #{username}, + nickname = #{nickname}, bio = #{bio}, gender = #{gender}, department = #{department}, major = #{major}, - grade = #{grade}, - updated_at = NOW() + grade = #{grade} WHERE id = #{id}