引言
随着前端技术的快速发展,Vue 3的Composition API为构建复杂的企业级应用提供了更加灵活和强大的开发模式。在现代Web应用开发中,一个良好的架构设计对于项目的可维护性、可扩展性和团队协作效率至关重要。
本文将深入探讨基于Vue 3 Composition API的企业级项目架构设计,涵盖状态管理、路由权限控制、组件通信等核心主题,提供一套完整的解决方案,帮助开发者构建高质量的前端应用。
Vue 3 Composition API基础概念
Composition API的核心优势
Vue 3的Composition API通过函数式的方式来组织和复用逻辑代码,相比传统的Options API具有以下优势:
- 更好的逻辑复用:通过组合函数实现跨组件的逻辑共享
- 更清晰的代码结构:将相关的逻辑组织在一起,避免了选项API中的分散问题
- 更强的类型支持:在TypeScript环境下提供更好的类型推断
- 更灵活的开发体验:允许开发者按照业务逻辑而非生命周期来组织代码
基本使用模式
// basic-composition-api.js
import { ref, reactive, computed, watch } from 'vue'
export default {
setup() {
// 响应式数据声明
const count = ref(0)
const user = reactive({
name: 'John',
age: 30
})
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法定义
const increment = () => {
count.value++
}
// 监听器
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
return {
count,
user,
doubleCount,
increment
}
}
}
状态管理架构设计
Pinia状态管理方案
Pinia是Vue 3官方推荐的状态管理库,相比Vuex具有更轻量、更好的TypeScript支持和更灵活的API设计。
安装与配置
npm install pinia
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
Store定义与组织
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// 状态
const userInfo = ref(null)
const isAuthenticated = ref(false)
// 计算属性
const permissions = computed(() => {
return userInfo.value?.permissions || []
})
const hasPermission = (permission) => {
return permissions.value.includes(permission)
}
// 方法
const login = async (credentials) => {
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
})
const data = await response.json()
if (data.token) {
userInfo.value = data.user
isAuthenticated.value = true
// 存储token到localStorage
localStorage.setItem('token', data.token)
return { success: true }
}
} catch (error) {
return { success: false, error: error.message }
}
}
const logout = () => {
userInfo.value = null
isAuthenticated.value = false
localStorage.removeItem('token')
}
const fetchUserInfo = async () => {
try {
const token = localStorage.getItem('token')
if (!token) return
const response = await fetch('/api/user', {
headers: {
'Authorization': `Bearer ${token}`
}
})
const data = await response.json()
userInfo.value = data
isAuthenticated.value = true
} catch (error) {
console.error('Failed to fetch user info:', error)
logout()
}
}
return {
userInfo,
isAuthenticated,
permissions,
hasPermission,
login,
logout,
fetchUserInfo
}
})
多Store架构设计
// stores/index.js
import { useUserStore } from './user'
import { useAppStore } from './app'
import { usePermissionStore } from './permission'
export {
useUserStore,
useAppStore,
usePermissionStore
}
状态持久化与恢复
// stores/plugins/persistence.js
import { watch } from 'vue'
export const createPersistPlugin = (storeName, keys = []) => {
return (store) => {
// 从localStorage恢复状态
const savedState = localStorage.getItem(`pinia:${storeName}`)
if (savedState) {
try {
const parsedState = JSON.parse(savedState)
Object.assign(store, parsedState)
} catch (error) {
console.error('Failed to restore state:', error)
}
}
// 监听状态变化并保存
watch(
() => store.$state,
(newState) => {
if (keys.length > 0) {
const filteredState = keys.reduce((acc, key) => {
acc[key] = newState[key]
return acc
}, {})
localStorage.setItem(`pinia:${storeName}`, JSON.stringify(filteredState))
} else {
localStorage.setItem(`pinia:${storeName}`, JSON.stringify(newState))
}
},
{ deep: true }
)
}
}
// 使用示例
// import { createPersistPlugin } from './plugins/persistence'
//
// const userStore = useUserStore()
// userStore.$subscribe(createPersistPlugin('user', ['userInfo', 'isAuthenticated']))
路由权限控制设计
路由配置与权限标记
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/user'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue'),
meta: { requiresAuth: false }
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { requiresAuth: false }
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: {
requiresAuth: true,
permissions: ['view_dashboard']
}
},
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: {
requiresAuth: true,
permissions: ['admin_access']
}
},
{
path: '/profile',
name: 'Profile',
component: () => import('@/views/Profile.vue'),
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 全局路由守卫
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
// 如果需要认证但用户未登录
if (to.meta.requiresAuth && !userStore.isAuthenticated) {
// 检查是否有token
await userStore.fetchUserInfo()
if (!userStore.isAuthenticated) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
return
}
}
// 权限检查
if (to.meta.permissions && to.meta.permissions.length > 0) {
const hasPermission = to.meta.permissions.every(permission =>
userStore.hasPermission(permission)
)
if (!hasPermission) {
next('/403')
return
}
}
next()
})
export default router
动态路由加载
// router/dynamicRoutes.js
import { useUserStore } from '@/stores/user'
export const generateDynamicRoutes = async () => {
const userStore = useUserStore()
// 根据用户权限生成动态路由
const baseRoutes = [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: {
requiresAuth: true,
permissions: ['view_dashboard']
}
}
]
// 根据用户角色动态添加路由
if (userStore.hasPermission('admin_access')) {
baseRoutes.push({
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: {
requiresAuth: true,
permissions: ['admin_access']
}
})
}
if (userStore.hasPermission('report_access')) {
baseRoutes.push({
path: '/reports',
name: 'Reports',
component: () => import('@/views/Reports.vue'),
meta: {
requiresAuth: true,
permissions: ['report_access']
}
})
}
return baseRoutes
}
路由权限组件封装
<!-- components/PermissionWrapper.vue -->
<template>
<div v-if="hasPermission" class="permission-wrapper">
<slot></slot>
</div>
<div v-else class="no-permission">
<slot name="no-permission">
<p>您没有访问此内容的权限</p>
</slot>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useUserStore } from '@/stores/user'
const props = defineProps({
permission: {
type: String,
required: true
}
})
const userStore = useUserStore()
const hasPermission = computed(() => {
return userStore.hasPermission(props.permission)
})
</script>
<style scoped>
.permission-wrapper {
/* 权限组件样式 */
}
.no-permission {
padding: 20px;
text-align: center;
color: #999;
border: 1px solid #eee;
border-radius: 4px;
}
</style>
权限控制完整解决方案
基于RBAC的权限系统
// stores/permission.js
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const usePermissionStore = defineStore('permission', () => {
// 角色和权限数据
const roles = ref([])
const permissions = ref([])
// 用户角色映射
const userRoles = ref([])
// 计算属性:获取用户所有权限
const userPermissions = computed(() => {
const allPermissions = new Set()
userRoles.value.forEach(role => {
const roleInfo = roles.value.find(r => r.name === role)
if (roleInfo && roleInfo.permissions) {
roleInfo.permissions.forEach(permission => {
allPermissions.add(permission)
})
}
})
return Array.from(allPermissions)
})
// 权限检查方法
const hasPermission = (permission) => {
return userPermissions.value.includes(permission)
}
const hasAnyPermission = (permissionsArray) => {
return permissionsArray.some(permission =>
userPermissions.value.includes(permission)
)
}
const hasAllPermissions = (permissionsArray) => {
return permissionsArray.every(permission =>
userPermissions.value.includes(permission)
)
}
// 角色检查方法
const hasRole = (role) => {
return userRoles.value.includes(role)
}
// 初始化权限系统
const initPermissionSystem = async () => {
try {
// 获取用户角色和权限信息
const response = await fetch('/api/permission/user')
const data = await response.json()
roles.value = data.roles || []
permissions.value = data.permissions || []
userRoles.value = data.userRoles || []
return { success: true }
} catch (error) {
console.error('Failed to initialize permission system:', error)
return { success: false, error: error.message }
}
}
return {
roles,
permissions,
userRoles,
userPermissions,
hasPermission,
hasAnyPermission,
hasAllPermissions,
hasRole,
initPermissionSystem
}
})
权限路由配置工具
// utils/permission.js
export const filterRoutesByPermission = (routes, permissions) => {
return routes.filter(route => {
// 如果不需要权限验证,直接通过
if (!route.meta || !route.meta.requiresAuth) {
return true
}
// 检查是否需要认证
if (route.meta.requiresAuth && !permissions.isAuthenticated) {
return false
}
// 检查权限要求
if (route.meta.permissions && route.meta.permissions.length > 0) {
const hasRequiredPermissions = route.meta.permissions.every(permission =>
permissions.userPermissions.includes(permission)
)
return hasRequiredPermissions
}
return true
})
}
export const buildPermissionTree = (permissions) => {
const tree = {}
permissions.forEach(permission => {
const parts = permission.split('.')
let current = tree
parts.forEach((part, index) => {
if (!current[part]) {
current[part] = {}
}
if (index === parts.length - 1) {
current[part].permission = permission
}
current = current[part]
})
})
return tree
}
权限控制指令封装
// directives/permission.js
import { usePermissionStore } from '@/stores/permission'
export default {
mounted(el, binding, vnode) {
const permissionStore = usePermissionStore()
const permission = binding.value
if (!permissionStore.hasPermission(permission)) {
el.style.display = 'none'
el.setAttribute('disabled', true)
}
},
updated(el, binding, vnode) {
const permissionStore = usePermissionStore()
const permission = binding.value
if (!permissionStore.hasPermission(permission)) {
el.style.display = 'none'
el.setAttribute('disabled', true)
} else {
el.style.display = ''
el.removeAttribute('disabled')
}
}
}
<!-- 使用权限指令 -->
<template>
<div>
<button v-permission="'user.create'">创建用户</button>
<button v-permission="'user.delete'">删除用户</button>
<div v-permission="'admin.view'">
管理员专属内容
</div>
</div>
</template>
<script setup>
import { usePermissionStore } from '@/stores/permission'
const permissionStore = usePermissionStore()
</script>
组件通信最佳实践
基于Pinia的组件间通信
// components/GlobalMessage.vue
<template>
<div class="message-container">
<transition-group name="message" tag="div">
<div
v-for="message in messages"
:key="message.id"
class="message-item"
:class="message.type"
>
{{ message.content }}
<button @click="removeMessage(message.id)">×</button>
</div>
</transition-group>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useAppStore } from '@/stores/app'
const appStore = useAppStore()
const messages = computed(() => appStore.messages)
const removeMessage = (id) => {
appStore.removeMessage(id)
}
</script>
<style scoped>
.message-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
.message-item {
padding: 12px 16px;
margin-bottom: 8px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.message-item.success {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.message-item.error {
background-color: #fff2f0;
border: 1px solid #ffccc7;
color: #ff4d4f;
}
.message-item.warning {
background-color: #fffbe6;
border: 1px solid #ffe58f;
color: #faad14;
}
.message-item.info {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
}
.message-item button {
background: none;
border: none;
cursor: pointer;
font-size: 16px;
margin-left: 8px;
}
</style>
事件总线模式
// utils/eventBus.js
import { createApp } from 'vue'
const EventBus = {
install(app) {
const eventBus = createApp({}).config.globalProperties.$eventBus = {}
// 实现事件监听
eventBus.on = (event, callback) => {
if (!eventBus._events) {
eventBus._events = {}
}
if (!eventBus._events[event]) {
eventBus._events[event] = []
}
eventBus._events[event].push(callback)
}
// 实现事件触发
eventBus.emit = (event, data) => {
if (eventBus._events && eventBus._events[event]) {
eventBus._events[event].forEach(callback => callback(data))
}
}
// 实现事件移除
eventBus.off = (event, callback) => {
if (eventBus._events && eventBus._events[event]) {
if (callback) {
eventBus._events[event] = eventBus._events[event].filter(cb => cb !== callback)
} else {
delete eventBus._events[event]
}
}
}
app.config.globalProperties.$eventBus = eventBus
}
}
export default EventBus
代码分割与性能优化
动态导入和懒加载
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue'),
meta: { requiresAuth: false }
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: {
requiresAuth: true,
permissions: ['view_dashboard']
}
},
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: {
requiresAuth: true,
permissions: ['admin_access']
}
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
组件懒加载
<!-- components/LazyComponent.vue -->
<template>
<div v-if="loaded">
<component :is="dynamicComponent" v-bind="componentProps" />
</div>
<div v-else class="loading-placeholder">
加载中...
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const props = defineProps({
componentName: {
type: String,
required: true
},
componentProps: {
type: Object,
default: () => ({})
}
})
const loaded = ref(false)
const dynamicComponent = ref(null)
onMounted(async () => {
try {
// 动态导入组件
const componentModule = await import(`@/components/${props.componentName}.vue`)
dynamicComponent.value = componentModule.default
loaded.value = true
} catch (error) {
console.error('Failed to load component:', error)
}
})
</script>
<style scoped>
.loading-placeholder {
padding: 20px;
text-align: center;
color: #999;
}
</style>
项目结构设计
标准化项目目录结构
src/
├── assets/ # 静态资源
│ ├── images/
│ ├── styles/
│ └── icons/
├── components/ # 公共组件
│ ├── layout/
│ ├── ui/
│ └── shared/
├── composables/ # 组合式函数
│ ├── useAuth.js
│ ├── useApi.js
│ └── usePermission.js
├── hooks/ # 自定义钩子
│ ├── useLocalStorage.js
│ └── useDebounce.js
├── views/ # 页面组件
│ ├── Home.vue
│ ├── Login.vue
│ ├── Dashboard/
│ └── Admin/
├── stores/ # 状态管理
│ ├── index.js
│ ├── user.js
│ ├── app.js
│ └── permission.js
├── router/ # 路由配置
│ ├── index.js
│ └── routes.js
├── services/ # API服务
│ ├── api.js
│ ├── auth.js
│ └── user.js
├── utils/ # 工具函数
│ ├── helpers.js
│ ├── validators.js
│ └── constants.js
├── plugins/ # 插件
│ ├── eventBus.js
│ └── permission.js
└── App.vue # 根组件
环境配置管理
// config/index.js
const config = {
development: {
apiUrl: 'http://localhost:3000/api',
debug: true,
enableMock: true
},
production: {
apiUrl: 'https://api.yourapp.com/api',
debug: false,
enableMock: false
},
staging: {
apiUrl: 'https://staging-api.yourapp.com/api',
debug: true,
enableMock: false
}
}
export default config[process.env.NODE_ENV] || config.development
完整示例应用
应用入口文件
<!-- App.vue -->
<template>
<div id="app">
<router-view />
<GlobalMessage />
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { useUserStore } from '@/stores/user'
import { useAppStore } from '@/stores/app'
const userStore = useUserStore()
const appStore = useAppStore()
onMounted(async () => {
// 应用启动时初始化用户信息
await userStore.fetchUserInfo()
// 初始化应用状态
appStore.initApp()
})
</script>
<style>
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f5f5f5;
}
#app {
min-height: 100vh;
}
</style>
权限控制的完整流程
// composables/usePermission.js
import { computed } from 'vue'
import { useUserStore } from '@/stores/user'
import { usePermissionStore } from '@/stores/permission'
export function usePermission() {
const userStore = useUserStore()
const permissionStore = usePermissionStore()
const hasPermission = (permission) => {
return userStore.hasPermission(permission)
}
const hasRole = (role) => {
return userStore.hasRole(role)
}
const checkPermissions = (permissions) => {
if (!Array.isArray(permissions)) {
permissions = [permissions]
}
return permissions.every(permission =>
userStore.hasPermission(permission)
)
}
const checkRoles = (roles) => {
if (!Array.isArray(roles)) {
roles = [roles]
}
return roles.some(role => userStore.hasRole(role))
}
// 权限检查组合式函数
const usePermissionCheck = (permission, options = {}) => {
const {
redirect = true,
redirectPath = '/403',
showComponent = true
} = options
const hasAccess = computed(() => {
return userStore.hasPermission(permission)
})
const checkAndRedirect = () => {
if (!hasAccess.value && redirect) {
// 这里可以使用路由跳转
console.log(`Redirecting to ${redirectPath}`)
}
}
return {
hasAccess,
checkAndRedirect
}
}
return {
hasPermission,
hasRole,
checkPermissions,
checkRoles,
usePermissionCheck
}
}
总结与最佳实践
架构设计原则
- 单一职责原则:每个模块只负责特定的功能领域
- 可扩展性设计:预留扩展点,便于功能增加
- 可维护性考虑:代码结构清晰,文档完整
- 性能优化:合理使用缓存、懒加载等技术
性能优化建议
- 路由懒加载:减少初始包大小
- 组件按需加载:避免不必要的组件引入
- 状态管理优化:只监听必要的状态变化
- 代码分割:合理划分代码块
安全性考虑
- 前端权限控制:作为用户体验的增强,不是安全防护的唯一手段
- 后端验证:所有权限检查都应该在后端进行验证
- 敏感数据处理:避免在前端存储敏感信息
- HTTPS使用:确保所有API通信都是加密的
通过本文介绍的基于Vue 3 Composition API的企业级项目架构设计,开发者可以构建出具有良好可维护性、可扩展性和安全性的现代化前端应用。这套方案结合了Pinia状态管理、路由权限控制、组件通信等核心技术,为实际项目开发提供了完整的解决方案。

评论 (0)