当前位置: 首页 > article >正文

权限管理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">&times;</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">&times;</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')

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


http://www.kler.cn/a/573068.html

相关文章:

  • kotlin高级用法总结
  • 3.RabbitMQ管理
  • 什么是XSS,什么是CSP,什么是gevent
  • docker 常用容器启动 docker-compose.yml 配置文件详解
  • IP离线库技术解析:实现高效数据处理能力
  • 【Qt】Qt Widgets和QML(Qt Quick)开发界面的区别
  • MySQL SyntaxErrorException SELECT list is not in GROUP BY 报错解决
  • 如何在Android中实现服务(Service)
  • 使用 CMake 构建 Qt 动态库模块
  • Tick数据20241224
  • 人机交互革命:从触屏到脑波的13维战争
  • grpc工具使用
  • 剑指 Offer II 060. 出现频率最高的 k 个数字
  • 基于RKNN的嵌入式深度学习开发(2)
  • 第3章 nmap网络映射器(网络安全防御实战--蓝军武器库)
  • 大语言模型中温度参数(Temperature)的核心原理
  • 汽车免拆诊断案例 | 2023款丰田雷凌汽油版车行驶中偶尔出现通信故障
  • PHP之字符串拼接
  • NLP如何训练AI模型以理解知识
  • 【Hudi-SQL DDL创建表语法】