从零到一:构建企业级流程协作平台的技术实践
本文基于一个真实的企业级流程协作平台项目,深入探讨 Vue 3 + TypeScript + Monorepo 架构下的核心技术实现,包括动态路由、权限控制、可视化流程设计器等核心功能的底层原理与最佳实践。
📖 项目背景
Enterprise Flow Platform 是一个基于 Vue 3 的企业级流程协作平台,提供流程设计、任务审批、数据看板、用户管理等核心功能。项目采用 Monorepo 架构,使用 pnpm workspace 管理多包项目,实现了完整的 RBAC 权限控制和动态路由系统。
🎯 核心技术架构
技术栈选型
| 技术 | 版本 | 选型原因 |
|---|---|---|
| Vue 3 | 3.5+ | Composition API 提供更好的逻辑复用和类型推导 |
| TypeScript | 5.0+ | 类型安全,提升代码可维护性 |
| Vite | 7+ | 极速的 HMR 和构建速度 |
| Pinia | 3.0+ | Vue 官方推荐,比 Vuex 更简洁 |
| LogicFlow | 2.1+ | 专业的流程图编辑框架 |
| ECharts | 6.0+ | 强大的数据可视化能力 |
| pnpm | 8+ | 高效的包管理,支持 Monorepo |
🔥 核心技术点深度解析
1. 动态路由系统:基于权限的路由加载
1.1 为什么需要动态路由?
在传统的静态路由方案中,所有路由都在应用启动时注册,这会导致:
- 安全问题:用户可以通过修改 URL 访问无权限的页面
- 性能问题:加载了用户不需要的路由和组件
- 用户体验:菜单中显示用户无法访问的功能
动态路由方案可以根据用户权限动态加载路由,实现真正的权限隔离。
1.2 实现原理
核心思路:在路由守卫中,根据用户权限过滤路由配置,使用 router.addRoute() 动态添加路由。
// apps/client/src/router/index.ts
router.beforeEach(async (to, _from, next) => {
const userStore = useUserStore()
const routesAdded = userStore.routesAdded
// 如果路由还没有添加过
if (!routesAdded) {
try {
// 获取用户信息
let userinfo = localStorage.getItem("USER_INFO")
if (!userinfo) {
const parsed = parseToken(userStore.token)
if (parsed && parsed.username) {
const userData = await getUserInfoApi(parsed.username)
userinfo = JSON.stringify(userData)
localStorage.setItem("USER_INFO", userinfo)
userStore.userInfo = userData
}
}
// 根据权限过滤路由
const permittedRoutes = filterRoutesByPermission(
routes,
userStore.getUserPermissions
)
// 动态添加路由
if (permittedRoutes.length > 0) {
permittedRoutes.forEach((route) => {
router.addRoute(route)
})
userStore.setRoutesAdded(true)
next({ ...to, replace: true })
return
}
} catch (error) {
ElMessage.error("获取用户信息失败,请重新登录")
userStore.logout()
next(`/login?redirect=${to.path}`)
}
}
// 权限检查
if (to.meta?.permissions) {
const hasPermission = hasAnyPermission(to.meta.permissions as string[])
if (!hasPermission) {
ElMessage.error("没有权限访问该页面")
next("/403")
return
}
}
next()
})
路由过滤函数:
// apps/client/src/router/permission.ts
export function filterRoutesByPermission(
routes: Array<any>,
userPermissions: string[]
) {
return routes.filter((route) => {
// 没有权限要求的路由,直接放行
if (!route.meta?.permissions) {
return true
}
// 检查用户是否拥有路由要求的任意一个权限
return route.meta.permissions.some((permission: string) =>
userPermissions.includes(permission)
)
})
}
1.3 关键技术细节
1. 避免重复添加路由
使用 routesAdded 标志位确保路由只添加一次:
const routesAdded = ref(false)
const setRoutesAdded = (val: boolean) => {
routesAdded.value = val
}
2. 路由替换策略
使用 next({ ...to, replace: true }) 替换当前导航,确保动态路由生效:
permittedRoutes.forEach((route) => {
router.addRoute(route)
})
userStore.setRoutesAdded(true)
next({ ...to, replace: true }) // 关键:replace 确保路由生效
3. 路由定义示例
// apps/client/src/router/routes.ts
{
path: '/user',
name: 'User',
component: () => import('../views/user/User.vue'),
meta: {
permissions: ['user:view'], // 权限要求
title: '用户管理'
}
}
1.4 竞对方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 静态路由 + 路由守卫 | 实现简单,性能好 | 安全性差,菜单显示问题 | 小型项目 |
| 动态路由(本文方案) | 安全性高,按需加载 | 实现复杂,需要处理异步 | 中大型项目 ✅ |
| 后端返回路由配置 | 权限控制最严格 | 前后端耦合,维护成本高 | 大型企业项目 |
| 微前端方案 | 完全隔离,独立部署 | 架构复杂,通信成本高 | 超大型项目 |
1.5 Vue Router 底层原理
router.addRoute() 的实现原理:
- 路由注册:Vue Router 内部维护一个路由映射表(
matcher) - 动态添加:
addRoute()会更新这个映射表,添加新的路由规则 - 匹配算法:使用路径匹配算法(基于正则表达式)查找匹配的路由
- 组件加载:通过动态
import()实现代码分割
路由匹配流程:
用户访问 /user
↓
router.beforeEach 触发
↓
检查路由是否已注册
↓
未注册 → 动态添加路由 → 重新匹配
已注册 → 权限检查 → 放行/拦截
2. Token 认证与无感刷新
2.1 为什么选择 Axios 拦截器?
Axios 拦截器提供了统一的请求/响应处理机制,相比在每个 API 调用中手动添加 Token,拦截器方案具有:
- 代码复用:一次配置,全局生效
- 统一处理:错误处理、Token 刷新等逻辑集中管理
- 解耦设计:业务代码无需关心认证细节
2.2 实现原理
核心挑战:在 Monorepo 架构中,共享包(flow-utils)不能直接依赖应用包(apps/client)的 Pinia Store,需要通过依赖注入解决。
解决方案:通过函数参数注入 Pinia 和 Router 实例。
// packages/flow-utils/src/request.ts
export function setupInterceptors(
pinia: Pinia,
router: Router,
useUserStore: any
) {
// 1. 请求拦截器:自动注入 Token
instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const userStore = useUserStore(pinia)
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 2. 响应拦截器:统一错误处理
instance.interceptors.response.use(
(res: AxiosResponse) => {
return res.data
},
(error) => {
const status = error.response?.status
if (status === 401) {
const userStore = useUserStore(pinia)
userStore.logout()
ElMessage.error("登录状态已过期,请重新登录!")
// 保存当前路径,登录后重定向
const currentPath = router.currentRoute.value.fullPath
router.push({
path: "/login",
query: {
redirect: currentPath === "/login" ? undefined : currentPath
},
})
} else if (status && status >= 500) {
ElMessage.error(`服务器错误:${status}`)
} else {
ElMessage.error(error.message)
}
return Promise.reject(error)
}
)
}
在应用入口调用:
// apps/client/src/main.ts
import { setupInterceptors } from "../../../packages/flow-utils/src/index"
app.use(pinia)
app.use(router)
// 依赖注入:将 Pinia 和 Router 注入到拦截器
setupInterceptors(pinia, router, useUserStore)
2.3 Token 无感刷新方案(扩展)
虽然当前项目未实现,但可以基于以下方案扩展:
// Token 刷新逻辑
let isRefreshing = false
let failedQueue: Array<{
resolve: (value?: any) => void
reject: (reason?: any) => void
}> = []
const processQueue = (error: any, token: string | null = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error)
} else {
prom.resolve(token)
}
})
failedQueue = []
}
instance.interceptors.response.use(
(res) => res.data,
async (error) => {
const { config, response } = error
if (response?.status === 401 && !config._retry) {
if (isRefreshing) {
// 如果正在刷新,将请求加入队列
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject })
}).then(token => {
config.headers.Authorization = `Bearer ${token}`
return instance(config)
}).catch(err => {
return Promise.reject(err)
})
}
config._retry = true
isRefreshing = true
try {
const userStore = useUserStore(pinia)
const newToken = await refreshTokenApi(userStore.refreshToken)
userStore.setToken(newToken)
processQueue(null, newToken)
config.headers.Authorization = `Bearer ${newToken}`
return instance(config)
} catch (err) {
processQueue(err, null)
userStore.logout()
router.push('/login')
return Promise.reject(err)
} finally {
isRefreshing = false
}
}
return Promise.reject(error)
}
)
2.4 竞对方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 手动添加 Token | 简单直接 | 代码重复,易遗漏 | 小型项目 |
| Axios 拦截器(本文) | 统一管理,代码复用 | 需要处理依赖注入 | 中大型项目 ✅ |
| Fetch 封装 | 原生 API,无依赖 | 需要手动实现拦截器 | 轻量级项目 |
| GraphQL + Apollo | 类型安全,缓存机制 | 学习成本高 | GraphQL 项目 |
3. 可视化流程设计器:LogicFlow 深度集成
3.1 为什么选择 LogicFlow?
对比其他流程图库:
| 库 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| LogicFlow | 专业、可扩展、中文文档 | 学习曲线陡 | 企业级应用 ✅ |
| jsPlumb | 轻量、灵活 | 需要手动实现很多功能 | 简单流程图 |
| mxGraph | 功能强大 | 商业授权,文档少 | 复杂图形应用 |
| React Flow | React 生态 | Vue 项目不适用 | React 项目 |
3.2 核心实现:Composable 模式
使用 Vue 3 Composition API 封装 LogicFlow 逻辑,实现关注点分离:
// apps/client/src/views/dashboard/processDesigner/composables/useLogicFlow.ts
export function useLogicFlow() {
const lf = shallowRef<LFInstance | null>(null)
const currentNode = ref<any | null>(null)
const showProperty = ref(false)
// 初始化 LogicFlow 实例
function initLogicFlow(container: HTMLElement): Promise<void> {
// 1. 销毁旧实例,避免内存泄漏
if (lf.value) {
try { lf.value.destroy() } catch {}
lf.value = null
}
// 2. 创建新实例
lf.value = new LogicFlow({
container: container,
grid: { type: 'dot', size: 10 },
keyboard: { enabled: true },
})
// 3. 注册自定义节点
registerCustomNodes(lf.value)
// 4. 等待容器具备有效尺寸
return new Promise<void>((resolve) => {
const ro = new ResizeObserver(() => {
const rect = container.getBoundingClientRect()
if (rect.width > 0 && rect.height > 0) {
lf.value?.render({})
ro.disconnect()
resolve()
}
})
ro.observe(container)
})
}
// 保存为模板
function saveAsTemplate(payload: {
name: string
category: string
description: string
}) {
if (!lf.value) return
const data = lf.value.getGraphData()
const result = transformLogicFlowDataToTemplate(data)
processTemplateTable.create({
name: payload.name,
category: payload.category,
description: payload.description,
nodes: result.nodes,
isActive: true,
})
}
// 从模板渲染
async function renderFromTemplate(template: ProcessTemplate) {
if (!lf.value) return
// 等待初始化完成
if (initPromise) {
await initPromise
}
// 确保容器已就绪
await new Promise<void>((resolve) => {
const ensure = () => {
const ok = containerEl &&
containerEl.isConnected &&
containerEl.getBoundingClientRect().width > 0
if (ok) return resolve()
requestAnimationFrame(ensure)
}
ensure()
})
// 清空旧数据
lf.value.clearData()
// 根据模板生成节点和连线
const nodes: any[] = []
const edges: any[] = []
// 生成起始节点
nodes.push({
id: 'start',
type: 'apply-node',
x: 240,
y: 320,
text: '申请',
})
// 生成审批节点
const sorted = [...template.nodes].sort((a, b) =>
(a.order ?? 0) - (b.order ?? 0)
)
sorted.forEach((n, idx) => {
const x = 240 + 220 * (idx + 1)
const nodeId = `approve_${idx}`
nodes.push({
id: nodeId,
type: n.type === 'condition' ? 'judge-node' : 'approve-node',
x,
y: 320,
text: n.name || '审批',
properties: {
role: n.approvers?.[0],
approvalType: n.type,
},
})
// 添加连线
const prevId = idx === 0 ? 'start' : `approve_${idx - 1}`
edges.push({
id: `e_${prevId}_${nodeId}`,
type: 'polyline',
sourceNodeId: prevId,
targetNodeId: nodeId,
})
})
// 渲染
lf.value.render({ nodes, edges })
}
return {
lf,
currentNode,
showProperty,
initLogicFlow,
saveAsTemplate,
renderFromTemplate,
dispose,
}
}
3.3 自定义节点注册
// apps/client/src/views/dashboard/processDesigner/composables/nodeConfig.ts
export function registerCustomNodes(lf: LogicFlow) {
// 申请节点(圆形)
lf.register({
type: 'apply-node',
view: CircleNode,
model: ApplyNodeModel
})
// 审批节点(矩形)
lf.register({
type: 'approve-node',
view: RectNode,
model: ApproveNodeModel
})
// 判断节点(菱形)
lf.register({
type: 'judge-node',
view: DiamondNode,
model: JudgeNodeModel
})
}
3.4 关键技术细节
1. 容器尺寸问题处理
LogicFlow 需要容器有有效尺寸才能渲染,使用 ResizeObserver 监听容器尺寸变化:
const ro = new ResizeObserver(() => {
const rect = container.getBoundingClientRect()
if (rect.width > 0 && rect.height > 0) {
lf.value?.render({})
ro.disconnect()
resolve()
}
})
ro.observe(container)
2. 内存泄漏防护
在组件卸载时清理资源:
function dispose() {
if (ro) {
ro.disconnect()
ro = null
}
if (lf.value) {
try {
lf.value.destroy()
} catch {}
lf.value = null
}
}
onBeforeUnmount(() => {
dispose()
})
3. 异步渲染竞态条件处理
使用 initPromise 确保初始化完成后再渲染:
let initPromise: Promise<void> | null = null
async function renderFromTemplate(template: ProcessTemplate) {
if (initPromise) {
await initPromise // 等待初始化完成
}
// 继续渲染逻辑
}
4. 权限控制系统:RBAC 实现
4.1 权限验证函数
// packages/flow-utils/src/auth.ts
export function hasPermission(permissionCode: string): boolean {
const userStore = useUserStore()
const userPermissions = userStore.getUserPermissions
if (!permissionCode) {
return false
}
// admin 角色拥有所有权限
if (userPermissions.includes("admin")) {
return true
}
return userPermissions.includes(permissionCode)
}
export function hasAnyPermission(permissionCodes: string[]): boolean {
if (!permissionCodes || permissionCodes.length === 0) {
return true // 无权限要求,默认放行
}
return permissionCodes.some((code) => hasPermission(code))
}
4.2 按钮级权限控制
<template>
<el-button
v-if="hasPermission('user:edit')"
@click="handleEdit"
>
编辑
</el-button>
</template>
<script setup lang="ts">
import { hasPermission } from '@/utils/auth'
const handleEdit = () => {
// 编辑逻辑
}
</script>
4.3 路由级权限控制
在路由定义中添加 meta.permissions:
{
path: '/user',
component: () => import('@/views/user/User.vue'),
meta: {
permissions: ['user:view']
}
}
在路由守卫中检查:
if (to.meta?.permissions) {
const hasPermission = hasAnyPermission(to.meta.permissions as string[])
if (!hasPermission) {
ElMessage.error("没有权限访问该页面")
next("/403")
return
}
}
5. Monorepo 架构设计
5.1 为什么选择 Monorepo?
传统多仓库方案的问题:
- 代码重复:工具函数需要在每个项目中复制
- 版本管理困难:修改工具函数需要同步多个仓库
- 依赖管理复杂:需要手动管理包版本
Monorepo 方案的优势:
- 代码共享:工具函数统一管理
- 原子提交:相关改动可以一起提交
- 依赖统一:使用 pnpm workspace 统一管理
5.2 项目结构
enterprise-flow-platform/
├── apps/
│ └── client/ # 前端应用
│ ├── src/
│ │ ├── api/ # API 接口
│ │ ├── components/ # 组件
│ │ ├── router/ # 路由
│ │ └── stores/ # Pinia Store
│ └── package.json
├── packages/
│ └── flow-utils/ # 工具包
│ └── src/
│ ├── auth.ts # 权限验证
│ ├── token.ts # Token 管理
│ ├── request.ts # HTTP 请求
│ └── index.ts # 导出入口
└── pnpm-workspace.yaml # Workspace 配置
5.3 pnpm Workspace 配置
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
package.json 配置:
{
"name": "flow-utils",
"version": "1.0.0",
"main": "src/index.ts",
"types": "src/index.ts"
}
依赖注入解决循环依赖:
// ❌ 错误:共享包不能直接依赖应用包
import { useUserStore } from '../../../apps/client/src/stores/modules/user'
// ✅ 正确:通过参数注入
export function setupInterceptors(
pinia: Pinia,
router: Router,
useUserStore: any
) {
// 使用注入的依赖
}
6. 数据可视化:ECharts 按需引入
6.1 为什么按需引入?
ECharts 全量引入体积约 700KB,按需引入可以减少 60%+ 的体积。
6.2 实现方案
// apps/client/src/views/charts/ApprovalRateChart.vue
import * as echarts from 'echarts/core'
import { BarChart } from 'echarts/charts'
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent
} from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
// 注册需要的组件
echarts.use([
BarChart,
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
CanvasRenderer
])
6.3 图表实现
function render() {
if (!chartRef.value) return
if (!chart) {
chart = echarts.init(chartRef.value)
}
const { labels, passData, rejectData } = buildSeries()
chart.setOption({
title: { text: '审批通过率', left: 'center', top: 5 },
tooltip: { trigger: 'axis' },
legend: { data: ['通过', '驳回'], top: 28 },
grid: { left: 30, right: 20, bottom: 40, top: 70 },
xAxis: {
type: 'category',
data: labels,
axisLabel: { interval: 0, rotate: 20 }
},
yAxis: { type: 'value' },
series: [
{
type: 'bar',
name: '通过',
data: passData,
itemStyle: { color: '#67C23A' }
},
{
type: 'bar',
name: '驳回',
data: rejectData,
itemStyle: { color: '#F56C6C' }
},
]
})
}
响应式处理:
function handleResize() {
chart?.resize()
}
onMounted(() => {
render()
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
chart?.dispose()
chart = null
})
7. 本地数据存储:IndexedDB 封装
7.1 为什么使用 IndexedDB?
| 方案 | 容量 | 性能 | 适用场景 |
|---|---|---|---|
| localStorage | 5-10MB | 同步,阻塞 | 小数据 ✅ |
| IndexedDB | 无限制 | 异步,非阻塞 | 大数据、离线应用 |
| WebSQL | 5-50MB | 已废弃 | 不推荐 |
本项目使用 localStorage 模拟数据库,实际生产环境应使用 IndexedDB。
7.2 数据表封装
// apps/client/src/database/user/users.ts
class UserTable {
private storageKey = 'MOCK_DB_USERS'
init() {
if (!this.getAll().length) {
this.seed() // 初始化种子数据
}
}
getAll(): UserRecord[] {
const data = localStorage.getItem(this.storageKey)
return data ? JSON.parse(data) : []
}
findById(id: number): UserRecord | null {
const users = this.getAll()
return users.find(user => user.id === id) || null
}
create(user: Partial<UserRecord>): UserRecord {
const users = this.getAll()
const newId = users.length > 0
? Math.max(...users.map(u => u.id)) + 1
: 1
const newUser: UserRecord = {
...user,
id: newId,
createdAt: Date.now(),
updatedAt: Date.now(),
}
users.push(newUser)
this.save(users)
return newUser
}
private save(users: UserRecord[]) {
localStorage.setItem(this.storageKey, JSON.stringify(users))
}
}
export const userTable = new UserTable()
🎓 技术栈八股延伸
Vue 3 核心原理
1. 响应式原理(Proxy)
// Vue 3 使用 Proxy 实现响应式
const reactive = (target: object) => {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key) // 依赖收集
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key) // 触发更新
return result
}
})
}
2. Composition API vs Options API
| 特性 | Options API | Composition API |
|---|---|---|
| 逻辑组织 | 按选项分组 | 按功能分组 |
| 逻辑复用 | Mixins(有缺陷) | Composables(推荐) |
| 类型推导 | 较差 | 优秀 |
| Tree-shaking | 较差 | 优秀 |
3. 生命周期对比
// Options API
export default {
mounted() {},
beforeUnmount() {}
}
// Composition API
import { onMounted, onBeforeUnmount } from 'vue'
onMounted(() => {})
onBeforeUnmount(() => {})
Pinia vs Vuex
| 特性 | Vuex | Pinia |
|---|---|---|
| API 设计 | 复杂(mutations/actions) | 简洁(actions) |
| TypeScript | 需要额外配置 | 原生支持 |
| DevTools | 支持 | 支持 |
| 代码分割 | 需要模块化 | 自动支持 |
| 体积 | 较大 | 更小 |
Vite 构建原理
1. 开发模式:ESM + HMR
// Vite 开发服务器直接返回 ESM
// 浏览器原生支持,无需打包
import { createApp } from 'vue'
2. 生产构建:Rollup
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'element-plus': ['element-plus'],
}
}
}
}
})
3. 依赖预构建
Vite 使用 esbuild 预构建依赖,提升开发体验:
// vite.config.ts
export default defineConfig({
optimizeDeps: {
include: ['vue', 'vue-router', 'pinia']
}
})
🚀 优化方向
1. 性能优化
代码分割:
// 路由懒加载
const User = () => import('@/views/user/User.vue')
// 组件懒加载
const HeavyComponent = defineAsyncComponent(() =>
import('@/components/HeavyComponent.vue')
)
虚拟滚动:
<template>
<el-table-v2
:columns="columns"
:data="tableData"
:width="700"
:height="400"
/>
</template>
图片懒加载:
<img
v-lazy="imageUrl"
alt="description"
/>
2. 用户体验优化
骨架屏:
<template>
<el-skeleton
v-if="loading"
:rows="5"
animated
/>
<div v-else>
<!-- 实际内容 -->
</div>
</template>
防抖/节流:
import { debounce, throttle } from 'lodash-es'
const handleSearch = debounce((keyword: string) => {
// 搜索逻辑
}, 300)
错误边界:
<template>
<ErrorBoundary>
<component :is="currentComponent" />
</ErrorBoundary>
</template>
3. 安全性优化
XSS 防护:
import DOMPurify from 'dompurify'
const sanitize = (html: string) => {
return DOMPurify.sanitize(html)
}
CSRF 防护:
// 在请求拦截器中添加 CSRF Token
config.headers['X-CSRF-Token'] = getCsrfToken()
4. 可维护性优化
类型定义:
// 使用 TypeScript 严格模式
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true
}
}
代码规范:
// ESLint + Prettier
// .eslintrc.js
module.exports = {
extends: [
'plugin:vue/vue3-recommended',
'@vue/typescript/recommended'
]
}
📚 准备建议
1. 基础知识准备
Vue 3 核心概念:
- 响应式原理(Proxy)
- Composition API
- 生命周期
- 组件通信
TypeScript:
- 类型系统
- 泛型
- 工具类型
- 类型推导
构建工具:
- Vite 原理
- Rollup 配置
- 代码分割
2. 项目实践
推荐学习路径:
- 从简单项目开始(Todo List)
- 逐步增加复杂度(权限系统)
- 学习 Monorepo 架构
- 实践性能优化
开源项目参考:
- VueUse - Vue 组合式函数库
- Vben Admin - Vue 3 后台模板
- Naive UI - Vue 3 组件库
3. 面试准备
常见问题:
- Vue 3 响应式原理?
- Composition API 的优势?
- 如何实现动态路由?
- Pinia 和 Vuex 的区别?
- Vite 为什么快?
项目亮点总结:
- 动态路由系统
- 权限控制方案
- Monorepo 架构设计
- 性能优化实践
📸 项目截图
🎯 总结
本文深入探讨了企业级流程协作平台的核心技术实现,包括:
- 动态路由系统:基于权限的路由加载,实现真正的权限隔离
- Token 认证:通过依赖注入解决 Monorepo 架构中的循环依赖问题
- 可视化流程设计器:LogicFlow 深度集成,Composable 模式封装
- 权限控制:RBAC 实现,支持路由级和按钮级权限
- Monorepo 架构:pnpm workspace 管理,代码共享和依赖统一
这些技术方案不仅解决了实际业务问题,也为前端工程化提供了最佳实践参考。希望本文能帮助读者深入理解 Vue 3 生态下的企业级应用开发。
项目地址:GitHub
作者:heidiug/春酲
发布时间:2025-11-17