2991692032 2 days ago
parent 95b833a711
commit d90126b45c

@ -0,0 +1,95 @@
import request from './request';
/**
*
* @param page
* @param size
* @param categoryId ID
* @param sort latest, hot, likes, commentslatest
* @param userId ID
*/
export function getPosts(page = 1, size = 10, categoryId?: number | null, sort = 'latest', userId?: number | null) {
if (userId) {
// 获取指定用户的帖子
return request({
url: `/posts/user/${userId}`,
method: 'get',
params: { page, size, sort }
});
} else {
// 获取所有帖子或者按分类筛选
return request({
url: '/posts',
method: 'get',
params: { page, size, categoryId, sort }
});
}
}
/**
*
* @param id ID
*/
export function getPostDetail(id: number) {
return request({
url: `/posts/${id}`,
method: 'get'
});
}
/**
*
* @param data
*/
export function createPost(data: any) {
return request({
url: '/posts',
method: 'post',
data
});
}
/**
*
* @param id ID
* @param data
*/
export function updatePost(id: number, data: any) {
return request({
url: `/posts/${id}`,
method: 'put',
data
});
}
/**
*
* @param id ID
*/
export function deletePost(id: number) {
return request({
url: `/posts/${id}`,
method: 'delete'
});
}
/**
*
* @param id ID
*/
export function likePost(id: number) {
return request({
url: `/posts/${id}/like`,
method: 'post'
});
}
/**
*
*/
export function getCategories() {
return request({
url: '/categories',
method: 'get'
});
}

@ -35,7 +35,19 @@ export interface CreatePostParams {
export default {
// 获取帖子列表
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);
// 将前端参数名转换为后端参数名
const serverParams: any = {
page: params.pageNum,
size: params.pageSize,
sort: params.sort
};
// 保留categoryId参数
if (params.categoryId !== undefined) {
serverParams.categoryId = params.categoryId;
}
return get<{ code: number; data: { total: number; list: PostItem[]; pages: number; pageNum: number; pageSize: number } }>('/posts', serverParams);
},
// 获取帖子详情

