From c8063fa38f7e6cf4e37448f2325aa9635b510b50 Mon Sep 17 00:00:00 2001 From: wuhans-first-deep-love <2966551326@qq.com> Date: Thu, 29 May 2025 21:12:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=B8=96=E5=AD=90=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=95=8C=E9=9D=A2=EF=BC=8C=E6=B7=BB=E5=8A=A0=E4=B8=AA?= =?UTF-8?q?=E4=BA=BA=E4=B8=BB=E9=A1=B5=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Front/vue-unilife/package.json | 2 +- Front/vue-unilife/pnpm-lock.yaml | 2 +- Front/vue-unilife/src/assets/style/style.css | 63 ++ Front/vue-unilife/src/components/Personal.vue | 6 +- Front/vue-unilife/src/routers/routers.ts | 22 +- Front/vue-unilife/src/utils/post.ts | 149 ++++ Front/vue-unilife/src/views/Home.vue | 292 +++++++- .../vue-unilife/src/views/PostManagement.vue | 636 ++++++++++++++++++ Front/vue-unilife/src/views/post.ts | 111 +++ Front/vue-unilife/src/views/usePost.ts | 138 ++++ 10 files changed, 1403 insertions(+), 18 deletions(-) create mode 100644 Front/vue-unilife/src/utils/post.ts create mode 100644 Front/vue-unilife/src/views/PostManagement.vue create mode 100644 Front/vue-unilife/src/views/post.ts create mode 100644 Front/vue-unilife/src/views/usePost.ts diff --git a/Front/vue-unilife/package.json b/Front/vue-unilife/package.json index 1b2f9ef..ca8012d 100644 --- a/Front/vue-unilife/package.json +++ b/Front/vue-unilife/package.json @@ -19,7 +19,7 @@ "marked-highlight": "^2.2.1", "vee-validate": "^4.15.0", "vue": "^3.5.13", - "vue-router": "^4.5.0", + "vue-router": "4", "yup": "^1.6.1" }, "devDependencies": { diff --git a/Front/vue-unilife/pnpm-lock.yaml b/Front/vue-unilife/pnpm-lock.yaml index b559b18..9dda154 100644 --- a/Front/vue-unilife/pnpm-lock.yaml +++ b/Front/vue-unilife/pnpm-lock.yaml @@ -39,7 +39,7 @@ importers: specifier: ^3.5.13 version: 3.5.13(typescript@5.7.3) vue-router: - specifier: ^4.5.0 + specifier: '4' version: 4.5.0(vue@3.5.13(typescript@5.7.3)) yup: specifier: ^1.6.1 diff --git a/Front/vue-unilife/src/assets/style/style.css b/Front/vue-unilife/src/assets/style/style.css index 399523b..2af3219 100644 --- a/Front/vue-unilife/src/assets/style/style.css +++ b/Front/vue-unilife/src/assets/style/style.css @@ -70,6 +70,53 @@ button { transform: translateY(-2px); } +/* ====================== + 圆形按钮扩展 (追加在原有.btn样式之后) + ====================== */ +.btn-circle { + /* 复用现有按钮基础样式 */ + @apply btn btn-primary; /* 如果使用Tailwind这类工具 */ + + /* 新增圆形特性 */ + --size: 56px; + width: var(--size); + height: var(--size); + padding: 0; + border-radius: 50%; + display: inline-flex; + justify-content: center; + align-items: center; + + /* 定位系统(新增) */ + position: fixed; + right: 30px; + bottom: 30px; + margin: 0 !important; /* 覆盖原有margin */ + + /* 层级管理 */ + z-index: 1000; + + /* 复用现有悬停动画 */ + /* 原有.btn-primary:hover已包含效果 */ +} + +/* 图标微调(新增) */ +.btn-circle .btn-icon { + font-size: 1.8rem; + line-height: 1; + margin-top: -3px; /* 视觉居中补偿 */ +} + +/* 响应式调整(新增) */ +@media (max-width: 768px) { + .btn-circle { + --size: 50px; + right: 15px; + bottom: 15px; + } +} + + /*信息展示在card上*/ .card { background-color: #fff; @@ -130,3 +177,19 @@ button { background-color: #f9f9f9; } } + + +/* 帖子管理容器样式 */ +.post-management { + padding: 30px; /* 比原来的20px更大 */ + width: 100%; + max-width: 1800px; /* 比建议的1200px更宽 */ + margin: 0 auto; + min-height: 80vh; + box-sizing: border-box; + + /* 添加一些额外的样式让内容更突出 */ + background-color: var(--light-purple); + border-radius: 20px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); +} \ No newline at end of file diff --git a/Front/vue-unilife/src/components/Personal.vue b/Front/vue-unilife/src/components/Personal.vue index 7c76b2d..e1dea39 100644 --- a/Front/vue-unilife/src/components/Personal.vue +++ b/Front/vue-unilife/src/components/Personal.vue @@ -59,14 +59,14 @@ export default defineComponent({
个人课表
-
  • - +
  • +
    -
    测试样例3
    +
    帖子管理
  • diff --git a/Front/vue-unilife/src/routers/routers.ts b/Front/vue-unilife/src/routers/routers.ts index 76c1b57..95e236b 100644 --- a/Front/vue-unilife/src/routers/routers.ts +++ b/Front/vue-unilife/src/routers/routers.ts @@ -1,12 +1,14 @@ -import type { RouteRecordRaw } from 'vue-router'; -import { createRouter, createWebHistory } from 'vue-router'; +import type { RouteRecord, RouteRecordRaw } from 'vue-router'; +import { createWebHashHistory, createRouter,createWebHistory } from 'vue-router'; import LogPage from '../views/LogPage.vue'; import Personal from '@/components/Personal.vue'; import Manager from '@/views/AcountManager.vue'; import PersonalHome from '@/views/Home.vue'; import ForumHome from '@/views/ForumHome.vue'; +import PostManager from '@/views/PostManagement.vue'; import Curriculum from '@/views/Curriculum.vue'; -import DirectMessage from '@/views/DirectMessage.vue'; +import DirectMessage from '@/views/DirectMessage.vue'; +import AIManager from '@/views/AiManager.vue' const routes: Array = [ { @@ -39,13 +41,19 @@ const routes: Array = [ component: Manager, }, { - path: 'ai', - redirect: '/personal', + path:'ai', + name:'AIManager', + component:AIManager, }, { path: 'curriculum', name: 'Curriculum', component: Curriculum, + }, + { + path:'postManager', + name:'PostManager', + component:PostManager, } ] }, @@ -55,7 +63,7 @@ const routes: Array = [ component: DirectMessage, }, { - path: '/uniLifeHome', + path:'/uniLifeHome', name: 'ForumHome', component: ForumHome, }, @@ -64,6 +72,8 @@ const routes: Array = [ name: 'PostDetail', component: () => import('@/views/PostDetailPage.vue'), }, + + ]; const router = createRouter({ diff --git a/Front/vue-unilife/src/utils/post.ts b/Front/vue-unilife/src/utils/post.ts new file mode 100644 index 0000000..f4757db --- /dev/null +++ b/Front/vue-unilife/src/utils/post.ts @@ -0,0 +1,149 @@ +// api/post.ts +import request from './request' +import type { Post, PostListParams, PostListResponse } from '@/views/post' + +export const postApi = { + // 获取我的帖子列表 + getMyPosts(params: PostListParams): Promise { + return request({ + url: '/posts/my-posts', + method: 'GET', + params + }) + }, + + // 获取帖子详情 + getPostDetail(id: number): Promise { + return request({ + url: `/posts/${id}`, + method: 'GET' + }) + }, + + // 创建帖子 + createPost(data: Partial): Promise { + return request({ + url: '/posts', + method: 'POST', + data + }) + }, + + // 更新帖子 + updatePost(id: number, data: Partial): Promise { + return request({ + url: `/posts/${id}`, + method: 'PUT', + data + }) + }, + + // 删除单个帖子 + deletePost(id: number): Promise { + return request({ + url: `/posts/${id}`, + method: 'DELETE' + }) + }, + + // 批量删除帖子 + batchDeletePosts(ids: number[]): Promise { + return request({ + url: '/posts/batch-delete', + method: 'DELETE', + data: { ids } + }) + }, + + // 发布帖子(从草稿状态发布) + publishPost(id: number): Promise { + return request({ + url: `/posts/${id}/publish`, + method: 'POST' + }) + }, + + // 将帖子设为草稿 + draftPost(id: number): Promise { + return request({ + url: `/posts/${id}/draft`, + method: 'POST' + }) + }, + + // 获取帖子统计信息 + getPostStats(): Promise<{ + totalPosts: number + totalViews: number + totalLikes: number + totalComments: number + todayPosts: number + weekPosts: number + monthPosts: number + }> { + return request({ + url: '/posts/stats', + method: 'GET' + }) + }, + + // 点赞帖子 + likePost(id: number): Promise<{ liked: boolean; likesCount: number }> { + return request({ + url: `/posts/${id}/like`, + method: 'POST' + }) + }, + + // 取消点赞 + unlikePost(id: number): Promise<{ liked: boolean; likesCount: number }> { + return request({ + url: `/posts/${id}/unlike`, + method: 'POST' + }) + }, + + // 增加浏览量 + increaseViews(id: number): Promise<{ views: number }> { + return request({ + url: `/posts/${id}/view`, + method: 'POST' + }) + }, + + // 搜索帖子 + searchPosts(params: { + keyword: string + category?: string + page?: number + pageSize?: number + }): Promise { + return request({ + url: '/posts/search', + method: 'GET', + params + }) + }, + + // 获取热门帖子 + getHotPosts(params: { + period?: 'day' | 'week' | 'month' + limit?: number + } = {}): Promise { + return request({ + url: '/posts/hot', + method: 'GET', + params + }) + }, + + // 获取推荐帖子 + getRecommendedPosts(limit = 10): Promise { + return request({ + url: '/posts/recommended', + method: 'GET', + params: { limit } + }) + } +} +// \ No newline at end of file diff --git a/Front/vue-unilife/src/views/Home.vue b/Front/vue-unilife/src/views/Home.vue index 170b5e6..b5965a5 100644 --- a/Front/vue-unilife/src/views/Home.vue +++ b/Front/vue-unilife/src/views/Home.vue @@ -1,21 +1,299 @@ \ No newline at end of file diff --git a/Front/vue-unilife/src/views/PostManagement.vue b/Front/vue-unilife/src/views/PostManagement.vue new file mode 100644 index 0000000..b10f3f6 --- /dev/null +++ b/Front/vue-unilife/src/views/PostManagement.vue @@ -0,0 +1,636 @@ + + + + + \ No newline at end of file diff --git a/Front/vue-unilife/src/views/post.ts b/Front/vue-unilife/src/views/post.ts new file mode 100644 index 0000000..e76142a --- /dev/null +++ b/Front/vue-unilife/src/views/post.ts @@ -0,0 +1,111 @@ +// types/post.ts +export interface Post { + id: number + title: string + content?: string + category: string + views: number + likes: number + comments?: number + createdAt: string + updatedAt?: string + status: 'published' | 'draft' + author?: { + id: number + username: string + avatar?: string + } +} + +export interface PostListParams { + page: number + pageSize: number + title?: string + category?: string + status?: string + sortBy?: 'createdAt' | 'views' | 'likes' + sortOrder?: 'asc' | 'desc' +} + +export interface PostListResponse { + data: Post[] + total: number + page: number + pageSize: number + totalPages: number +} + +export interface FilterForm { + title: string + category: string + status?: string +} + +export interface Pagination { + currentPage: number + pageSize: number + total: number +} + +export interface Stats { + totalPosts: number + totalViews: number + totalLikes: number + totalComments: number + avgViews: number + avgLikes: number +} + +export interface DeleteDialog { + visible: boolean + loading: boolean + type: 'single' | 'batch' + post?: Post +} + +// types/api.ts +export interface ApiResponse { + code: number + message: string + data: T + timestamp: number +} + +export interface ApiError { + code: number + message: string + details?: any +} + +// types/category.ts +export interface Category { + id: string + name: string + description?: string + color?: string + icon?: string +} + +export const CATEGORIES: Category[] = [ + { id: 'tech', name: '技术讨论', color: 'primary', icon: 'Monitor' }, + { id: 'life', name: '生活随笔', color: 'success', icon: 'Coffee' }, + { id: 'study', name: '学习分享', color: 'warning', icon: 'Reading' }, + { id: 'qa', name: '问答求助', color: 'info', icon: 'QuestionFilled' } +] + +// types/user.ts +export interface User { + id: number + username: string + email: string + avatar?: string + role: 'admin' | 'user' + createdAt: string + profile?: { + nickname?: string + bio?: string + location?: string + website?: string + } +} + diff --git a/Front/vue-unilife/src/views/usePost.ts b/Front/vue-unilife/src/views/usePost.ts new file mode 100644 index 0000000..26c2a87 --- /dev/null +++ b/Front/vue-unilife/src/views/usePost.ts @@ -0,0 +1,138 @@ +// hooks/usePost.ts +import { ref, reactive, computed } from 'vue' +import type { Post, PostListParams, FilterForm, Pagination, Stats } from '@/views/post' +import { postApi } from '@/utils/post' +import { ElMessage } from 'element-plus' + +export function usePostManagement() { + const loading = ref(false) + const posts = ref([]) + const selectedPosts = ref([]) + + const filterForm = reactive({ + title: '', + category: '', + status: '' + }) + + const pagination = reactive({ + currentPage: 1, + pageSize: 20, + total: 0 + }) + + // 计算统计数据 + const stats = computed(() => { + const totalPosts = posts.value.length + const totalViews = posts.value.reduce((sum, post) => sum + post.views, 0) + const totalLikes = posts.value.reduce((sum, post) => sum + post.likes, 0) + const totalComments = posts.value.reduce((sum, post) => sum + (post.comments || 0), 0) + const avgViews = totalPosts > 0 ? totalViews / totalPosts : 0 + const avgLikes = totalPosts > 0 ? totalLikes / totalPosts : 0 + + return { + totalPosts, + totalViews, + totalLikes, + totalComments, + avgViews, + avgLikes + } + }) + + // 获取帖子列表 + const fetchPosts = async () => { + loading.value = true + try { + const params: PostListParams = { + page: pagination.currentPage, + pageSize: pagination.pageSize, + title: filterForm.title || undefined, + category: filterForm.category || undefined, + status: filterForm.status || undefined + } + + const response = await postApi.getMyPosts(params) + posts.value = response.data + pagination.total = response.total + } catch (error) { + ElMessage.error('获取帖子列表失败') + console.error('Fetch posts error:', error) + } finally { + loading.value = false + } + } + + // 删除帖子 + const deletePost = async (postId: number): Promise => { + try { + await postApi.deletePost(postId) + ElMessage.success('删除成功') + return true + } catch (error) { + ElMessage.error('删除失败') + console.error('Delete post error:', error) + return false + } + } + + // 批量删除 + const batchDeletePosts = async (postIds: number[]): Promise => { + try { + await postApi.batchDeletePosts(postIds) + ElMessage.success(`成功删除 ${postIds.length} 个帖子`) + return true + } catch (error) { + ElMessage.error('批量删除失败') + console.error('Batch delete posts error:', error) + return false + } + } + + // 搜索 + const handleSearch = () => { + pagination.currentPage = 1 + fetchPosts() + } + + // 重置筛选 + const handleReset = () => { + Object.keys(filterForm).forEach(key => { + filterForm[key as keyof FilterForm] = '' + }) + handleSearch() + } + + // 选择变化 + const handleSelectionChange = (selection: Post[]) => { + selectedPosts.value = selection + } + + // 分页变化 + const handleSizeChange = (newSize: number) => { + pagination.pageSize = newSize + fetchPosts() + } + + const handleCurrentChange = (newPage: number) => { + pagination.currentPage = newPage + fetchPosts() + } + + return { + loading, + posts, + selectedPosts, + filterForm, + pagination, + stats, + fetchPosts, + deletePost, + batchDeletePosts, + handleSearch, + handleReset, + handleSelectionChange, + handleSizeChange, + handleCurrentChange + } +} \ No newline at end of file