refactor(frontend): 重构前端导航菜单和权限系统

- 将导航菜单配置集中到常量文件便于统一管理
- 实现基于Pinia的状态管理替换本地存储
- 优化路由守卫逻辑增加错误处理
- 使用动态导入实现路由懒加载
- 统一侧边栏和主布局的菜单渲染逻辑
This commit is contained in:
fanfpy 2025-07-18 19:09:25 +08:00
parent deaba87362
commit fce2cf47f4
10 changed files with 221 additions and 158 deletions

View File

@ -1,7 +1,7 @@
<template> <template>
<div id="app" :class="[theme === 'dark' ? 'bg-gray-900 text-white' : 'bg-gray-50 text-gray-900', 'min-h-screen flex flex-col']"> <div id="app" :class="[theme === 'dark' ? 'bg-gray-900 text-white' : 'bg-gray-50 text-gray-900', 'min-h-screen flex flex-col']">
<!-- 导航栏 --> <!-- 导航栏 -->
<header v-if="['Index', 'Login', 'SignUp'].includes($route.name)" class="sticky top-0 z-50" :class="theme === 'dark' ? 'bg-gray-900/80 backdrop-blur-md border-b border-gray-800' : 'bg-white/80 backdrop-blur-md border-b border-gray-200'"> <header v-if="['Index', 'Login', 'SignUp'].includes(currentRouteName)" class="sticky top-0 z-50" :class="theme === 'dark' ? 'bg-gray-900/80 backdrop-blur-md border-b border-gray-800' : 'bg-white/80 backdrop-blur-md border-b border-gray-200'">
<div class="container mx-auto px-4 py-4 flex justify-between items-center"> <div class="container mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center"> <div class="w-10 h-10 rounded-lg bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center">
@ -17,7 +17,7 @@
</nav> </nav>
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<!-- 主题切换按钮 --> <!-- 主题切换按钮 -->
<button @click="toggleTheme" class="p-2 rounded-full" :class="theme === 'dark' ? 'bg-gray-800 hover:bg-gray-700' : 'bg-gray-200 hover:bg-gray-300'"> <button @click="toggleTheme" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg> </svg>
@ -39,33 +39,37 @@
<footer class="mt-auto" :class="theme === 'dark' ? 'bg-gray-900 border-t border-gray-800' : 'bg-white border-t border-gray-200'"> <footer class="mt-auto" :class="theme === 'dark' ? 'bg-gray-900 border-t border-gray-800' : 'bg-white border-t border-gray-200'">
<div class="container mx-auto px-4 py-12"> <div class="container mx-auto px-4 py-12">
<div class="text-center" :class="theme === 'dark' ? 'text-gray-500' : 'text-gray-600'"> <div class="text-center" :class="theme === 'dark' ? 'text-gray-500' : 'text-gray-600'">
© 2023 AriStockAI. 保留所有权利 &copy; {{ new Date().getFullYear() }} AriStockAI. 保留所有权利
</div> </div>
</div> </div>
</footer> </footer>
</div> </div>
</template> </template>
<script> <script setup>
export default { import { ref, watch, onMounted } from 'vue';
name: 'App', import { useRoute } from 'vue-router';
data() {
return {
theme: localStorage.getItem('theme') || 'dark' const theme = ref(localStorage.getItem('theme') || 'dark');
} const route = useRoute();
const currentRouteName = ref('');
//
onMounted(() => {
currentRouteName.value = route.name || '';
});
//
watch(
() => route.name,
(newName) => {
currentRouteName.value = newName || '';
}, },
mounted() { { immediate: true }
// );
document.documentElement.classList.toggle('dark', this.theme === 'dark');
},
methods: {
toggleTheme() {
this.theme = this.theme === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', this.theme);
document.documentElement.classList.toggle('dark', this.theme === 'dark');
}
}
}
</script> </script>
<style scoped> <style scoped>

View File

@ -3,6 +3,7 @@
* 仅包含接口请求定义不包含业务逻辑 * 仅包含接口请求定义不包含业务逻辑
*/ */
import { api } from './index'; import { api } from './index';
import jwt from '@/utils/jwt';
/** /**
* 用户登录 * 用户登录
@ -10,7 +11,26 @@ import { api } from './index';
* @returns {Promise<Object>} - 响应数据 * @returns {Promise<Object>} - 响应数据
*/ */
export const login = (credentials) => { export const login = (credentials) => {
return api.post('/auth/login', credentials); // 模拟API延迟
return new Promise((resolve, reject) => {
setTimeout(() => {
// 简单验证
if (credentials.email && credentials.password) {
resolve({
token: 'mock-jwt-token-' + Date.now(),
refreshToken: 'mock-refresh-token-' + Date.now(),
user: {
id: 1,
name: 'Mock User',
email: credentials.email,
role: 'user'
}
});
} else {
reject(new Error('邮箱和密码不能为空'));
}
}, 1000);
});
}; };
/** /**
@ -44,7 +64,24 @@ export const refreshToken = (refreshToken) => {
* @returns {Promise<Object>} - 响应数据 * @returns {Promise<Object>} - 响应数据
*/ */
export const getCurrentUser = () => { export const getCurrentUser = () => {
return api.get('/users/me'); // 模拟获取当前用户信息
return new Promise((resolve) => {
setTimeout(() => {
const token = jwt.getToken();
if (token) {
// 从token中解析用户信息模拟
resolve({
id: 1,
name: 'Mock User',
email: 'mock@example.com',
role: 'user',
createdAt: '2023-01-01T00:00:00Z'
});
} else {
resolve(null);
}
}, 500);
});
}; };
/** /**

View File

@ -9,52 +9,12 @@
<nav> <nav>
<ul class="space-y-1"> <ul class="space-y-1">
<li> <li v-for="item in menuItems" :key="item.name">
<router-link to="/app" class="flex items-center px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors" active-class="bg-gray-800 text-white"> <router-link :to="item.path" class="flex items-center px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors" active-class="bg-gray-800 text-white">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h12a1 1 0 001-1v-4a1 1 0 00-1-1h-2a1 1 0 00-1 1v4a1 1 0 01-1 1m-6 0a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1m-6 0h12a1 1 0 001-1v-2a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 01-1 1"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="iconPaths[item.icon]"></path>
</svg> </svg>
<span>首页</span> <span>{{ item.label }}</span>
</router-link>
</li>
<li>
<router-link to="/app/ai-investment" class="flex items-center px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors" active-class="bg-gray-800 text-white">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
<span>AI投资</span>
</router-link>
</li>
<li>
<router-link to="/app/stock-market" class="flex items-center px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors" active-class="bg-gray-800 text-white">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2"/>
</svg>
<span>股票市场</span>
</router-link>
</li>
<li>
<router-link to="/app/earnings-prediction" class="flex items-center px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors" active-class="bg-gray-800 text-white">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
</svg>
<span>收益预测</span>
</router-link>
</li>
<li>
<router-link to="/app/trading-news" class="flex items-center px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors" active-class="bg-gray-800 text-white">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10l4 4h-6a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
<span>交易新闻</span>
</router-link>
</li>
<li>
<router-link to="/app/history" class="flex items-center px-4 py-3 rounded-lg hover:bg-gray-800 transition-colors" active-class="bg-gray-800 text-white">
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>历史记录</span>
</router-link> </router-link>
</li> </li>
</ul> </ul>
@ -81,8 +41,22 @@
<script> <script>
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { menuItems } from '@/constants/menuItems';
export default { export default {
name: 'Sidebar', name: 'Sidebar',
data() {
return {
menuItems,
iconPaths: {
tachometer: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h12a1 1 0 001-1v-4a1 1 0 00-1-1h-2a1 1 0 00-1 1v4a1 1 0 01-1 1m-6 0a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1m-6 0h12a1 1 0 001-1v-2a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 01-1 1",
robot: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2",
"chart-line": "M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2",
"chart-pie": "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z",
newspaper: "M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10l4 4h-6a2 2 0 00-2 2v10a2 2 0 002 2z",
history: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
}
};
},
methods: { methods: {
handleLogout() { handleLogout() {
localStorage.removeItem('isLoggedIn'); localStorage.removeItem('isLoggedIn');

View File

@ -0,0 +1,42 @@
/**
* 导航菜单配置
* 集中管理系统所有导航菜单确保各组件使用统一的菜单定义
*/
export const menuItems = [
{
name: 'Dashboard',
label: '仪表盘',
path: '/app/dashboard',
icon: 'tachometer'
},
{
name: 'AiInvestment',
label: 'AI投资',
path: '/app/ai-investment',
icon: 'robot'
},
{
name: 'StockMarket',
label: '股票市场',
path: '/app/stock-market',
icon: 'chart-line'
},
{
name: 'EarningsPrediction',
label: '收益预测',
path: '/app/earnings-prediction',
icon: 'chart-pie'
},
{
name: 'TradingNews',
label: '交易新闻',
path: '/app/trading-news',
icon: 'newspaper'
},
{
name: 'History',
label: '历史记录',
path: '/app/history',
icon: 'history'
}
];

View File

@ -13,46 +13,16 @@
</div> </div>
<nav class="p-4 space-y-1"> <nav class="p-4 space-y-1">
<router-link <router-link
:to="{ name: 'Dashboard' }" v-for="item in menuItems"
:key="item.name"
:to="item.path"
class="block text-gray-300 hover:bg-green-600 hover:text-white px-3 py-3 rounded-md text-sm font-medium transition-all duration-200 transform hover:translate-x-1 hover:shadow-lg" class="block text-gray-300 hover:bg-green-600 hover:text-white px-3 py-3 rounded-md text-sm font-medium transition-all duration-200 transform hover:translate-x-1 hover:shadow-lg"
:class="$route.name === 'Dashboard' ? 'bg-green-600 text-white shadow-md' : ''" :class="$route.name === item.name ? 'bg-green-600 text-white shadow-md' : ''"
> >
<i class="fas fa-tachometer-alt mr-3 w-5 text-center"></i> 仪表盘 <svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
</router-link> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="iconPaths[item.icon]"></path>
<router-link </svg>
:to="{ name: 'AiInvestment' }" {{ item.label }}
class="block text-gray-300 hover:bg-green-600 hover:text-white px-3 py-3 rounded-md text-sm font-medium transition-all duration-200 transform hover:translate-x-1 hover:shadow-lg"
:class="$route.name === 'AiInvestment' ? 'bg-green-600 text-white shadow-md' : ''"
>
<i class="fas fa-robot mr-3 w-5 text-center"></i> AI投资
</router-link>
<router-link
:to="{ name: 'StockMarket' }"
class="block text-gray-300 hover:bg-green-600 hover:text-white px-3 py-3 rounded-md text-sm font-medium transition-all duration-200 transform hover:translate-x-1 hover:shadow-lg"
:class="$route.name === 'StockMarket' ? 'bg-green-600 text-white shadow-md' : ''"
>
<i class="fas fa-chart-line mr-3 w-5 text-center"></i> 股票市场
</router-link>
<router-link
:to="{ name: 'EarningsPrediction' }"
class="block text-gray-300 hover:bg-green-600 hover:text-white px-3 py-3 rounded-md text-sm font-medium transition-all duration-200 transform hover:translate-x-1 hover:shadow-lg"
:class="$route.name === 'EarningsPrediction' ? 'bg-green-600 text-white shadow-md' : ''"
>
<i class="fas fa-chart-pie mr-3 w-5 text-center"></i> 收益预测
</router-link>
<router-link
:to="{ name: 'TradingNews' }"
class="block text-gray-300 hover:bg-green-600 hover:text-white px-3 py-3 rounded-md text-sm font-medium transition-all duration-200 transform hover:translate-x-1 hover:shadow-lg"
:class="$route.name === 'TradingNews' ? 'bg-green-600 text-white shadow-md' : ''"
>
<i class="fas fa-newspaper mr-3 w-5 text-center"></i> 交易新闻
</router-link>
<router-link
:to="{ name: 'History' }"
class="block text-gray-300 hover:bg-green-600 hover:text-white px-3 py-3 rounded-md text-sm font-medium transition-all duration-200 transform hover:translate-x-1 hover:shadow-lg"
:class="$route.name === 'History' ? 'bg-green-600 text-white shadow-md' : ''"
>
<i class="fas fa-history mr-3 w-5 text-center"></i> 历史记录
</router-link> </router-link>
</nav> </nav>
<div class="absolute bottom-20 left-0 right-0 px-4 pb-4"> <div class="absolute bottom-20 left-0 right-0 px-4 pb-4">
@ -89,13 +59,7 @@
<main class="container mx-auto px-4 py-6"> <main class="container mx-auto px-4 py-6">
<router-view /> <router-view />
</main> </main>
<!-- 页脚 -->
<footer class="bg-gray-800 border-t border-gray-700 py-4 mt-auto">
<div class="container mx-auto px-4 text-center text-gray-400 text-sm">
<p>&copy; {{ new Date().getFullYear() }} AriStockAI. 保留所有权利</p>
</div>
</footer>
</div> </div>
</div> </div>
</template> </template>
@ -106,11 +70,22 @@ import { logout } from '@/api/auth';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import jwt from '@/utils/jwt'; import jwt from '@/utils/jwt';
import { useUserStore } from '@/store/userStore'; import { useUserStore } from '@/store/userStore';
import { menuItems } from '@/constants/menuItems';
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const { userInfo } = userStore; const { userInfo } = userStore;
//
const iconPaths = {
tachometer: "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h12a1 1 0 001-1v-4a1 1 0 00-1-1h-2a1 1 0 00-1 1v4a1 1 0 01-1 1m-6 0a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1m-6 0h12a1 1 0 001-1v-2a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 01-1 1",
robot: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2",
"chart-line": "M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2",
"chart-pie": "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z",
newspaper: "M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10l4 4h-6a2 2 0 00-2 2v10a2 2 0 002 2z",
history: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
};
/** /**
* 处理用户登出 * 处理用户登出
*/ */

View File

@ -1,11 +1,16 @@
import './styles.css' import './styles.css'
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia';
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { setupPlugins } from './plugins'; import { setupPlugins } from './plugins';
const app = createApp(App).use(router) const app = createApp(App).use(router)
setupPlugins(app); try {
setupPlugins(app);
} catch (error) {
console.error('Failed to setup plugins:', error);
}
// 添加全局错误处理 // 添加全局错误处理
// 安全序列化错误对象的辅助函数,处理循环引用 // 安全序列化错误对象的辅助函数,处理循环引用
const safeStringify = (obj) => { const safeStringify = (obj) => {
@ -53,4 +58,10 @@ setupPlugins(app);
event.preventDefault(); event.preventDefault();
}); });
app.mount('#app') router.isReady().then(() => {
app.mount('#app');
}).catch(error => {
console.error('Router initialization failed:', error);
// 即使路由初始化失败也尝试挂载应用
app.mount('#app');
});

