feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置

This commit is contained in:
2025-07-27 10:05:59 +08:00
parent 6903ac1c0d
commit cc886cd4d5
126 changed files with 21179 additions and 15734 deletions
+270
View File
@@ -0,0 +1,270 @@
/**
* 路由守卫
*/
import type { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useAuthStore } from '@/stores/auth'
import { envConfig } from '@/config/env'
// 不需要登录的路由白名单
const whiteList = [
'/login',
'/register',
'/forgot-password',
'/reset-password',
'/',
'/404',
'/403',
'/500'
]
// 需要登录的路由
const authRequiredRoutes = [
'/chat',
'/chat-history',
'/diary',
'/life-milestones',
'/life-trajectory',
'/messages',
'/personal-dashboard',
'/settings',
'/topic-tracker',
'/emotion',
'/map',
'/social',
'/analysis',
'/profile'
]
/**
* 检查路由是否需要认证
*/
const requiresAuth = (path: string): boolean => {
return authRequiredRoutes.some(route => path.startsWith(route))
}
/**
* 检查路由是否在白名单中
*/
const isInWhiteList = (path: string): boolean => {
return whiteList.includes(path) || whiteList.some(route => path.startsWith(route))
}
/**
* 权限检查守卫
*/
const authGuard = async (
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) => {
const authStore = useAuthStore()
// 如果目标路由不需要认证,直接通过
if (!requiresAuth(to.path)) {
next()
return
}
// 检查是否已登录
if (!authStore.isLoggedIn) {
// 未登录,跳转到登录页
ElMessage.warning('请先登录')
next({
path: '/login',
query: { redirect: to.fullPath }
})
return
}
// 已登录,检查token是否有效
try {
// 这里可以添加token验证逻辑
// const isValid = await authStore.validateToken()
// if (!isValid) {
// throw new Error('Token无效')
// }
next()
} catch (error) {
console.error('Token验证失败:', error)
ElMessage.error('登录状态已过期,请重新登录')
// 清除认证状态
await authStore.logout()
// 跳转到登录页
next({
path: '/login',
query: { redirect: to.fullPath }
})
}
}
/**
* 页面标题守卫
*/
const titleGuard = (
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) => {
// 设置页面标题
const title = to.meta.title as string
if (title) {
document.title = `${title} - ${envConfig.appTitle}`
} else {
document.title = envConfig.appTitle
}
next()
}
/**
* 页面加载进度守卫
*/
const progressGuard = (
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) => {
// 这里可以添加页面加载进度条逻辑
// NProgress.start()
next()
}
/**
* 页面加载完成守卫
*/
const progressDoneGuard = () => {
// 这里可以添加页面加载完成逻辑
// NProgress.done()
}
/**
* 登录重定向守卫
*/
const loginRedirectGuard = (
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) => {
const authStore = useAuthStore()
// 如果已登录且访问登录页,重定向到首页
if (authStore.isLoggedIn && (to.path === '/login' || to.path === '/register')) {
const redirect = to.query.redirect as string || '/'
next(redirect)
return
}
next()
}
// 移除权限检查守卫,该功能不存在
/**
* 安装路由守卫
*/
export const setupRouterGuards = (router: Router) => {
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
try {
// 页面加载进度
// NProgress.start()
const authStore = useAuthStore()
console.log('🔍 路由守卫检查:', {
to: to.path,
from: from.path,
isLoggedIn: authStore.isLoggedIn,
hasToken: !!authStore.accessToken,
hasUserInfo: !!authStore.userInfo
})
// 如果已登录且访问登录页,重定向到首页
if (authStore.isLoggedIn && (to.path === '/login' || to.path === '/register')) {
const redirect = to.query.redirect as string || '/'
console.log('🔍 已登录用户访问登录页,重定向到:', redirect)
next(redirect)
return
}
// 检查是否需要认证
if (requiresAuth(to.path)) {
console.log('🔍 页面需要认证:', to.path)
// 如果当前未登录,先尝试恢复本地存储的认证状态
if (!authStore.isLoggedIn) {
console.log('🔍 路由守卫:尝试恢复本地认证状态')
const restored = authStore.restoreLocalAuth()
console.log('🔍 路由守卫:恢复结果:', restored)
if (restored) {
console.log('🔍 认证状态已恢复:', {
isLoggedIn: authStore.isLoggedIn,
hasToken: !!authStore.accessToken,
hasUserInfo: !!authStore.userInfo
})
}
}
// 再次检查登录状态
if (!authStore.isLoggedIn) {
console.log('🔍 用户未登录,跳转到登录页')
// 未登录,跳转到登录页
ElMessage.warning('请先登录')
next({
path: '/login',
query: { redirect: to.fullPath }
})
return
}
console.log('🔍 用户已登录,允许访问:', to.path)
}
// 设置页面标题
const title = to.meta.title as string
if (title) {
document.title = `${title} - ${envConfig.appTitle}`
} else {
document.title = envConfig.appTitle
}
next()
} catch (error) {
console.error('路由守卫错误:', error)
next()
}
})
// 全局后置守卫
router.afterEach((to, from) => {
// 页面加载完成
progressDoneGuard()
// 页面访问统计
if (envConfig.debug) {
console.log(`路由跳转: ${from.path} -> ${to.path}`)
}
})
// 全局解析守卫
router.beforeResolve((to, from, next) => {
// 这里可以添加异步数据加载逻辑
next()
})
}
export {
authGuard,
titleGuard,
progressGuard,
progressDoneGuard,
loginRedirectGuard,
requiresAuth,
isInWhiteList
}
+172 -92
View File
@@ -1,5 +1,19 @@
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import { setupRouterGuards } from './guards'
// 扩展路由元信息类型
declare module 'vue-router' {
interface RouteMeta {
title?: string
requiresAuth?: boolean
permission?: string
role?: string
icon?: string
hidden?: boolean
keepAlive?: boolean
}
}
const routes: RouteRecordRaw[] = [
{
@@ -7,8 +21,9 @@ const routes: RouteRecordRaw[] = [
name: 'Home',
component: () => import('@/views/Home/index.vue'),
meta: {
title: '开心APP - 你的情绪陪伴使者',
keepAlive: true
title: '首页',
requiresAuth: false,
icon: 'House'
}
},
{
@@ -16,8 +31,20 @@ const routes: RouteRecordRaw[] = [
name: 'Chat',
component: () => import('@/views/Chat/index.vue'),
meta: {
title: '与开开聊天',
requiresAuth: false
title: 'AI对话',
requiresAuth: true,
icon: 'ChatDotRound',
keepAlive: true
}
},
{
path: '/chat-history',
name: 'ChatHistory',
component: () => import('@/views/ChatHistory/index.vue'),
meta: {
title: '聊天历史',
requiresAuth: true,
icon: 'Clock'
}
},
{
@@ -26,34 +53,18 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/Diary/index.vue'),
meta: {
title: '情绪日记',
requiresAuth: false
requiresAuth: true,
icon: 'EditPen'
}
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard/index.vue'),
path: '/life-milestones',
name: 'LifeMilestones',
component: () => import('@/views/LifeMilestones/index.vue'),
meta: {
title: '个人展板',
requiresAuth: false
}
},
{
path: '/profile',
name: 'Profile',
component: () => import('@/views/Profile/index.vue'),
meta: {
title: '个人中心',
requiresAuth: true
}
},
{
path: '/topic-tracker',
name: 'TopicTracker',
component: () => import('@/views/TopicTracker/index.vue'),
meta: {
title: '话题追踪',
requiresAuth: false
title: '人生里程碑',
requiresAuth: true,
icon: 'Trophy'
}
},
{
@@ -62,7 +73,8 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/LifeTrajectory/index.vue'),
meta: {
title: '人生轨迹',
requiresAuth: false
requiresAuth: true,
icon: 'TrendCharts'
}
},
{
@@ -71,7 +83,18 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/Messages/index.vue'),
meta: {
title: '消息中心',
requiresAuth: false
requiresAuth: true,
icon: 'Message'
}
},
{
path: '/personal-dashboard',
name: 'PersonalDashboard',
component: () => import('@/views/PersonalDashboard/index.vue'),
meta: {
title: '个人仪表盘',
requiresAuth: true,
icon: 'DataBoard'
}
},
{
@@ -79,26 +102,81 @@ const routes: RouteRecordRaw[] = [
name: 'Settings',
component: () => import('@/views/Settings/index.vue'),
meta: {
title: '用户设置',
requiresAuth: false
title: '设置',
requiresAuth: true,
icon: 'Setting'
}
},
{
path: '/chat-history',
name: 'ChatHistory',
component: () => import('@/views/Chat/History.vue'),
path: '/topic-tracker',
name: 'TopicTracker',
component: () => import('@/views/TopicTracker/index.vue'),
meta: {
title: '聊天历史',
requiresAuth: false
title: '话题追踪',
requiresAuth: true,
icon: 'Search'
}
},
// 兼容原有页面
{
path: '/emotion',
name: 'Emotion',
component: () => import('@/views/Emotion/index.vue'),
meta: {
title: '情绪管理',
requiresAuth: true,
icon: 'Sunny'
}
},
{
path: '/map',
name: 'Map',
component: () => import('@/views/Map/index.vue'),
meta: {
title: '情绪地图',
requiresAuth: true,
icon: 'Location'
}
},
{
path: '/social',
name: 'Social',
component: () => import('@/views/Social/index.vue'),
meta: {
title: '社交分享',
requiresAuth: true,
icon: 'Share'
}
},
{
path: '/analysis',
name: 'Analysis',
component: () => import('@/views/Analysis/index.vue'),
meta: {
title: '情绪分析',
requiresAuth: true,
icon: 'TrendCharts'
}
},
{
path: '/profile',
name: 'Profile',
component: () => import('@/views/Profile/index.vue'),
meta: {
title: '个人中心',
requiresAuth: true,
icon: 'User'
}
},
// 认证相关页面
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login/index.vue'),
meta: {
title: '用户登录',
requiresAuth: false
title: '登录',
requiresAuth: false,
hidden: true
}
},
{
@@ -106,25 +184,65 @@ const routes: RouteRecordRaw[] = [
name: 'Register',
component: () => import('@/views/Register/index.vue'),
meta: {
title: '用户注册',
requiresAuth: false
title: '注册',
requiresAuth: false,
hidden: true
}
},
// 调试页面(仅开发环境)
{
path: '/debug',
name: 'Debug',
component: () => import('@/views/Debug/index.vue'),
meta: {
title: '环境变量调试',
requiresAuth: false,
hidden: true
}
},
{
path: '/debug/websocket',
name: 'WebSocketTest',
component: () => import('@/views/Debug/WebSocketTest.vue'),
meta: {
title: 'WebSocket测试',
requiresAuth: false,
hidden: true
}
},
// 错误页面
{
path: '/403',
name: 'Forbidden',
component: () => import('@/views/NotFound/index.vue'),
meta: {
title: '访问被拒绝',
requiresAuth: false,
hidden: true
}
},
{
path: '/404',
name: 'NotFound',
component: () => import('@/views/NotFound/index.vue'),
meta: {
title: '页面未找到',
requiresAuth: false,
hidden: true
}
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFound.vue'),
meta: {
title: '页面未找到'
}
redirect: '/404'
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
history: createWebHistory('/emotion-museum/'),
routes,
scrollBehavior(_to, _from, savedPosition) {
scrollBehavior(to, from, savedPosition) {
// 路由切换时的滚动行为
if (savedPosition) {
return savedPosition
} else {
@@ -133,49 +251,11 @@ const router = createRouter({
}
})
// 路由守卫
router.beforeEach(async (to, _from, next) => {
// 设置页面标题
if (to.meta.title) {
document.title = to.meta.title as string
}
// 检查是否需要认证
if (to.meta.requiresAuth) {
// 动态导入用户store以避免循环依赖
const { useUserStore } = await import('@/stores/user')
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
// 保存当前路径,登录后跳转回来
next({
path: '/login',
query: { redirect: to.fullPath }
})
return
}
}
// 如果已登录用户访问登录/注册页面,重定向到首页
if (to.path === '/login' || to.path === '/register') {
const { useUserStore } = await import('@/stores/user')
const userStore = useUserStore()
console.log('路由守卫检查登录状态:', {
path: to.path,
isLoggedIn: userStore.isLoggedIn,
token: !!userStore.token,
userInfo: !!userStore.userInfo
})
if (userStore.isLoggedIn) {
console.log('用户已登录,重定向到首页')
next('/')
return
}
}
next()
})
// 设置路由守卫
setupRouterGuards(router)
export default router
// 导出路由相关工具
export { routes }
export type { RouteRecordRaw }