从零到一:构建企业级流程协作平台的技术实践

76 阅读9分钟

从零到一:构建企业级流程协作平台的技术实践

本文基于一个真实的企业级流程协作平台项目,深入探讨 Vue 3 + TypeScript + Monorepo 架构下的核心技术实现,包括动态路由、权限控制、可视化流程设计器等核心功能的底层原理与最佳实践。

📖 项目背景

Enterprise Flow Platform 是一个基于 Vue 3 的企业级流程协作平台,提供流程设计、任务审批、数据看板、用户管理等核心功能。项目采用 Monorepo 架构,使用 pnpm workspace 管理多包项目,实现了完整的 RBAC 权限控制和动态路由系统。

EE41735F2E0A3D15CF0923B719011ED7.png

🎯 核心技术架构

技术栈选型

技术版本选型原因
Vue 33.5+Composition API 提供更好的逻辑复用和类型推导
TypeScript5.0+类型安全,提升代码可维护性
Vite7+极速的 HMR 和构建速度
Pinia3.0+Vue 官方推荐,比 Vuex 更简洁
LogicFlow2.1+专业的流程图编辑框架
ECharts6.0+强大的数据可视化能力
pnpm8+高效的包管理,支持 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() 的实现原理

  1. 路由注册:Vue Router 内部维护一个路由映射表(matcher
  2. 动态添加addRoute() 会更新这个映射表,添加新的路由规则
  3. 匹配算法:使用路径匹配算法(基于正则表达式)查找匹配的路由
  4. 组件加载:通过动态 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 FlowReact 生态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?
方案容量性能适用场景
localStorage5-10MB同步,阻塞小数据 ✅
IndexedDB无限制异步,非阻塞大数据、离线应用
WebSQL5-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 APIComposition API
逻辑组织按选项分组按功能分组
逻辑复用Mixins(有缺陷)Composables(推荐)
类型推导较差优秀
Tree-shaking较差优秀

3. 生命周期对比

// Options API
export default {
  mounted() {},
  beforeUnmount() {}
}

// Composition API
import { onMounted, onBeforeUnmount } from 'vue'

onMounted(() => {})
onBeforeUnmount(() => {})

Pinia vs Vuex

特性VuexPinia
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. 项目实践

推荐学习路径

  1. 从简单项目开始(Todo List)
  2. 逐步增加复杂度(权限系统)
  3. 学习 Monorepo 架构
  4. 实践性能优化

开源项目参考

3. 面试准备

常见问题

  1. Vue 3 响应式原理?
  2. Composition API 的优势?
  3. 如何实现动态路由?
  4. Pinia 和 Vuex 的区别?
  5. Vite 为什么快?

项目亮点总结

  • 动态路由系统
  • 权限控制方案
  • Monorepo 架构设计
  • 性能优化实践

📸 项目截图

15B1D94D76F78AA62FCC26E78FD9B1C7.png

D9324D2A0D95082941211A39449566EE.png

64E95E7F751A26BE7FD114E46ACA9214.png

F328D78308D207A2B3530DE09771BC51.png

F15CA63E2D0E8FD4B0174C788243D752.png

E9A60308B65453F16BC996C67F8F4468.png

🎯 总结

本文深入探讨了企业级流程协作平台的核心技术实现,包括:

  1. 动态路由系统:基于权限的路由加载,实现真正的权限隔离
  2. Token 认证:通过依赖注入解决 Monorepo 架构中的循环依赖问题
  3. 可视化流程设计器:LogicFlow 深度集成,Composable 模式封装
  4. 权限控制:RBAC 实现,支持路由级和按钮级权限
  5. Monorepo 架构:pnpm workspace 管理,代码共享和依赖统一

这些技术方案不仅解决了实际业务问题,也为前端工程化提供了最佳实践参考。希望本文能帮助读者深入理解 Vue 3 生态下的企业级应用开发。


项目地址GitHub

作者:heidiug/春酲

发布时间:2025-11-17