权限管理Vue实现
1.网站开发中为什么需要权限控制
保护敏感信息
:确保只有授权用户才能访问特定数据。
增强安全性
:防御未经授权的访问和其他安全威胁。
满足合规要求
:遵守相关法律法规,如处理个人或财务信息的规定。
提升用户体验
:为不同角色定制化访问和服务,简化界面。
支持多角色操作
:允许基于用户角色分配不同的访问级别和功能。
防止误操作
:限制关键操作仅限于指定人员执行,减少错误风险。
审计追踪
:记录用户活动以便审查和问题排查。
维护数据一致性
:防止未经授权的数据修改,保持数据准确性和完整性。
2.下载Pinia
npm install pina
3.配置Pinia
import { createPinia } from 'pinia'
const pinia = createPinia() app.use(pinia)
4.新建store文件夹,创建user文件配置权限列表
// src/stores/user.js import {defineStore} from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ role: 'guest', // 默认角色为访客 menus: [ {name: '主页', path: '/', permission: ['guest', 'user', 'admin']}, {name: '用户面板', path: '/dashboard', permission: ['user', 'admin']}, {name: '管理员面板', path: '/admin', permission: ['admin']}, {name: ' 产品管理', path: '/product', permission: ['admin']} ] }), getters: { accessibleMenus: (state) => { return state.menus.filter(menu => menu.permission.includes(state.role)); } }, actions: { setRole(role) { this.role = role; } } })
5.创建router.js文件配置路由
// src/router/index.js import {createRouter, createWebHistory} from 'vue-router' import Home from '../views/Home.vue' import Dashboard from '../views/Dashboard.vue' import AdminPanel from '../views/AdminPanel.vue' import Product from '../views/ProductManager.vue' import {useUserStore} from '../store/user' const routes = [ {path: '/', component: Home}, {path: '/dashboard', component: Dashboard, meta: {requiresAuth: true, roles: ['user', 'admin']}}, {path: '/admin', component: AdminPanel, meta: {requiresAuth: true, roles: ['admin']}}, {path: '/product', component: Product, meta: {requiresAuth: true, roles: ['admin']}} ] const router = createRouter({ history: createWebHistory(), routes }) router.beforeEach(async (to, from, next) => { const userStore = useUserStore() if (to.matched.some(record => record.meta.requiresAuth)) { if (!to.meta.roles.includes(userStore.role)) { next({path: '/'}) } else { next() } } else { next() } }) export default router
6.创建vue所需组件
- 首页组件
<script setup> import { onMounted, ref } from 'vue' import axios from "axios"; // 定义产品列表 const products = ref([]) // 获取所有产品 const getProducts = async () => { try { const response = await axios.get('http://localhost:8080/api/product/getAll'); products.value = response.data; } catch (error) { console.error('获取产品列表时出错:', error); } }; onMounted(() => { getProducts(); }) </script> <template> <div class="product-page"> <!-- 展示产品列表 --> <section class="product-list-section"> <h2>我们的产品</h2> <ul class="product-list"> <li v-for="product in products" :key="product.id" class="product-item"> <img :src="product.image" alt="产品图片" class="product-image"> <div class="product-info"> <h3>{{ product.content }}</h3> <p>{{ product.addTime }}</p> </div> </li> </ul> </section> </div> </template> <style scoped> .product-page { font-family: 'Microsoft YaHei', Arial, sans-serif; text-align: center; background-color: #f4f4f4; } .product-list-section { padding: 50px 20px; } .product-list { list-style-type: none; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; } .product-item { background-color: white; margin: 10px; border-radius: 10px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); width: 300px; overflow: hidden; transition: transform 0.3s ease-in-out; } .product-item:hover { transform: scale(1.05); } .product-image { width: 100%; height: auto; border-top-left-radius: 10px; border-top-right-radius: 10px; } .product-info { padding: 15px; } .product-info h3 { margin-top: 0; font-size: 1.2em; color: #333; } .product-info p { font-size: 1em; color: #666; } </style>
- 面板组件
<script setup> import { onMounted, computed } from 'vue' import { useUserStore } from '../store/user' const userStore = useUserStore() // 使用计算属性获取角色信息 const role = computed(() => userStore.role) onMounted(() => { // 在组件挂载时可以执行一些初始化操作 }) </script> <template> <div class="dashboard"> <header class="dashboard-header"> <h1>仪表盘</h1> <p>欢迎回来,{{ role === 'admin' ? '管理员' : '用户' }}!</p> </header> <!-- 管理员特定内容 --> <section v-if="role === 'admin'" class="admin-section"> <div class="card"> <h2>管理员面板</h2> <p>此部分内容仅对管理员开放。</p> <button @click="showAdminDetails">查看详情</button> </div> </section> <!-- 用户特定内容 --> <section v-else class="user-section"> <div class="card"> <h2>用户仪表盘</h2> <p>欢迎来到您的个人仪表盘。在这里,您可以管理任务并查看重要信息。</p> </div> </section> <!-- 公共内容 --> <section class="common-section"> <div class="card"> <h2>公共部分</h2> <p>无论您的角色是什么,都可以看到此部分内容。</p> </div> </section> </div> </template> <style scoped> .dashboard { font-family: 'Microsoft YaHei', Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; } .dashboard-header { background-color: #4CAF50; color: white; padding: 20px; border-radius: 8px; text-align: center; } .dashboard-header h1 { font-size: 2em; margin-bottom: 0.5em; } .dashboard-header p { font-size: 1.2em; } .admin-section, .user-section, .common-section { display: flex; justify-content: space-around; flex-wrap: wrap; margin-top: 20px; } .card { background-color: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); padding: 20px; width: 30%; margin-bottom: 20px; transition: transform 0.2s; } .card:hover { transform: translateY(-5px); } .card h2 { font-size: 1.5em; margin-bottom: 0.5em; } .card p { font-size: 1em; } button { background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; margin-top: 10px; } button:hover { background-color: #45a049; } </style> <script> export default { methods: { showAdminDetails() { alert('这是管理员详情部分'); } } } </script>
- 管理员面板组件
<script setup> import {onMounted, ref} from 'vue' import {useUserStore} from '../store/user' const userStore = useUserStore() // 示例数据 const users = ref([]) // 模拟获取用户列表的方法 const fetchUsers = () => { // 这里可以替换为实际的API调用 setTimeout(() => { users.value = [ {id: 1, name: '张伟', role: 'admin'}, {id: 2, name: '李娜', role: 'user'} ] }, 1000) } onMounted(() => { if (userStore.role === 'admin') { fetchUsers() } }) </script> <template> <div class="admin-panel"> <header class="panel-header"> <h1>管理员面板</h1> <p>欢迎,管理员!</p> </header> <!-- 用户列表 --> <section v-if="users.length > 0" class="user-list-section"> <h2>用户列表</h2> <ul> <li v-for="user in users" :key="user.id" class="user-item"> <span>{{ user.name }}</span> - <span>{{ user.role === 'admin' ? '管理员' : '普通用户' }}</span> </li> </ul> </section> <section v-else class="loading-section"> <p>加载用户中...</p> </section> </div> </template> <style scoped> .admin-panel { max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f9f9f9; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); font-family: 'Microsoft YaHei', Arial, sans-serif; } .panel-header { text-align: center; margin-bottom: 20px; } .panel-header h1 { color: #333; font-size: 2em; } .panel-header p { color: #666; font-size: 1.2em; } .user-list-section, .loading-section { text-align: center; } .user-list-section ul { list-style-type: none; padding: 0; } .user-item { background-color: #fff; margin-bottom: 10px; padding: 15px; border-radius: 4px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); display: flex; justify-content: space-between; align-items: center; } .user-item span:first-child { font-weight: bold; } .loading-section p { font-size: 1.2em; color: #666; } </style>
<template> <div class="product-management"> <!-- 添加产品的弹窗 --> <div v-if="isAddModalVisible" class="modal-overlay" @click.self="closeAddModal"> <div class="modal-content"> <span class="close" @click="closeAddModal">×</span> <h2>添加产品</h2> <form @submit.prevent="saveProduct()"> <div class="form-group"> <label for="content">内容:</label> <input type="text" id="content" v-model="addProduct.content" required /> </div> <div class="form-group"> <label for="image">图片URL:</label> <input type="url" id="image" v-model="addProduct.image" required /> </div> <button type="submit">保存</button> </form> </div> </div> <!-- 编辑产品的弹窗 --> <div v-if="isEditModalVisible" class="modal-overlay" @click.self="closeEditModal"> <div class="modal-content"> <span class="close" @click="closeEditModal">×</span> <h2>编辑产品</h2> <form @submit.prevent="updateProduct()"> <div class="form-group"> <label for="editContent">内容:</label> <input type="text" id="editContent" v-model="editProductData.content" required /> </div> <div class="form-group"> <label for="editImage">图片URL:</label> <input type="url" id="editImage" v-model="editProductData.image" required /> </div> <button type="submit">更新</button> </form> </div> </div> <!-- 产品列表 --> <div class="product-list"> <h2>产品列表</h2> <ul> <li v-for="(item, index) in products" :key="index" class="product-item"> <img :src="item.image" alt="Product Image" class="product-image" @click="showImage(item.image)"> <div class="product-info"> <span>{{ item.content }}</span> <span>{{ item.addTime }}</span> </div> <div class="actions"> <button @click="editProduct(item)">编辑</button> <button @click="deleteProduct(item)">删除</button> </div> </li> </ul> </div> <!-- 添加产品的按钮 --> <button class="add-product-button" @click="openAddModal">添加产品</button> <!-- Lightbox 图片预览 --> <vue-easy-lightbox :visible="visible" :imgs="imgs" @hide="handleHide" ></vue-easy-lightbox> </div> </template> <script> import { ref, onMounted } from 'vue'; import axios from 'axios'; import VueEasyLightbox from 'vue-easy-lightbox'; export default { components: { VueEasyLightbox }, setup() { const isAddModalVisible = ref(false); const isEditModalVisible = ref(false); const addProduct = ref({ content: '', image: '' }); const editProductData = ref({ content: '', image: '' }); const products = ref([]); const currentProductId = ref(null); const visible = ref(false); const imgs = ref(''); // 打开添加产品的模态框 const openAddModal = () => { isAddModalVisible.value = true; }; // 关闭添加产品的模态框 const closeAddModal = () => { isAddModalVisible.value = false; resetAddForm(); }; // 清空添加表单 const resetAddForm = () => { addProduct.value.content = ''; addProduct.value.image = ''; }; // 打开编辑产品的模态框 const openEditModal = () => { isEditModalVisible.value = true; }; // 关闭编辑产品的模态框 const closeEditModal = () => { isEditModalVisible.value = false; resetEditForm(); }; // 清空编辑表单 const resetEditForm = () => { editProductData.value.content = ''; editProductData.value.image = ''; }; // 保存产品 const saveProduct = async () => { try { await axios.post('http://localhost:8080/api/product/save', addProduct.value); await getProducts(); closeAddModal(); } catch (error) { console.error('保存产品时出错:', error); } }; // 更新产品 const updateProduct = async () => { try { await axios.put(`http://localhost:8080/api/product/update/${currentProductId.value}`, editProductData.value); await getProducts(); closeEditModal(); } catch (error) { console.error('更新产品时出错:', error); } }; // 删除产品 const deleteProduct = async (item) => { if (confirm('确定要删除此产品吗?')) { try { await axios.delete(`http://localhost:8080/api/product/delete/${item.id}`); await getProducts(); } catch (error) { console.error('删除产品时出错:', error); } } }; // 编辑产品 const editProduct = (item) => { currentProductId.value = item.id; editProductData.value = { ...item }; openEditModal(); }; // 获取所有产品 const getProducts = async () => { try { const response = await axios.get('http://localhost:8080/api/product/getAll'); products.value = response.data; } catch (error) { console.error('获取产品列表时出错:', error); } }; // 显示大图 const showImage = (src) => { imgs.value = src; visible.value = true; }; // 隐藏大图 const handleHide = () => { visible.value = false; }; onMounted(() => { getProducts(); }); return { isAddModalVisible, isEditModalVisible, addProduct, editProductData, products, openAddModal, closeAddModal, openEditModal, closeEditModal, saveProduct, updateProduct, deleteProduct, editProduct, visible, imgs, showImage, handleHide }; } }; </script> <style scoped> .product-management { display: flex; flex-direction: column; align-items: center; font-family: 'Microsoft YaHei', Arial, sans-serif; } .add-product-button { margin-top: 20px; padding: 10px 20px; background-color: #4CAF50; color: white; border: none; cursor: pointer; font-size: 16px; border-radius: 5px; transition: background-color 0.3s ease; } .add-product-button:hover { background-color: #45a049; } .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; } .modal-content { background-color: white; padding: 20px; border-radius: 8px; width: 400px; position: relative; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } .close { position: absolute; top: 10px; right: 10px; font-size: 20px; cursor: pointer; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; font-weight: bold; } .form-group input { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; } button[type="submit"] { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; transition: background-color 0.3s ease; } button[type="submit"]:hover { background-color: #45a049; } .product-list { max-width: 1000px; width: 100%; margin-top: 20px; } .product-list h2 { text-align: center; color: #333; font-size: 1.8em; margin-bottom: 20px; } .product-list ul { list-style-type: none; padding: 0; } .product-item { display: flex; align-items: center; margin-bottom: 15px; padding: 15px; border: 1px solid #ddd; border-radius: 8px; justify-content: space-between; background-color: #f9f9f9; transition: background-color 0.3s ease; } .product-item:hover { background-color: #e9e9e9; } .product-image { max-width: 120px; max-height: 120px; object-fit: cover; margin-right: 15px; cursor: pointer; } .product-info { flex-grow: 1; } .actions button { margin-left: 10px; padding: 5px 10px; background-color: #007bff; color: white; border: none; cursor: pointer; font-size: 14px; border-radius: 3px; transition: background-color 0.3s ease; } .actions button:hover { background-color: #0056b3; } </style>
7.在App.vue中遍历Pinia数据获取权限列表
<!-- src/App.vue --> <template> <div id="app"> <!-- 导航栏 --> <nav class="menu-bar"> <ul> <li v-for="menu in accessibleMenus" :key="menu.path" class="menu-item"> <router-link :to="menu.path">{{ menu.name }}</router-link> </li> </ul> </nav> <!-- 路由视图 --> <router-view></router-view> </div> </template> <script setup> import { computed } from 'vue' import { useUserStore } from './store/user' // 使用用户状态管理 const userStore = useUserStore() // 计算属性获取可访问菜单列表 const accessibleMenus = computed(() => userStore.accessibleMenus) </script> <style scoped> /* 应用全局样式 */ #app { font-family: 'Microsoft YaHei', Arial, sans-serif; } .menu-bar { background-color: #333; padding: 10px 0; } .menu-bar ul { list-style-type: none; margin: 0; padding: 0; display: flex; justify-content: center; } .menu-item { margin: 0 15px; } .menu-item a { color: white; text-decoration: none; padding: 8px 15px; border-radius: 5px; transition: background-color 0.3s ease-in-out; } .menu-item a:hover { background-color: #4CAF50; } .menu-item a.router-link-active { background-color: #4CAF50; font-weight: bold; } </style>
8.在main.js里配置当前用户权限(user or admin)
// src/main.js import { createApp } from 'vue' import App from './App.vue' import router from './router/router.js' import { createPinia } from 'pinia' import { useUserStore } from './store/user.js' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' const app = createApp(App) const pinia = createPinia() app.use(pinia) app.use(router) app.use(ElementPlus) const userStore = useUserStore() // 模拟登录逻辑 userStore.setRole('admin') // 可以根据实际情况设置不同的角色 app.mount('#app')