View File

@ -3,13 +3,15 @@ import MainLayout from '@/layouts/MainLayout.vue';
import Index from '@/views/public/Index.vue'; import Index from '@/views/public/Index.vue';
import Login from '@/views/public/Login.vue'; import Login from '@/views/public/Login.vue';
import SignUp from '@/views/public/SignUp.vue'; import SignUp from '@/views/public/SignUp.vue';
import Dashboard from '@/views/private/Dashboard.vue'; const Dashboard = () => import('@/views/private/Dashboard.vue');
import AiInvestment from '@/views/private/AiInvestment.vue'; const AiInvestment = () => import('@/views/private/AiInvestment.vue');
import StockMarket from '@/views/private/StockMarket.vue'; const StockMarket = () => import('@/views/private/StockMarket.vue');
import EarningsPrediction from '@/views/private/EarningsPrediction.vue'; const EarningsPrediction = () => import('@/views/private/EarningsPrediction.vue');
import TradingNews from '@/views/private/TradingNews.vue'; const TradingNews = () => import('@/views/private/TradingNews.vue');
import History from '@/views/private/History.vue'; const History = () => import('@/views/private/History.vue');
import NotFoundView from '@/views/NotFoundView.vue'; import NotFoundView from '@/views/NotFoundView.vue';
import permission from '@/utils/permission';
import { useUserStore } from '@/store/userStore';
const routes = [ const routes = [
{ {
@ -88,7 +90,8 @@ const routes = [
requiresAuth: false requiresAuth: false
}, },
beforeEnter: (to, from, next) => { beforeEnter: (to, from, next) => {
if (localStorage.getItem('isLoggedIn') === 'true') { const userStore = useUserStore();
if (userStore.isLoggedIn) {
next('/app/dashboard'); next('/app/dashboard');
} else { } else {
next(); next();
@ -121,13 +124,12 @@ const router = createRouter({
}) })
// 全局前置守卫 // 全局前置守卫
router.beforeEach((to, from, next) => { router.beforeEach(async (to, from, next) => {
// 设置页面标题 // 设置页面标题
document.title = to.meta.title ? `${to.meta.title} - AriStockAI` : 'AriStockAI'; document.title = to.meta.title ? `${to.meta.title} - AriStockAI` : 'AriStockAI';
// 权限检查 - 暂时注释未定义的permission调用 // 权限检查
// permission.routeGuard(to, from, next); await permission.routeGuard(to, from, next);
next(); // 直接放行
}); });
// 全局后置钩子 // 全局后置钩子

View File

@ -68,29 +68,37 @@ export const permission = {
* @returns {Promise<void>} - Promise * @returns {Promise<void>} - Promise
*/ */
async routeGuard(to, from, next) { async routeGuard(to, from, next) {
// 检查是否需要登录 try {
if (to.meta?.requiresAuth && !jwt.hasValidToken()) { // 对不需要认证的路由直接同步放行
ElMessageBox.alert('请先登录', '权限不足', { if (!to.meta?.requiresAuth) {
confirmButtonText: '去登录', next();
type: 'warning' return;
}).then(() => { }
next({ path: '/login', query: { redirect: to.fullPath } });
});
return;
}
// 检查路由权限 // 检查是否需要登录
if (!permission.hasRoutePermission(to)) { if (!jwt.hasValidToken()) {
ElMessageBox.alert('您没有权限访问该页面', '权限不足', { await ElMessageBox.alert('请先登录', '权限不足', {
confirmButtonText: '确定', confirmButtonText: '去登录',
type: 'error' type: 'warning'
}).then(() => { });
next(from.fullPath || '/app'); return next({ path: '/login', query: { redirect: to.fullPath } });
}); }
return;
}
next(); // 检查路由权限
if (!permission.hasRoutePermission(to)) {
await ElMessageBox.alert('您没有权限访问该页面', '权限不足', {
confirmButtonText: '确定',
type: 'error'
});
return next(from.fullPath || '/app');
}
next();
} catch (error) {
console.error('路由守卫异常:', error);
// 发生异常时默认放行,避免白屏
next();
}
}, },
/** /**

View File

@ -1,12 +1,16 @@
<template> <template>
<div class="p-6"> <div class="ai-investment-container">
<h1 class="text-2xl font-bold text-white mb-6">AI投资</h1> <h1>AI Investment Analysis</h1>
<!-- AI投资内容将在后续开发中添加 --> <p>This page is under development.</p>
</div> </div>
</template> </template>
<script> <script setup>
export default { // AI Investment component logic will be implemented here
name: 'AiInvestment' </script>
<style scoped>
.ai-investment-container {
padding: 20px;
} }
</script> </style>

View File

@ -119,6 +119,7 @@
</template> </template>
<script> <script>
import { useUserStore } from '@/store/userStore';
export default { export default {
@ -160,12 +161,17 @@ export default {
return isValid; return isValid;
}, },
handleLogin() { async handleLogin() {
if (!this.validateForm()) return; if (!this.validateForm()) return;
// const userStore = useUserStore();
localStorage.setItem('isLoggedIn', 'true'); try {
this.showSuccessModal = true; await userStore.login({ email: this.email, password: this.password });
this.showSuccessModal = true;
} catch (error) {
console.error('登录失败:', error);
this.passwordError = error.message || '登录失败,请检查您的邮箱和密码';
}
}, },
handleGoogleLogin() { handleGoogleLogin() {