feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置
This commit is contained in:
@@ -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
@@ -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 }
|
||||
Reference in New Issue
Block a user