@ -45,7 +45,9 @@ service.interceptors.response.use(
if (res.code === 401) {
// 未授权清除token并重定向到登录页
localStorage.removeItem('token');
window.location.href = '/login';
const currentPath = window.location.pathname;
window.location.href = `/login?redirect=${encodeURIComponent(currentPath)}`;
return Promise.reject(new Error('未登录或登录已过期'));
}
return Promise.reject(new Error(res.message || '请求失败'));
@ -56,7 +58,23 @@ service.interceptors.response.use(
(error) => {
console.error('响应错误:', error);
// 显示错误信息
// 处理HTTP 401错误
if (error.response && error.response.status === 401) {
ElMessage({
message: '未登录或登录已过期,请重新登录',
type: 'warning',
duration: 3000
});
// 清除token
localStorage.removeItem('token');
// 获取当前页面路径,并重定向到登录页面
const currentPath = window.location.pathname;
window.location.href = `/login?redirect=${encodeURIComponent(currentPath)}`;
return Promise.reject(error);
}
ElMessage({
message: error.message || '请求失败',
type: 'error',

@ -102,7 +102,7 @@ const routes: Array<RouteRecordRaw> = [
{
path: 'posts', // URL: /personal/posts
name: 'MyPosts',
component: () => import('../views/NotFound.vue'), // 占位符
component: () => import('../views/forum/MyPostsView.vue'),
meta: { title: '我的帖子 - UniLife' }
},
{

@ -93,9 +93,19 @@ export const usePostStore = defineStore('post', {
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);
// 检查是否是未登录错误
if (error.response && error.response.status === 401) {
this.error = '您需要登录后才能查看帖子详情';
ElMessage.warning(this.error);
// 将用户重定向到登录页面,并记录需要返回的帖子详情页面
const currentPath = `/post/${id}`;
window.location.href = `/login?redirect=${encodeURIComponent(currentPath)}`;
return;
} else {
// 处理其他错误
this.error = error.message || '加载帖子详情时发生未知错误';
ElMessage.error(this.error);
}
} finally {
this.loading = false;
}

@ -0,0 +1,248 @@
<template>
<div class="my-posts-container">
<el-card class="my-posts-card" shadow="hover">
<template #header>
<div class="card-header">
<h3>我的帖子</h3>
<el-button type="primary" @click="createNewPost"></el-button>
</div>
</template>
<div v-if="loading" class="loading-container">
<el-skeleton :rows="5" animated />
</div>
<div v-else-if="posts.length === 0" class="empty-container">
<el-empty description="您还没有发布过帖子" />
<el-button type="primary" @click="createNewPost"></el-button>
</div>
<div v-else class="posts-list">
<el-table :data="posts" style="width: 100%">
<el-table-column prop="title" label="标题" min-width="180">
<template #default="{ row }">
<router-link :to="`/forum/post/${row.id}`" class="post-title">
{{ row.title }}
</router-link>
</template>
</el-table-column>
<el-table-column prop="categoryName" label="分类" width="120" />
<el-table-column prop="viewCount" label="浏览" width="80" align="center" />
<el-table-column prop="likeCount" label="点赞" width="80" align="center" />
<el-table-column prop="commentCount" label="评论" width="80" align="center" />
<el-table-column prop="createdAt" label="发布时间" width="150">
<template #default="{ row }">
{{ formatDateTime(row.createdAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button
size="small"
type="primary"
plain
@click="editPost(row.id)"
>
编辑
</el-button>
<el-button
size="small"
type="danger"
plain
@click="deletePost(row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30, 50]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useUserStore } from '@/stores/user';
import { deletePost as apiDeletePost } from '@/api/forum';
import request from '@/api/request';
//
const router = useRouter();
const userStore = useUserStore();
//
const loading = ref(true);
const posts = ref([]);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const currentSort = ref('latest');
//
const fetchUserPosts = async () => {
if (!userStore.isLoggedIn) {
ElMessage.warning('请先登录');
router.push('/login');
return;
}
loading.value = true;
try {
//
if (!userStore.userInfo) {
await userStore.fetchUserInfo();
}
const userId = userStore.userInfo?.id;
if (!userId) {
throw new Error('无法获取用户ID');
}
// 使API
const response = await request({
url: `/posts/user/${userId}`,
method: 'get',
params: {
page: currentPage.value,
size: pageSize.value,
sort: currentSort.value
}
});
posts.value = response.data.list || [];
total.value = response.data.total || 0;
} catch (error) {
console.error('获取用户帖子失败:', error);
ElMessage.error('获取用户帖子列表失败');
} finally {
loading.value = false;
}
};
//
const editPost = (postId) => {
router.push(`/edit-post/${postId}`);
};
//
const deletePost = async (postId) => {
try {
await ElMessageBox.confirm('确定要删除这篇帖子吗?此操作不可逆。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await apiDeletePost(postId);
ElMessage.success('删除成功');
fetchUserPosts(); //
} catch (error) {
if (error !== 'cancel') {
console.error('删除帖子失败:', error);
ElMessage.error('删除帖子失败');
}
}
};
//
const createNewPost = () => {
router.push('/create-post');
};
//
const handleSizeChange = (size) => {
pageSize.value = size;
fetchUserPosts();
};
const handleCurrentChange = (page) => {
currentPage.value = page;
fetchUserPosts();
};
//
const formatDateTime = (dateTimeStr) => {
if (!dateTimeStr) return '';
const date = new Date(dateTimeStr);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
};
//
onMounted(() => {
fetchUserPosts();
});
</script>
<style scoped>
.my-posts-container {
max-width: 1200px;
margin: 20px auto;
padding: 0 20px;
}
.my-posts-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header h3 {
margin: 0;
font-weight: 500;
}
.loading-container, .empty-container {
padding: 40px 0;
text-align: center;
}
.empty-container .el-button {
margin-top: 16px;
}
.posts-list {
margin-top: 16px;
}
.post-title {
color: var(--el-color-primary);
text-decoration: none;
font-weight: 500;
}
.post-title:hover {
text-decoration: underline;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
</style>

@ -71,11 +71,13 @@
<div class="pagination-container" v-if="postStore.totalPages > 1">
<el-pagination
background
layout="prev, pager, next, jumper, ->, total"
layout="sizes, prev, pager, next, jumper, ->, total"
:total="postStore.totalPosts"
:page-size="postStore.pageSize"
:page-sizes="[5, 10, 20, 50]"
:current-page="postStore.currentPage"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
@ -138,6 +140,10 @@ const handleCurrentChange = (page: number) => {
postStore.fetchPosts({ pageNum: page });
};
const handleSizeChange = (size: number) => {
postStore.fetchPosts({ pageNum: 1, pageSize: size });
};
onMounted(async () => {
await postStore.fetchCategories(); // Fetch categories first
// Fetch posts, it will use the default selectedCategoryId (null) from store initially

Loading…
Cancel
Save