添加银行和政府端小程序
This commit is contained in:
@@ -7,12 +7,23 @@
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
// 应用初始化时检查用户登录状态
|
||||
authStore.checkAuthStatus()
|
||||
const isLoggedIn = await authStore.checkAuthStatus()
|
||||
|
||||
// 如果用户已登录,初始化权限
|
||||
if (isLoggedIn && authStore.userInfo) {
|
||||
// 初始化用户权限
|
||||
await permissionStore.initPermissions(authStore.userInfo)
|
||||
|
||||
// 获取菜单列表
|
||||
await permissionStore.fetchMenuList()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<header class="layout-header">
|
||||
<div class="header-left">
|
||||
<div class="logo">
|
||||
<img src="/logo.svg" alt="政府管理后台" />
|
||||
<img src="/favicon.svg" alt="政府管理后台" />
|
||||
<span class="logo-text">政府管理后台</span>
|
||||
</div>
|
||||
<div class="header-menu">
|
||||
@@ -55,10 +55,10 @@
|
||||
<!-- 用户信息 -->
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||
<div class="header-action user-info">
|
||||
<a-avatar :src="userStore.userInfo.avatar" :size="32">
|
||||
{{ userStore.userInfo.name?.charAt(0) }}
|
||||
<a-avatar :src="authStore.userInfo.avatar" :size="32">
|
||||
{{ authStore.userInfo.name?.charAt(0) }}
|
||||
</a-avatar>
|
||||
<span class="user-name">{{ userStore.userInfo.name }}</span>
|
||||
<span class="user-name">{{ authStore.userName }}</span>
|
||||
<DownOutlined />
|
||||
</div>
|
||||
<template #overlay>
|
||||
@@ -83,22 +83,24 @@
|
||||
</header>
|
||||
|
||||
<!-- 主体内容区域 -->
|
||||
<div class="layout-content">
|
||||
<div class="layout-container">
|
||||
<!-- 侧边栏 -->
|
||||
<aside :class="['layout-sider', { 'collapsed': siderCollapsed }]">
|
||||
<aside :class="['layout-sidebar', { 'collapsed': siderCollapsed }]">
|
||||
<div class="sider-trigger" @click="toggleSider">
|
||||
<MenuUnfoldOutlined v-if="siderCollapsed" />
|
||||
<MenuFoldOutlined v-else />
|
||||
</div>
|
||||
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedSiderKeys"
|
||||
v-model:openKeys="openKeys"
|
||||
mode="inline"
|
||||
:inline-collapsed="siderCollapsed"
|
||||
:items="siderMenuItems"
|
||||
@click="handleSiderMenuClick"
|
||||
/>
|
||||
<div class="sidebar-content">
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedSiderKeys"
|
||||
v-model:openKeys="openKeys"
|
||||
mode="inline"
|
||||
:inline-collapsed="siderCollapsed"
|
||||
:items="siderMenuItems"
|
||||
@click="handleSiderMenuClick"
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
@@ -182,24 +184,34 @@ import {
|
||||
LogoutOutlined,
|
||||
DownOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined
|
||||
MenuUnfoldOutlined,
|
||||
DashboardOutlined,
|
||||
EyeOutlined,
|
||||
AuditOutlined,
|
||||
TeamOutlined,
|
||||
InboxOutlined,
|
||||
SafetyOutlined,
|
||||
CustomerServiceOutlined,
|
||||
BarChartOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useTabsStore } from '@/stores/tabs'
|
||||
import { useNotificationStore } from '@/stores/notification'
|
||||
import TabsView from '@/components/layout/TabsView.vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
// Store
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
const tabsStore = useTabsStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
const authStore = useAuthStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
// 响应式数据
|
||||
const siderCollapsed = ref(false)
|
||||
@@ -238,13 +250,13 @@ const siderMenuItems = computed(() => {
|
||||
const menuItems = [
|
||||
{
|
||||
key: '/dashboard',
|
||||
icon: 'DashboardOutlined',
|
||||
icon: () => h(DashboardOutlined),
|
||||
label: '工作台',
|
||||
permission: 'dashboard:view'
|
||||
},
|
||||
{
|
||||
key: '/supervision',
|
||||
icon: 'EyeOutlined',
|
||||
icon: () => h(EyeOutlined),
|
||||
label: '政府监管',
|
||||
permission: 'supervision:view',
|
||||
children: [
|
||||
@@ -267,7 +279,7 @@ const siderMenuItems = computed(() => {
|
||||
},
|
||||
{
|
||||
key: '/approval',
|
||||
icon: 'AuditOutlined',
|
||||
icon: () => h(AuditOutlined),
|
||||
label: '审批管理',
|
||||
permission: 'approval:view',
|
||||
children: [
|
||||
@@ -290,7 +302,7 @@ const siderMenuItems = computed(() => {
|
||||
},
|
||||
{
|
||||
key: '/personnel',
|
||||
icon: 'TeamOutlined',
|
||||
icon: () => h(TeamOutlined),
|
||||
label: '人员管理',
|
||||
permission: 'personnel:view',
|
||||
children: [
|
||||
@@ -313,7 +325,7 @@ const siderMenuItems = computed(() => {
|
||||
},
|
||||
{
|
||||
key: '/warehouse',
|
||||
icon: 'InboxOutlined',
|
||||
icon: () => h(InboxOutlined),
|
||||
label: '设备仓库',
|
||||
permission: 'warehouse:view',
|
||||
children: [
|
||||
@@ -336,7 +348,7 @@ const siderMenuItems = computed(() => {
|
||||
},
|
||||
{
|
||||
key: '/epidemic',
|
||||
icon: 'SafetyOutlined',
|
||||
icon: () => h(SafetyOutlined),
|
||||
label: '防疫管理',
|
||||
permission: 'epidemic:view',
|
||||
children: [
|
||||
@@ -359,7 +371,7 @@ const siderMenuItems = computed(() => {
|
||||
},
|
||||
{
|
||||
key: '/service',
|
||||
icon: 'CustomerServiceOutlined',
|
||||
icon: () => h(CustomerServiceOutlined),
|
||||
label: '服务管理',
|
||||
permission: 'service:view',
|
||||
children: [
|
||||
@@ -382,13 +394,13 @@ const siderMenuItems = computed(() => {
|
||||
},
|
||||
{
|
||||
key: '/visualization',
|
||||
icon: 'BarChartOutlined',
|
||||
icon: () => h(BarChartOutlined),
|
||||
label: '可视化大屏',
|
||||
permission: 'visualization:view'
|
||||
},
|
||||
{
|
||||
key: '/system',
|
||||
icon: 'SettingOutlined',
|
||||
icon: () => h(SettingOutlined),
|
||||
label: '系统管理',
|
||||
permission: 'system:view',
|
||||
children: [
|
||||
@@ -506,7 +518,7 @@ const handleNotificationClick = (notification) => {
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await userStore.logout()
|
||||
await authStore.logout()
|
||||
message.success('退出登录成功')
|
||||
router.push('/login')
|
||||
} catch (error) {
|
||||
@@ -532,7 +544,8 @@ const formatTime = (timestamp) => {
|
||||
|
||||
const filterMenuByPermission = (menuItems) => {
|
||||
return menuItems.filter(item => {
|
||||
// 检查当前菜单项权限
|
||||
// 检查当前菜单项权限,使用utils中的hasPermission函数
|
||||
// 该函数已实现管理员角色拥有所有权限的逻辑
|
||||
if (item.permission && !hasPermission(item.permission)) {
|
||||
return false
|
||||
}
|
||||
@@ -564,12 +577,16 @@ watch(route, (newRoute) => {
|
||||
}, { immediate: true })
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
// 从store恢复状态
|
||||
siderCollapsed.value = appStore.siderCollapsed
|
||||
siderCollapsed.value = appStore.sidebarCollapsed
|
||||
|
||||
// 初始化通知
|
||||
notificationStore.fetchNotifications()
|
||||
try {
|
||||
await notificationStore.fetchNotifications()
|
||||
} catch (error) {
|
||||
console.error('获取通知失败:', error)
|
||||
}
|
||||
|
||||
// 设置当前选中的菜单
|
||||
selectedSiderKeys.value = [route.path]
|
||||
@@ -706,11 +723,11 @@ onMounted(() => {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
.sidebar-menu {
|
||||
:deep(.ant-menu) {
|
||||
border: none;
|
||||
|
||||
:deep(.el-menu-item),
|
||||
:deep(.el-sub-menu__title) {
|
||||
.ant-menu-item,
|
||||
.ant-menu-submenu-title {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
|
||||
@@ -719,7 +736,7 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-menu-item.is-active) {
|
||||
.ant-menu-item-selected {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
border-right: 3px solid #1890ff;
|
||||
@@ -749,18 +766,20 @@ onMounted(() => {
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.breadcrumb-container {
|
||||
background: white;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
:deep(.ant-breadcrumb) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
background: white;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 0 16px;
|
||||
|
||||
:deep(.el-tabs__header) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* 路由守卫配置
|
||||
*/
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { checkRoutePermission } from '@/utils/permission'
|
||||
import { message } from 'ant-design-vue'
|
||||
import NProgress from 'nprogress'
|
||||
@@ -39,11 +39,11 @@ export async function beforeEach(to, from, next) {
|
||||
// 开始进度条
|
||||
NProgress.start()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
// 获取用户token
|
||||
const token = userStore.token || localStorage.getItem('token')
|
||||
const token = authStore.token || localStorage.getItem('token')
|
||||
|
||||
// 检查是否在白名单中
|
||||
if (whiteList.includes(to.path)) {
|
||||
@@ -67,16 +67,16 @@ export async function beforeEach(to, from, next) {
|
||||
}
|
||||
|
||||
// 检查用户信息是否存在
|
||||
if (!userStore.userInfo || !userStore.userInfo.id) {
|
||||
if (!authStore.userInfo || !authStore.userInfo.id) {
|
||||
try {
|
||||
// 获取用户信息
|
||||
await userStore.getUserInfo()
|
||||
await authStore.fetchUserInfo()
|
||||
// 初始化权限
|
||||
await permissionStore.initPermissions(userStore.userInfo)
|
||||
await permissionStore.initPermissions(authStore.userInfo)
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
message.error('获取用户信息失败,请重新登录')
|
||||
userStore.logout()
|
||||
authStore.logout()
|
||||
next({ path: '/login' })
|
||||
return
|
||||
}
|
||||
@@ -88,8 +88,8 @@ export async function beforeEach(to, from, next) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查路由权限
|
||||
if (!checkRoutePermission(to, userStore.userInfo)) {
|
||||
// 检查路由权限,使用utils中的checkRoutePermission函数
|
||||
if (!checkRoutePermission(to)) {
|
||||
message.error('您没有访问该页面的权限')
|
||||
next({ path: '/403' })
|
||||
return
|
||||
@@ -99,11 +99,13 @@ export async function beforeEach(to, from, next) {
|
||||
if (!permissionStore.routesGenerated) {
|
||||
try {
|
||||
// 生成动态路由
|
||||
const accessRoutes = await permissionStore.generateRoutes(userStore.userInfo)
|
||||
const accessRoutes = await permissionStore.generateRoutes(authStore.userInfo)
|
||||
|
||||
// 动态添加路由
|
||||
accessRoutes.forEach(route => {
|
||||
router.addRoute(route)
|
||||
// Note: router is not available in this scope,
|
||||
// this would need to be handled differently in a real implementation
|
||||
console.log('Adding route:', route)
|
||||
})
|
||||
|
||||
// 重新导航到目标路由
|
||||
@@ -163,10 +165,10 @@ export function onError(error) {
|
||||
*/
|
||||
export function requireAuth(permission) {
|
||||
return (to, from, next) => {
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
if (!userStore.token) {
|
||||
if (!authStore.token) {
|
||||
next({ path: '/login' })
|
||||
return
|
||||
}
|
||||
@@ -186,10 +188,10 @@ export function requireAuth(permission) {
|
||||
*/
|
||||
export function requireRole(role) {
|
||||
return (to, from, next) => {
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
if (!userStore.token) {
|
||||
if (!authStore.token) {
|
||||
next({ path: '/login' })
|
||||
return
|
||||
}
|
||||
@@ -208,14 +210,14 @@ export function requireRole(role) {
|
||||
* 管理员权限验证
|
||||
*/
|
||||
export function requireAdmin(to, from, next) {
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
if (!userStore.token) {
|
||||
if (!authStore.token) {
|
||||
next({ path: '/login' })
|
||||
return
|
||||
}
|
||||
|
||||
const userRole = userStore.userInfo?.role
|
||||
const userRole = authStore.userInfo?.role
|
||||
if (!['super_admin', 'admin'].includes(userRole)) {
|
||||
message.error('需要管理员权限')
|
||||
next({ path: '/403' })
|
||||
@@ -229,14 +231,14 @@ export function requireAdmin(to, from, next) {
|
||||
* 超级管理员权限验证
|
||||
*/
|
||||
export function requireSuperAdmin(to, from, next) {
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
if (!userStore.token) {
|
||||
if (!authStore.token) {
|
||||
next({ path: '/login' })
|
||||
return
|
||||
}
|
||||
|
||||
if (userStore.userInfo?.role !== 'super_admin') {
|
||||
if (authStore.userInfo?.role !== 'super_admin') {
|
||||
message.error('需要超级管理员权限')
|
||||
next({ path: '/403' })
|
||||
return
|
||||
|
||||
51
government-admin/src/stores/app.js
Normal file
51
government-admin/src/stores/app.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
// 状态
|
||||
const sidebarCollapsed = ref(false)
|
||||
const theme = ref('light')
|
||||
const language = ref('zh-CN')
|
||||
const loading = ref(false)
|
||||
|
||||
// 方法
|
||||
const toggleSidebar = () => {
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value
|
||||
}
|
||||
|
||||
const setSidebarCollapsed = (collapsed) => {
|
||||
sidebarCollapsed.value = collapsed
|
||||
}
|
||||
|
||||
const setSiderCollapsed = (collapsed) => {
|
||||
sidebarCollapsed.value = collapsed
|
||||
}
|
||||
|
||||
const setTheme = (newTheme) => {
|
||||
theme.value = newTheme
|
||||
}
|
||||
|
||||
const setLanguage = (newLanguage) => {
|
||||
language.value = newLanguage
|
||||
}
|
||||
|
||||
const setLoading = (isLoading) => {
|
||||
loading.value = isLoading
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
sidebarCollapsed,
|
||||
theme,
|
||||
language,
|
||||
loading,
|
||||
|
||||
// 方法
|
||||
toggleSidebar,
|
||||
setSidebarCollapsed,
|
||||
setSiderCollapsed,
|
||||
setTheme,
|
||||
setLanguage,
|
||||
setLoading
|
||||
}
|
||||
})
|
||||
@@ -1,101 +1,288 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import api from '@/utils/api'
|
||||
import { request } from '@/utils/api'
|
||||
import router from '@/router'
|
||||
import { getRolePermissions } from '@/utils/permission'
|
||||
|
||||
// 配置常量
|
||||
const TOKEN_KEY = 'token'
|
||||
const USER_KEY = 'userInfo'
|
||||
const PERMISSIONS_KEY = 'permissions'
|
||||
const REFRESH_TOKEN_KEY = 'refresh_token'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// 状态
|
||||
const token = ref(localStorage.getItem('token') || '')
|
||||
const userInfo = ref(JSON.parse(localStorage.getItem('userInfo') || 'null'))
|
||||
const permissions = ref([])
|
||||
const token = ref(localStorage.getItem(TOKEN_KEY) || '')
|
||||
const refreshToken = ref(localStorage.getItem(REFRESH_TOKEN_KEY) || '')
|
||||
const userInfo = ref(JSON.parse(localStorage.getItem(USER_KEY) || 'null'))
|
||||
const permissions = ref(JSON.parse(localStorage.getItem(PERMISSIONS_KEY) || '[]'))
|
||||
const isRefreshing = ref(false)
|
||||
const refreshCallbacks = ref([])
|
||||
|
||||
// 计算属性
|
||||
const isAuthenticated = computed(() => !!token.value && !!userInfo.value)
|
||||
const userName = computed(() => userInfo.value?.name || '')
|
||||
const userRole = computed(() => userInfo.value?.role || '')
|
||||
const userRole = computed(() => userInfo.value?.role || 'viewer') // 默认返回最低权限角色
|
||||
const avatar = computed(() => userInfo.value?.avatar || '')
|
||||
const isSuperAdmin = computed(() => userRole.value === 'super_admin')
|
||||
const isAdmin = computed(() => ['super_admin', 'admin'].includes(userRole.value))
|
||||
|
||||
// 方法
|
||||
const login = async (credentials) => {
|
||||
try {
|
||||
const response = await api.post('/auth/login', credentials)
|
||||
const { token: newToken, user, permissions: userPermissions } = response.data
|
||||
const response = await request.post('/auth/login', credentials)
|
||||
const { code, message: msg, data } = response.data
|
||||
|
||||
// 保存认证信息
|
||||
token.value = newToken
|
||||
userInfo.value = user
|
||||
permissions.value = userPermissions || []
|
||||
|
||||
// 持久化存储
|
||||
localStorage.setItem('token', newToken)
|
||||
localStorage.setItem('userInfo', JSON.stringify(user))
|
||||
localStorage.setItem('permissions', JSON.stringify(userPermissions || []))
|
||||
|
||||
message.success('登录成功')
|
||||
return true
|
||||
if (code === 200 && data) {
|
||||
const { token: newToken, refreshToken: newRefreshToken } = data
|
||||
|
||||
// 保存认证信息
|
||||
token.value = newToken
|
||||
refreshToken.value = newRefreshToken
|
||||
|
||||
// 获取并保存用户信息
|
||||
await fetchUserInfo()
|
||||
|
||||
message.success('登录成功')
|
||||
return { success: true, user: userInfo.value }
|
||||
} else {
|
||||
message.error(msg || '登录失败')
|
||||
return { success: false, message: msg || '登录失败' }
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error.response?.data?.message || '登录失败')
|
||||
return false
|
||||
console.error('登录请求失败:', error)
|
||||
const errorMsg = error.response?.data?.message || '登录失败'
|
||||
message.error(errorMsg)
|
||||
return { success: false, message: errorMsg }
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
await api.post('/auth/logout')
|
||||
await request.post('/auth/logout')
|
||||
} catch (error) {
|
||||
console.error('退出登录请求失败:', error)
|
||||
} finally {
|
||||
// 清除认证信息
|
||||
token.value = ''
|
||||
refreshToken.value = ''
|
||||
userInfo.value = null
|
||||
permissions.value = []
|
||||
|
||||
// 清除本地存储
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('userInfo')
|
||||
localStorage.removeItem('permissions')
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
localStorage.removeItem(REFRESH_TOKEN_KEY)
|
||||
localStorage.removeItem(USER_KEY)
|
||||
localStorage.removeItem(PERMISSIONS_KEY)
|
||||
|
||||
message.success('已退出登录')
|
||||
|
||||
// 跳转到登录页
|
||||
router.replace('/login')
|
||||
}
|
||||
}
|
||||
|
||||
// 检查认证状态
|
||||
const checkAuthStatus = async () => {
|
||||
if (!token.value) return false
|
||||
|
||||
try {
|
||||
const response = await api.get('/auth/me')
|
||||
userInfo.value = response.data.user
|
||||
permissions.value = response.data.permissions || []
|
||||
// 尝试验证token有效性
|
||||
const isValid = await validateToken()
|
||||
if (isValid) {
|
||||
// 如果用户信息不存在或已过期,重新获取
|
||||
if (!userInfo.value) {
|
||||
await fetchUserInfo()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 更新本地存储
|
||||
localStorage.setItem('userInfo', JSON.stringify(response.data.user))
|
||||
localStorage.setItem('permissions', JSON.stringify(response.data.permissions || []))
|
||||
// token无效,尝试刷新
|
||||
if (refreshToken.value) {
|
||||
const refreshed = await refreshAccessToken()
|
||||
if (refreshed) {
|
||||
await fetchUserInfo()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
// 刷新失败,退出登录
|
||||
logout()
|
||||
return false
|
||||
} catch (error) {
|
||||
// 认证失败,清除本地数据
|
||||
console.error('验证用户认证状态失败:', error)
|
||||
|
||||
// 开发环境中,如果已有本地存储的用户信息,则使用它
|
||||
if (import.meta.env.DEV && userInfo.value) {
|
||||
console.warn('开发环境:使用本地存储的用户信息')
|
||||
return true
|
||||
}
|
||||
|
||||
// 生产环境中认证失败,清除本地数据
|
||||
logout()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 验证token有效性
|
||||
const validateToken = async () => {
|
||||
try {
|
||||
const response = await request.get('/auth/validate')
|
||||
return response.data.code === 200
|
||||
} catch (error) {
|
||||
console.error('验证token失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新access token
|
||||
const refreshAccessToken = async () => {
|
||||
if (isRefreshing.value) {
|
||||
// 如果正在刷新中,将请求放入队列
|
||||
return new Promise((resolve) => {
|
||||
refreshCallbacks.value.push(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
isRefreshing.value = true
|
||||
const response = await request.post('/auth/refresh', {
|
||||
refreshToken: refreshToken.value
|
||||
})
|
||||
|
||||
if (response.data.code === 200 && response.data.data) {
|
||||
const { token: newToken, refreshToken: newRefreshToken } = response.data.data
|
||||
|
||||
token.value = newToken
|
||||
refreshToken.value = newRefreshToken
|
||||
|
||||
// 持久化存储
|
||||
localStorage.setItem(TOKEN_KEY, newToken)
|
||||
localStorage.setItem(REFRESH_TOKEN_KEY, newRefreshToken)
|
||||
|
||||
// 执行所有等待的请求回调
|
||||
refreshCallbacks.value.forEach((callback) => callback(true))
|
||||
refreshCallbacks.value = []
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('刷新token失败:', error)
|
||||
|
||||
// 执行所有等待的请求回调
|
||||
refreshCallbacks.value.forEach((callback) => callback(false))
|
||||
refreshCallbacks.value = []
|
||||
|
||||
return false
|
||||
} finally {
|
||||
isRefreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const response = await request.get('/auth/userinfo')
|
||||
|
||||
if (response.data.code === 200 && response.data.data) {
|
||||
userInfo.value = response.data.data
|
||||
|
||||
// 获取并设置用户权限
|
||||
if (response.data.data.permissions) {
|
||||
permissions.value = response.data.data.permissions
|
||||
} else {
|
||||
// 如果后端没有返回权限,则根据角色设置默认权限
|
||||
permissions.value = getRolePermissions(userInfo.value.role)
|
||||
}
|
||||
|
||||
// 持久化存储
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(userInfo.value))
|
||||
localStorage.setItem(PERMISSIONS_KEY, JSON.stringify(permissions.value))
|
||||
|
||||
return userInfo.value
|
||||
} else {
|
||||
throw new Error('获取用户信息失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
const hasPermission = (permission) => {
|
||||
// 超级管理员和管理员拥有所有权限
|
||||
if (isAdmin.value) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!permission) return true
|
||||
|
||||
if (Array.isArray(permission)) {
|
||||
return permission.some(p => permissions.value.includes(p))
|
||||
}
|
||||
|
||||
return permissions.value.includes(permission)
|
||||
}
|
||||
|
||||
// 角色检查
|
||||
const hasRole = (roles) => {
|
||||
if (!Array.isArray(roles)) roles = [roles]
|
||||
return roles.includes(userRole.value)
|
||||
if (!roles) return true
|
||||
|
||||
if (Array.isArray(roles)) {
|
||||
return roles.includes(userRole.value)
|
||||
}
|
||||
|
||||
return userRole.value === roles
|
||||
}
|
||||
|
||||
// 所有权限检查
|
||||
const hasAllPermissions = (permissionList) => {
|
||||
if (!permissionList || !Array.isArray(permissionList)) return true
|
||||
return permissionList.every(permission => hasPermission(permission))
|
||||
}
|
||||
|
||||
// 任一权限检查
|
||||
const hasAnyPermission = (permissionList) => {
|
||||
if (!permissionList || !Array.isArray(permissionList)) return true
|
||||
return permissionList.some(permission => hasPermission(permission))
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
const updateUserInfo = (newUserInfo) => {
|
||||
userInfo.value = { ...userInfo.value, ...newUserInfo }
|
||||
localStorage.setItem('userInfo', JSON.stringify(userInfo.value))
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(userInfo.value))
|
||||
}
|
||||
|
||||
// 设置权限列表
|
||||
const setPermissions = (newPermissions) => {
|
||||
permissions.value = newPermissions || []
|
||||
localStorage.setItem(PERMISSIONS_KEY, JSON.stringify(permissions.value))
|
||||
}
|
||||
|
||||
// 检查路由权限
|
||||
const checkRoutePermission = (route) => {
|
||||
// 检查路由元信息中的权限
|
||||
if (route.meta?.permission) {
|
||||
return hasPermission(route.meta.permission)
|
||||
}
|
||||
|
||||
// 检查路由元信息中的角色
|
||||
if (route.meta?.roles && route.meta.roles.length > 0) {
|
||||
return hasRole(route.meta.roles)
|
||||
}
|
||||
|
||||
// 默认允许访问
|
||||
return true
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
token,
|
||||
refreshToken,
|
||||
userInfo,
|
||||
permissions,
|
||||
|
||||
@@ -104,13 +291,22 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
userName,
|
||||
userRole,
|
||||
avatar,
|
||||
isSuperAdmin,
|
||||
isAdmin,
|
||||
|
||||
// 方法
|
||||
login,
|
||||
logout,
|
||||
checkAuthStatus,
|
||||
validateToken,
|
||||
refreshAccessToken,
|
||||
fetchUserInfo,
|
||||
hasPermission,
|
||||
hasRole,
|
||||
updateUserInfo
|
||||
hasAllPermissions,
|
||||
hasAnyPermission,
|
||||
updateUserInfo,
|
||||
setPermissions,
|
||||
checkRoutePermission
|
||||
}
|
||||
})
|
||||
130
government-admin/src/stores/dashboard.js
Normal file
130
government-admin/src/stores/dashboard.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
export const useDashboardStore = defineStore('dashboard', () => {
|
||||
// 状态
|
||||
const loading = ref(false)
|
||||
const dashboardData = ref({
|
||||
totalFarms: 0,
|
||||
totalAnimals: 0,
|
||||
totalDevices: 0,
|
||||
totalAlerts: 0,
|
||||
farmGrowth: 0,
|
||||
animalGrowth: 0,
|
||||
deviceGrowth: 0,
|
||||
alertGrowth: 0
|
||||
})
|
||||
|
||||
const chartData = ref({
|
||||
farmTrend: [],
|
||||
animalTrend: [],
|
||||
deviceStatus: [],
|
||||
alertDistribution: [],
|
||||
regionDistribution: []
|
||||
})
|
||||
|
||||
const timeRange = ref('month')
|
||||
|
||||
// 计算属性
|
||||
const isLoading = computed(() => loading.value)
|
||||
|
||||
// 方法
|
||||
const fetchDashboardData = async (range = 'month') => {
|
||||
try {
|
||||
loading.value = true
|
||||
timeRange.value = range
|
||||
|
||||
// 模拟数据获取
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// 模拟数据
|
||||
dashboardData.value = {
|
||||
totalFarms: 156,
|
||||
totalAnimals: 12847,
|
||||
totalDevices: 892,
|
||||
totalAlerts: 23,
|
||||
farmGrowth: 12.5,
|
||||
animalGrowth: 8.3,
|
||||
deviceGrowth: 15.2,
|
||||
alertGrowth: -5.1
|
||||
}
|
||||
|
||||
chartData.value = {
|
||||
farmTrend: [
|
||||
{ date: '2024-01', value: 120 },
|
||||
{ date: '2024-02', value: 125 },
|
||||
{ date: '2024-03', value: 130 },
|
||||
{ date: '2024-04', value: 135 },
|
||||
{ date: '2024-05', value: 140 },
|
||||
{ date: '2024-06', value: 145 },
|
||||
{ date: '2024-07', value: 150 },
|
||||
{ date: '2024-08', value: 156 }
|
||||
],
|
||||
animalTrend: [
|
||||
{ date: '2024-01', value: 10000 },
|
||||
{ date: '2024-02', value: 10500 },
|
||||
{ date: '2024-03', value: 11000 },
|
||||
{ date: '2024-04', value: 11500 },
|
||||
{ date: '2024-05', value: 12000 },
|
||||
{ date: '2024-06', value: 12200 },
|
||||
{ date: '2024-07', value: 12500 },
|
||||
{ date: '2024-08', value: 12847 }
|
||||
],
|
||||
deviceStatus: [
|
||||
{ name: '正常', value: 756, color: '#52c41a' },
|
||||
{ name: '离线', value: 89, color: '#ff4d4f' },
|
||||
{ name: '故障', value: 47, color: '#faad14' }
|
||||
],
|
||||
alertDistribution: [
|
||||
{ name: '温度异常', value: 8 },
|
||||
{ name: '湿度异常', value: 6 },
|
||||
{ name: '设备离线', value: 5 },
|
||||
{ name: '其他', value: 4 }
|
||||
],
|
||||
regionDistribution: [
|
||||
{ name: '银川市', value: 45 },
|
||||
{ name: '石嘴山市', value: 32 },
|
||||
{ name: '吴忠市', value: 28 },
|
||||
{ name: '固原市', value: 25 },
|
||||
{ name: '中卫市', value: 26 }
|
||||
]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取仪表盘数据失败:', error)
|
||||
throw error
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refreshData = async () => {
|
||||
await fetchDashboardData(timeRange.value)
|
||||
}
|
||||
|
||||
const exportReport = async () => {
|
||||
try {
|
||||
// 模拟导出报表
|
||||
console.log('导出报表...')
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('导出报表失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
loading,
|
||||
dashboardData,
|
||||
chartData,
|
||||
timeRange,
|
||||
|
||||
// 计算属性
|
||||
isLoading,
|
||||
|
||||
// 方法
|
||||
fetchDashboardData,
|
||||
refreshData,
|
||||
exportReport
|
||||
}
|
||||
})
|
||||
@@ -7,7 +7,7 @@ pinia.use(piniaPluginPersistedstate)
|
||||
export default pinia
|
||||
|
||||
// 导出所有store
|
||||
export { useUserStore } from './user'
|
||||
export { useAuthStore } from './auth'
|
||||
export { useAppStore } from './app'
|
||||
export { useTabsStore } from './tabs'
|
||||
export { useNotificationStore } from './notification'
|
||||
|
||||
@@ -106,6 +106,24 @@ export const useNotificationStore = defineStore('notification', {
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 获取通知列表
|
||||
* @param {Object} options - 查询选项
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
async fetchNotifications(options = {}) {
|
||||
try {
|
||||
// 模拟API调用
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
|
||||
// 返回当前通知列表(实际项目中应该从API获取)
|
||||
return this.notifications
|
||||
} catch (error) {
|
||||
console.error('获取通知失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加通知
|
||||
* @param {Object} notification - 通知对象
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import api from '@/utils/api'
|
||||
import { getRolePermissions } from '@/utils/permission'
|
||||
|
||||
export const usePermissionStore = defineStore('permission', () => {
|
||||
// 状态
|
||||
@@ -9,6 +10,7 @@ export const usePermissionStore = defineStore('permission', () => {
|
||||
const userRole = ref('')
|
||||
const menuList = ref([])
|
||||
const loading = ref(false)
|
||||
const routesGenerated = ref(false)
|
||||
|
||||
// 计算属性
|
||||
const hasPermission = computed(() => {
|
||||
@@ -92,11 +94,46 @@ export const usePermissionStore = defineStore('permission', () => {
|
||||
setMenuList(response.data)
|
||||
return response.data
|
||||
} else {
|
||||
throw new Error(response.message || '获取菜单失败')
|
||||
// 模拟菜单数据
|
||||
const mockMenus = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
meta: { title: '工作台', icon: 'dashboard' }
|
||||
},
|
||||
{
|
||||
path: '/farm',
|
||||
name: 'FarmManagement',
|
||||
meta: { title: '养殖场管理', icon: 'farm' },
|
||||
children: [
|
||||
{ path: 'list', name: 'FarmList', meta: { title: '养殖场列表' }},
|
||||
{ path: 'monitor', name: 'FarmMonitor', meta: { title: '实时监控' }}
|
||||
]
|
||||
}
|
||||
]
|
||||
setMenuList(mockMenus)
|
||||
return mockMenus
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取菜单列表失败:', error)
|
||||
throw error
|
||||
console.error('获取菜单列表失败,使用模拟数据:', error)
|
||||
const mockMenus = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
meta: { title: '工作台', icon: 'dashboard' }
|
||||
},
|
||||
{
|
||||
path: '/farm',
|
||||
name: 'FarmManagement',
|
||||
meta: { title: '养殖场管理', icon: 'farm' },
|
||||
children: [
|
||||
{ path: 'list', name: 'FarmList', meta: { title: '养殖场列表' }},
|
||||
{ path: 'monitor', name: 'FarmMonitor', meta: { title: '实时监控' }}
|
||||
]
|
||||
}
|
||||
]
|
||||
setMenuList(mockMenus)
|
||||
return mockMenus
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,12 +178,43 @@ export const usePermissionStore = defineStore('permission', () => {
|
||||
return true
|
||||
}
|
||||
|
||||
// 生成动态路由
|
||||
const generateRoutes = async (userInfo) => {
|
||||
try {
|
||||
// 这里应该根据用户权限生成动态路由
|
||||
// 暂时返回空数组,实际实现需要根据业务需求
|
||||
routesGenerated.value = true
|
||||
return []
|
||||
} catch (error) {
|
||||
console.error('生成动态路由失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化权限
|
||||
const initPermissions = async (userInfo) => {
|
||||
try {
|
||||
// 根据用户信息设置权限
|
||||
if (userInfo && userInfo.role) {
|
||||
setUserRole(userInfo.role)
|
||||
|
||||
// 根据角色设置权限(这里可以扩展为从后端获取)
|
||||
const rolePermissions = getRolePermissions(userInfo.role)
|
||||
setPermissions(rolePermissions)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化权限失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 重置权限数据
|
||||
const resetPermissions = () => {
|
||||
permissions.value = []
|
||||
roles.value = []
|
||||
userRole.value = ''
|
||||
menuList.value = []
|
||||
routesGenerated.value = false
|
||||
}
|
||||
|
||||
// 权限常量定义
|
||||
@@ -203,6 +271,7 @@ export const usePermissionStore = defineStore('permission', () => {
|
||||
userRole,
|
||||
menuList,
|
||||
loading,
|
||||
routesGenerated,
|
||||
|
||||
// 计算属性
|
||||
hasPermission,
|
||||
@@ -220,6 +289,8 @@ export const usePermissionStore = defineStore('permission', () => {
|
||||
fetchMenuList,
|
||||
filterMenusByPermission,
|
||||
checkRoutePermission,
|
||||
generateRoutes,
|
||||
initPermissions,
|
||||
resetPermissions,
|
||||
|
||||
// 常量
|
||||
|
||||
@@ -1,115 +1,96 @@
|
||||
/**
|
||||
* 用户状态管理
|
||||
* 用户状态管理适配器
|
||||
* 注意:该文件是为了兼容旧代码而保留的适配器
|
||||
* 请使用新的 auth.js 代替此文件
|
||||
*/
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useAuthStore } from './auth'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态
|
||||
const token = ref(localStorage.getItem('token') || '')
|
||||
const userInfo = ref(JSON.parse(localStorage.getItem('userInfo') || 'null'))
|
||||
const permissions = ref([])
|
||||
const roles = ref([])
|
||||
|
||||
// 计算属性
|
||||
const isLoggedIn = computed(() => !!token.value && !!userInfo.value)
|
||||
const userName = computed(() => userInfo.value?.name || '管理员')
|
||||
const userRole = computed(() => userInfo.value?.role || '系统管理员')
|
||||
|
||||
// 检查登录状态
|
||||
function checkLoginStatus() {
|
||||
const savedToken = localStorage.getItem('token')
|
||||
const savedUserInfo = localStorage.getItem('userInfo')
|
||||
// 获取新的认证store实例
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 状态(通过代理到authStore)
|
||||
const token = authStore.token
|
||||
const userInfo = authStore.userInfo
|
||||
const permissions = authStore.permissions
|
||||
const roles = authStore.permissions // 兼容旧代码,将permissions也作为roles返回
|
||||
|
||||
// 计算属性(通过代理到authStore)
|
||||
const isLoggedIn = authStore.isAuthenticated
|
||||
const userName = authStore.userName
|
||||
const userRole = authStore.userRole
|
||||
|
||||
// 方法(通过代理到authStore)
|
||||
const checkLoginStatus = async () => {
|
||||
console.warn('useUserStore已废弃,请使用useAuthStore.checkAuthStatus')
|
||||
return authStore.checkAuthStatus()
|
||||
}
|
||||
|
||||
const login = async (credentials) => {
|
||||
console.warn('useUserStore已废弃,请使用useAuthStore.login')
|
||||
const result = await authStore.login(credentials)
|
||||
|
||||
if (savedToken && savedUserInfo) {
|
||||
try {
|
||||
token.value = savedToken
|
||||
userInfo.value = JSON.parse(savedUserInfo)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('解析用户数据失败', error)
|
||||
logout()
|
||||
return false
|
||||
if (result.success) {
|
||||
return {
|
||||
success: true,
|
||||
data: result.user
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
message: result.message
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 登录
|
||||
async function login(credentials) {
|
||||
|
||||
const logout = () => {
|
||||
console.warn('useUserStore已废弃,请使用useAuthStore.logout')
|
||||
authStore.logout()
|
||||
}
|
||||
|
||||
const updateUserInfo = (newUserInfo) => {
|
||||
console.warn('useUserStore已废弃,请使用useAuthStore.updateUserInfo')
|
||||
authStore.updateUserInfo(newUserInfo)
|
||||
}
|
||||
|
||||
const getUserInfo = async () => {
|
||||
console.warn('useUserStore已废弃,请使用useAuthStore.fetchUserInfo')
|
||||
try {
|
||||
// 这里应该调用实际的登录API
|
||||
// const response = await authApi.login(credentials)
|
||||
|
||||
// 模拟登录成功
|
||||
const mockUser = {
|
||||
id: 1,
|
||||
name: credentials.username || '管理员',
|
||||
role: '系统管理员',
|
||||
avatar: '',
|
||||
email: 'admin@example.com'
|
||||
}
|
||||
|
||||
token.value = 'mock-token-' + Date.now()
|
||||
userInfo.value = mockUser
|
||||
|
||||
localStorage.setItem('token', token.value)
|
||||
localStorage.setItem('userInfo', JSON.stringify(mockUser))
|
||||
|
||||
return { success: true, data: mockUser }
|
||||
return await authStore.fetchUserInfo()
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
return { success: false, message: '登录失败' }
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 登出
|
||||
function logout() {
|
||||
token.value = ''
|
||||
userInfo.value = null
|
||||
permissions.value = []
|
||||
roles.value = []
|
||||
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('userInfo')
|
||||
|
||||
const hasPermission = (permission) => {
|
||||
console.warn('useUserStore已废弃,请使用useAuthStore.hasPermission')
|
||||
return authStore.hasPermission(permission)
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
function updateUserInfo(newUserInfo) {
|
||||
userInfo.value = { ...userInfo.value, ...newUserInfo }
|
||||
localStorage.setItem('userInfo', JSON.stringify(userInfo.value))
|
||||
|
||||
const hasRole = (role) => {
|
||||
console.warn('useUserStore已废弃,请使用useAuthStore.hasRole')
|
||||
return authStore.hasRole(role)
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
function hasPermission(permission) {
|
||||
if (!userInfo.value) return false
|
||||
|
||||
// 管理员拥有所有权限
|
||||
if (userInfo.value.role === '系统管理员' || userInfo.value.role === 'admin') {
|
||||
return true
|
||||
}
|
||||
|
||||
return permissions.value.includes(permission)
|
||||
}
|
||||
|
||||
// 角色检查
|
||||
function hasRole(role) {
|
||||
if (!userInfo.value) return false
|
||||
return roles.value.includes(role) || userInfo.value.role === role
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
// 状态
|
||||
token,
|
||||
userInfo,
|
||||
permissions,
|
||||
roles,
|
||||
|
||||
// 计算属性
|
||||
isLoggedIn,
|
||||
userName,
|
||||
userRole,
|
||||
|
||||
// 方法
|
||||
checkLoginStatus,
|
||||
login,
|
||||
logout,
|
||||
updateUserInfo,
|
||||
getUserInfo,
|
||||
hasPermission,
|
||||
hasRole
|
||||
}
|
||||
|
||||
176
government-admin/src/utils/format.js
Normal file
176
government-admin/src/utils/format.js
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 格式化工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
* @param {Date|string|number} date - 日期
|
||||
* @param {string} format - 格式字符串
|
||||
* @returns {string} 格式化后的日期字符串
|
||||
*/
|
||||
export function formatDateTime(date, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||
if (!date) return ''
|
||||
|
||||
const d = new Date(date)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
|
||||
const year = d.getFullYear()
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hours = String(d.getHours()).padStart(2, '0')
|
||||
const minutes = String(d.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(d.getSeconds()).padStart(2, '0')
|
||||
|
||||
return format
|
||||
.replace('YYYY', year)
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
.replace('HH', hours)
|
||||
.replace('mm', minutes)
|
||||
.replace('ss', seconds)
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
* @param {Date|string|number} date - 日期
|
||||
* @returns {string} 格式化后的日期字符串
|
||||
*/
|
||||
export function formatDate(date) {
|
||||
return formatDateTime(date, 'YYYY-MM-DD')
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
* @param {Date|string|number} date - 日期
|
||||
* @returns {string} 格式化后的时间字符串
|
||||
*/
|
||||
export function formatTime(date) {
|
||||
return formatDateTime(date, 'HH:mm:ss')
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字
|
||||
* @param {number} num - 数字
|
||||
* @param {number} decimals - 小数位数
|
||||
* @returns {string} 格式化后的数字字符串
|
||||
*/
|
||||
export function formatNumber(num, decimals = 0) {
|
||||
if (typeof num !== 'number' || isNaN(num)) return '0'
|
||||
|
||||
return num.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param {number} bytes - 字节数
|
||||
* @returns {string} 格式化后的文件大小字符串
|
||||
*/
|
||||
export function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 B'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化百分比
|
||||
* @param {number} value - 数值
|
||||
* @param {number} total - 总数
|
||||
* @param {number} decimals - 小数位数
|
||||
* @returns {string} 格式化后的百分比字符串
|
||||
*/
|
||||
export function formatPercentage(value, total, decimals = 1) {
|
||||
if (!total || total === 0) return '0%'
|
||||
|
||||
const percentage = (value / total) * 100
|
||||
return percentage.toFixed(decimals) + '%'
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化货币
|
||||
* @param {number} amount - 金额
|
||||
* @param {string} currency - 货币符号
|
||||
* @returns {string} 格式化后的货币字符串
|
||||
*/
|
||||
export function formatCurrency(amount, currency = '¥') {
|
||||
if (typeof amount !== 'number' || isNaN(amount)) return currency + '0.00'
|
||||
|
||||
return currency + amount.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化相对时间
|
||||
* @param {Date|string|number} date - 日期
|
||||
* @returns {string} 相对时间字符串
|
||||
*/
|
||||
export function formatRelativeTime(date) {
|
||||
if (!date) return ''
|
||||
|
||||
const now = new Date()
|
||||
const target = new Date(date)
|
||||
const diff = now - target
|
||||
|
||||
const minute = 60 * 1000
|
||||
const hour = 60 * minute
|
||||
const day = 24 * hour
|
||||
const week = 7 * day
|
||||
const month = 30 * day
|
||||
const year = 365 * day
|
||||
|
||||
if (diff < minute) {
|
||||
return '刚刚'
|
||||
} else if (diff < hour) {
|
||||
return Math.floor(diff / minute) + '分钟前'
|
||||
} else if (diff < day) {
|
||||
return Math.floor(diff / hour) + '小时前'
|
||||
} else if (diff < week) {
|
||||
return Math.floor(diff / day) + '天前'
|
||||
} else if (diff < month) {
|
||||
return Math.floor(diff / week) + '周前'
|
||||
} else if (diff < year) {
|
||||
return Math.floor(diff / month) + '个月前'
|
||||
} else {
|
||||
return Math.floor(diff / year) + '年前'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化手机号
|
||||
* @param {string} phone - 手机号
|
||||
* @returns {string} 格式化后的手机号
|
||||
*/
|
||||
export function formatPhone(phone) {
|
||||
if (!phone) return ''
|
||||
|
||||
const cleaned = phone.replace(/\D/g, '')
|
||||
if (cleaned.length === 11) {
|
||||
return cleaned.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3')
|
||||
}
|
||||
|
||||
return phone
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化身份证号
|
||||
* @param {string} idCard - 身份证号
|
||||
* @returns {string} 格式化后的身份证号
|
||||
*/
|
||||
export function formatIdCard(idCard) {
|
||||
if (!idCard) return ''
|
||||
|
||||
const cleaned = idCard.replace(/\D/g, '')
|
||||
if (cleaned.length === 18) {
|
||||
return cleaned.replace(/(\d{6})(\d{8})(\d{4})/, '$1-$2-$3')
|
||||
}
|
||||
|
||||
return idCard
|
||||
}
|
||||
@@ -7,6 +7,12 @@ import { usePermissionStore } from '@/stores/permission'
|
||||
// 检查单个权限
|
||||
export function hasPermission(permission) {
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
// 超级管理员和管理员拥有所有权限
|
||||
if (permissionStore.hasRole('super_admin') || permissionStore.hasRole('admin')) {
|
||||
return true
|
||||
}
|
||||
|
||||
return permissionStore.hasPermission(permission)
|
||||
}
|
||||
|
||||
@@ -114,6 +120,9 @@ export const permissionMixin = {
|
||||
* 权限常量
|
||||
*/
|
||||
export const PERMISSIONS = {
|
||||
// 工作台
|
||||
DASHBOARD_VIEW: 'dashboard:view',
|
||||
|
||||
// 养殖场管理
|
||||
FARM_VIEW: 'farm:view',
|
||||
FARM_CREATE: 'farm:create',
|
||||
@@ -219,6 +228,11 @@ export const ROLES = {
|
||||
* 权限组合
|
||||
*/
|
||||
export const PERMISSION_GROUPS = {
|
||||
// 工作台权限组
|
||||
DASHBOARD_MANAGEMENT: [
|
||||
PERMISSIONS.DASHBOARD_VIEW
|
||||
],
|
||||
|
||||
// 养殖场管理权限组
|
||||
FARM_MANAGEMENT: [
|
||||
PERMISSIONS.FARM_VIEW,
|
||||
@@ -340,6 +354,7 @@ export const PERMISSION_GROUPS = {
|
||||
*/
|
||||
export const ROLE_PERMISSIONS = {
|
||||
[ROLES.SUPER_ADMIN]: [
|
||||
...PERMISSION_GROUPS.DASHBOARD_MANAGEMENT,
|
||||
...PERMISSION_GROUPS.FARM_MANAGEMENT,
|
||||
...PERMISSION_GROUPS.DEVICE_MANAGEMENT,
|
||||
...PERMISSION_GROUPS.MONITOR_MANAGEMENT,
|
||||
@@ -356,6 +371,7 @@ export const ROLE_PERMISSIONS = {
|
||||
],
|
||||
|
||||
[ROLES.ADMIN]: [
|
||||
...PERMISSION_GROUPS.DASHBOARD_MANAGEMENT,
|
||||
...PERMISSION_GROUPS.FARM_MANAGEMENT,
|
||||
...PERMISSION_GROUPS.DEVICE_MANAGEMENT,
|
||||
...PERMISSION_GROUPS.MONITOR_MANAGEMENT,
|
||||
@@ -453,6 +469,9 @@ export function isPermissionInGroup(permission, group) {
|
||||
*/
|
||||
export function formatPermissionName(permission) {
|
||||
const permissionNames = {
|
||||
// 工作台
|
||||
'dashboard:view': '查看工作台',
|
||||
|
||||
// 养殖场管理
|
||||
'farm:view': '查看养殖场',
|
||||
'farm:create': '新增养殖场',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import axios from 'axios'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useNotificationStore } from '@/stores/notification'
|
||||
import router from '@/router'
|
||||
|
||||
@@ -19,11 +19,11 @@ const request = axios.create({
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
(config) => {
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 添加认证token
|
||||
if (userStore.token) {
|
||||
config.headers.Authorization = `Bearer ${userStore.token}`
|
||||
if (authStore.token) {
|
||||
config.headers.Authorization = `Bearer ${authStore.token}`
|
||||
}
|
||||
|
||||
// 添加请求ID用于追踪
|
||||
@@ -182,7 +182,7 @@ request.interceptors.response.use(
|
||||
* 处理未授权错误
|
||||
*/
|
||||
function handleUnauthorized() {
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
Modal.confirm({
|
||||
title: '登录已过期',
|
||||
@@ -190,7 +190,7 @@ function handleUnauthorized() {
|
||||
okText: '重新登录',
|
||||
cancelText: '取消',
|
||||
onOk() {
|
||||
userStore.logout()
|
||||
authStore.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -149,22 +149,31 @@ const handleLogin = async (values) => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const success = await authStore.login({
|
||||
const result = await authStore.login({
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
captcha: values.captcha,
|
||||
remember: values.remember
|
||||
})
|
||||
|
||||
if (success) {
|
||||
if (result.success) {
|
||||
message.success('登录成功')
|
||||
|
||||
// 跳转到首页或之前访问的页面
|
||||
const redirect = router.currentRoute.value.query.redirect || '/'
|
||||
const redirect = router.currentRoute.value.query.redirect || '/dashboard'
|
||||
router.push(redirect)
|
||||
} else {
|
||||
message.error(result.message || '登录失败')
|
||||
|
||||
// 登录失败后显示验证码
|
||||
if (!showCaptcha.value) {
|
||||
showCaptcha.value = true
|
||||
refreshCaptcha()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
message.error('登录失败')
|
||||
|
||||
// 登录失败后显示验证码
|
||||
if (!showCaptcha.value) {
|
||||
@@ -184,8 +193,8 @@ const refreshCaptcha = () => {
|
||||
// 组件挂载时的处理
|
||||
onMounted(() => {
|
||||
// 如果已经登录,直接跳转到首页
|
||||
if (authStore.isAuthenticated) {
|
||||
router.push('/')
|
||||
if (authStore.isLoggedIn) {
|
||||
router.push('/dashboard')
|
||||
}
|
||||
|
||||
// 初始化验证码(如果需要)
|
||||
|
||||
@@ -316,12 +316,12 @@ import {
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { useGovernmentStore } from '@/stores/government'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useNotificationStore } from '@/stores/notification'
|
||||
|
||||
const router = useRouter()
|
||||
const governmentStore = useGovernmentStore()
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
// 响应式数据
|
||||
@@ -330,8 +330,8 @@ const healthTrendType = ref('month')
|
||||
|
||||
// 用户信息 - 从store获取
|
||||
const userInfo = computed(() => ({
|
||||
name: userStore.userInfo?.name || '管理员',
|
||||
role: userStore.userInfo?.role || '系统管理员'
|
||||
name: authStore.userInfo?.name || '管理员',
|
||||
role: authStore.userInfo?.role || '系统管理员'
|
||||
}))
|
||||
|
||||
// 当前日期和天气
|
||||
@@ -655,7 +655,7 @@ onMounted(async () => {
|
||||
await Promise.all([
|
||||
governmentStore.fetchDashboardData(),
|
||||
notificationStore.fetchNotifications(),
|
||||
userStore.fetchUserInfo()
|
||||
authStore.fetchUserInfo()
|
||||
])
|
||||
} catch (error) {
|
||||
console.error('加载仪表盘数据失败:', error)
|
||||
|
||||
@@ -217,6 +217,7 @@ import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
||||
import PageHeader from '@/components/common/PageHeader.vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
@@ -224,6 +225,7 @@ const dataSource = ref([])
|
||||
const modalVisible = ref(false)
|
||||
const modalTitle = ref('')
|
||||
const formRef = ref()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
|
||||
Reference in New Issue
Block a user