一、权限管理
1.1 权限分类
核心概念
权限管理本质上可以分为三类:
| 类型 | 说明 | 实现方式 |
|---|---|---|
| 功能权限 | 决定用户可以访问哪些页面或功能模块 | 动态路由 |
| 按钮权限 | 决定用户在某个页面上可以使用哪些具体操作 | 指令/组件控制 |
| 接口权限 | 决定用户可以调用哪些接口 | 服务端校验 + 前端配合 |
1.2 实现步骤
流程图:
登录 → 获取权限树 → 生成动态路由 → 添加路由 → 渲染菜单
↓
按钮权限控制
↓
接口权限校验
代码实现:
// 1. 登录后获取权限树
const userInfo = await login(credentials)
const permissionTree = await getPermissionTree(userInfo.id)
// 2. 生成动态路由
const routes = generateRoutes(permissionTree)
// 3. 添加路由
routes.forEach((route) => {
router.addRoute(route)
})
// 4. 按钮权限指令
app.directive("permission", {
mounted(el, binding) {
const { value } = binding
const hasPermission = store.state.permissions.includes(value)
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
},
})使用示例:
<template>
<!-- 按钮权限 -->
<button v-permission="'user:add'">添加用户</button>
<!-- 条件渲染 -->
<el-button v-if="hasPermission('user:delete')">删除</el-button>
</template>二、Token 处理
2.1 Token 完整流程
登录 → 获取 Token → 存储 Token → 请求携带 → 验证 Token → 刷新/过期
2.2 请求拦截器配置
// request.js
import axios from "axios"
import { getToken, removeToken } from "@/utils/auth"
const request = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 15000,
})
// 请求拦截器
request.interceptors.request.use(
(config) => {
const token = getToken()
if (token) {
config.headers["Authorization"] = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
},
)
// 响应拦截器
request.interceptors.response.use(
(response) => {
const { code, data } = response.data
// 业务错误处理
if (code !== 200) {
Message.error(data.message || "Error")
return Promise.reject(new Error(data.message || "Error"))
}
return data
},
(error) => {
// 401 Token 过期
if (error.response?.status === 401) {
removeToken()
location.reload()
}
return Promise.reject(error)
},
)
export default request2.3 Token 存储方式对比
| 存储方式 | 优点 | 缺点 | 安全性 |
|---|---|---|---|
| localStorage | 持久化 | XSS 攻击 | ⭐⭐ |
| sessionStorage | 会话级安全 | 刷新丢失 | ⭐⭐⭐ |
| Cookie (HttpOnly) | 防 XSS | 需服务端配合 | ⭐⭐⭐⭐ |
| Pinia/Vuex | 内存安全 | 刷新丢失 | ⭐⭐⭐ |
最佳实践
推荐组合:
Cookie (HttpOnly)+内存存储,双重保护
三、单点登录(SSO)
3.1 常见方案对比
| 方案 | 适用场景 | 实现难度 |
|---|---|---|
| 主域名共享 Cookie | 同主域名子系统 | ⭐ |
| CAS | 跨域多系统 | ⭐⭐⭐ |
| OAuth2.0 | 第三方登录 | ⭐⭐⭐ |
| JWT | 分布式系统 | ⭐⭐ |
3.2 同主域名方案
// 主域名:baidu.com
// 子系统:xxx.baidu.com
// 设置 Cookie 时指定主域名
document.cookie = `token=${token}; domain=.baidu.com; path=/`3.3 CAS 流程
1. 用户访问 system-a.com → 跳转 CAS 认证中心
2. 登录成功后生成全局会话 → 返回 system-a.com?ticket=ST-xxx
3. system-a 验证 ticket → 获取用户信息
4. 访问 system-b.com → 发现已有全局会话 → 直接登录
四、全局异常捕获
4.1 Vue 错误处理
// main.js
const app = createApp(App)
// Vue 级别错误
app.config.errorHandler = (err, vm, info) => {
console.error("Vue Error:", err, info)
// 发送错误日志到服务器
reportError({
message: err.message,
stack: err.stack,
info,
})
}
// 全局未捕获错误
window.onerror = function (message, source, lineno, colno, error) {
console.error("Global Error:", message, source, lineno, colno, error)
reportError({ message, source, lineno, colno, error })
return false
}
// Promise 错误
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled Promise:", event.reason)
reportError({
message: event.reason?.message || "Unhandled Promise",
stack: event.reason?.stack,
})
})
// 资源加载错误
window.addEventListener(
"error",
(event) => {
if (event.target.tagName) {
// 图片、脚本等资源加载错误
console.error("Resource Error:", event.target.src || event.target.href)
}
},
true,
)4.2 错误上报服务
// utils/error-report.js
export function reportError(error) {
const errorData = {
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now(),
...error,
}
// 使用 sendBeacon 保证离线也能发送
if (navigator.sendBeacon) {
navigator.sendBeacon("/api/error/log", JSON.stringify(errorData))
} else {
// 降级方案
fetch("/api/error/log", {
method: "POST",
body: JSON.stringify(errorData),
keepalive: true,
})
}
}五、常见功能实现
5.1 防抖与节流
// 防抖(debounce)- n 秒后执行,期间触发重新计时
function debounce(fn, delay = 300) {
let timer = null
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 节流(throttle)- n 秒内只执行一次
function throttle(fn, delay = 300) {
let lastTime = 0
return function (...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}5.2 深拷贝
// 简单深拷贝
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== "object") return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
// 循环引用检测
if (map.has(obj)) return map.get(obj)
const clone = Array.isArray(obj) ? [] : {}
map.set(obj, clone)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map)
}
}
return clone
}5.3 数组转树形结构
// 数组转树
function arrayToTree(array, parentId = null) {
return array
.filter((item) => item.parentId === parentId)
.map((item) => ({
...item,
children: arrayToTree(array, item.id),
}))
}
// 树转数组
function treeToArray(tree) {
return tree.reduce((acc, item) => {
const { children, ...rest } = item
acc.push(rest)
if (children?.length) {
acc.push(...treeToArray(children))
}
return acc
}, [])
}