refactor(frontend): 重构前端导航菜单和权限系统
- 将导航菜单配置集中到常量文件便于统一管理 - 实现基于Pinia的状态管理替换本地存储 - 优化路由守卫逻辑增加错误处理 - 使用动态导入实现路由懒加载 - 统一侧边栏和主布局的菜单渲染逻辑
This commit is contained in:
parent
deaba87362
commit
fce2cf47f4
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<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="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">
|
||||
@ -17,7 +17,7 @@
|
||||
</nav>
|
||||
<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">
|
||||
<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>
|
||||
@ -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'">
|
||||
<div class="container mx-auto px-4 py-12">
|
||||
<div class="text-center" :class="theme === 'dark' ? 'text-gray-500' : 'text-gray-600'">
|
||||
© 2023 AriStockAI. 保留所有权利。
|
||||
© {{ new Date().getFullYear() }} AriStockAI. 保留所有权利。
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App',
|
||||
data() {
|
||||
return {
|
||||
theme: localStorage.getItem('theme') || 'dark'
|
||||
}
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
|
||||
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() {
|
||||
// 应用保存的主题
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
* 仅包含接口请求定义,不包含业务逻辑
|
||||
*/
|
||||
import { api } from './index';
|
||||
import jwt from '@/utils/jwt';
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
@ -10,7 +11,26 @@ import { api } from './index';
|
||||
* @returns {Promise<Object>} - 响应数据
|
||||
*/
|
||||
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>} - 响应数据
|
||||
*/
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -9,52 +9,12 @@
|
||||
|
||||
<nav>
|
||||
<ul class="space-y-1">
|
||||
<li>
|
||||
<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">
|
||||
<li v-for="item in menuItems" :key="item.name">
|
||||
<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">
|
||||
<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>
|
||||
<span>首页</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>
|
||||
<span>{{ item.label }}</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
@ -81,8 +41,22 @@
|
||||
|
||||
<script>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { menuItems } from '@/constants/menuItems';
|
||||
export default {
|
||||
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: {
|
||||
handleLogout() {
|
||||
localStorage.removeItem('isLoggedIn');
|
||||
|
||||
42
services/frontend/src/constants/menuItems.js
Normal file
42
services/frontend/src/constants/menuItems.js
Normal 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'
|
||||
}
|
||||
];
|
||||
@ -13,46 +13,16 @@
|
||||
</div>
|
||||
<nav class="p-4 space-y-1">
|
||||
<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="$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> 仪表盘
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'AiInvestment' }"
|
||||
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> 历史记录
|
||||
<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="iconPaths[item.icon]"></path>
|
||||
</svg>
|
||||
{{ item.label }}
|
||||
</router-link>
|
||||
</nav>
|
||||
<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">
|
||||
<router-view />
|
||||
</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>© {{ new Date().getFullYear() }} AriStockAI. 保留所有权利。</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -106,11 +70,22 @@ import { logout } from '@/api/auth';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import jwt from '@/utils/jwt';
|
||||
import { useUserStore } from '@/store/userStore';
|
||||
import { menuItems } from '@/constants/menuItems';
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
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"
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理用户登出
|
||||
*/
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
import './styles.css'
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia';
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { setupPlugins } from './plugins';
|
||||
|
||||
const app = createApp(App).use(router)
|
||||
setupPlugins(app);
|
||||
try {
|
||||
setupPlugins(app);
|
||||
} catch (error) {
|
||||
console.error('Failed to setup plugins:', error);
|
||||
}
|
||||
// 添加全局错误处理
|
||||
// 安全序列化错误对象的辅助函数,处理循环引用
|
||||
const safeStringify = (obj) => {
|
||||
@ -53,4 +58,10 @@ setupPlugins(app);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
app.mount('#app')
|
||||
router.isReady().then(() => {
|
||||
app.mount('#app');
|
||||
}).catch(error => {
|
||||
console.error('Router initialization failed:', error);
|
||||
// 即使路由初始化失败也尝试挂载应用
|
||||
app.mount('#app');
|
||||
});
|
||||
@ -3,13 +3,15 @@ import MainLayout from '@/layouts/MainLayout.vue';
|
||||
import Index from '@/views/public/Index.vue';
|
||||
import Login from '@/views/public/Login.vue';
|
||||
import SignUp from '@/views/public/SignUp.vue';
|
||||
import Dashboard from '@/views/private/Dashboard.vue';
|
||||
import AiInvestment from '@/views/private/AiInvestment.vue';
|
||||
import StockMarket from '@/views/private/StockMarket.vue';
|
||||
import EarningsPrediction from '@/views/private/EarningsPrediction.vue';
|
||||
import TradingNews from '@/views/private/TradingNews.vue';
|
||||
import History from '@/views/private/History.vue';
|
||||
const Dashboard = () => import('@/views/private/Dashboard.vue');
|
||||
const AiInvestment = () => import('@/views/private/AiInvestment.vue');
|
||||
const StockMarket = () => import('@/views/private/StockMarket.vue');
|
||||
const EarningsPrediction = () => import('@/views/private/EarningsPrediction.vue');
|
||||
const TradingNews = () => import('@/views/private/TradingNews.vue');
|
||||
const History = () => import('@/views/private/History.vue');
|
||||
import NotFoundView from '@/views/NotFoundView.vue';
|
||||
import permission from '@/utils/permission';
|
||||
import { useUserStore } from '@/store/userStore';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@ -88,7 +90,8 @@ const routes = [
|
||||
requiresAuth: false
|
||||
},
|
||||
beforeEnter: (to, from, next) => {
|
||||
if (localStorage.getItem('isLoggedIn') === 'true') {
|
||||
const userStore = useUserStore();
|
||||
if (userStore.isLoggedIn) {
|
||||
next('/app/dashboard');
|
||||
} else {
|
||||
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';
|
||||
|
||||
// 权限检查 - 暂时注释未定义的permission调用
|
||||
// permission.routeGuard(to, from, next);
|
||||
next(); // 直接放行
|
||||
// 权限检查
|
||||
await permission.routeGuard(to, from, next);
|
||||
});
|
||||
|
||||
// 全局后置钩子
|
||||
|
||||
@ -68,29 +68,37 @@ export const permission = {
|
||||
* @returns {Promise<void>} - Promise
|
||||
*/
|
||||
async routeGuard(to, from, next) {
|
||||
// 检查是否需要登录
|
||||
if (to.meta?.requiresAuth && !jwt.hasValidToken()) {
|
||||
ElMessageBox.alert('请先登录', '权限不足', {
|
||||
confirmButtonText: '去登录',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
next({ path: '/login', query: { redirect: to.fullPath } });
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 对不需要认证的路由直接同步放行
|
||||
if (!to.meta?.requiresAuth) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查路由权限
|
||||
if (!permission.hasRoutePermission(to)) {
|
||||
ElMessageBox.alert('您没有权限访问该页面', '权限不足', {
|
||||
confirmButtonText: '确定',
|
||||
type: 'error'
|
||||
}).then(() => {
|
||||
next(from.fullPath || '/app');
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 检查是否需要登录
|
||||
if (!jwt.hasValidToken()) {
|
||||
await ElMessageBox.alert('请先登录', '权限不足', {
|
||||
confirmButtonText: '去登录',
|
||||
type: 'warning'
|
||||
});
|
||||
return next({ path: '/login', query: { redirect: to.fullPath } });
|
||||
}
|
||||
|
||||
next();
|
||||
// 检查路由权限
|
||||
if (!permission.hasRoutePermission(to)) {
|
||||
await ElMessageBox.alert('您没有权限访问该页面', '权限不足', {
|
||||
confirmButtonText: '确定',
|
||||
type: 'error'
|
||||
});
|
||||
return next(from.fullPath || '/app');
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('路由守卫异常:', error);
|
||||
// 发生异常时默认放行,避免白屏
|
||||
next();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
<template>
|
||||
<div class="p-6">
|
||||
<h1 class="text-2xl font-bold text-white mb-6">AI投资</h1>
|
||||
<!-- AI投资内容将在后续开发中添加 -->
|
||||
<div class="ai-investment-container">
|
||||
<h1>AI Investment Analysis</h1>
|
||||
<p>This page is under development.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiInvestment'
|
||||
<script setup>
|
||||
// AI Investment component logic will be implemented here
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-investment-container {
|
||||
padding: 20px;
|
||||
}
|
||||
</script>
|
||||
</style>
|
||||
@ -119,6 +119,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useUserStore } from '@/store/userStore';
|
||||
|
||||
export default {
|
||||
|
||||
@ -160,12 +161,17 @@ export default {
|
||||
return isValid;
|
||||
},
|
||||
|
||||
handleLogin() {
|
||||
async handleLogin() {
|
||||
if (!this.validateForm()) return;
|
||||
|
||||
// 模拟登录逻辑
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
this.showSuccessModal = true;
|
||||
const userStore = useUserStore();
|
||||
try {
|
||||
await userStore.login({ email: this.email, password: this.password });
|
||||
this.showSuccessModal = true;
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error);
|
||||
this.passwordError = error.message || '登录失败,请检查您的邮箱和密码';
|
||||
}
|
||||
},
|
||||
|
||||
handleGoogleLogin() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user