Skip to content

前端技术架构设计

文档编号: SYS-DES-ARCH-FRONTEND-001
版本: 1.0
创建日期: 2026-03-08
作者: 架构师
状态: 🔄 进行中


1. 概述

1.1 目的

本文档定义System平台前端技术架构设计,包括技术选型、项目结构、组件设计、状态管理、路由设计等。

1.2 适用范围

  • Web管理端(Vue3 + Element Plus)
  • 移动端(可选,暂不实现)
  • 第三方集成(iframe嵌入)

1.3 设计原则

  1. 组件化:高内聚、低耦合的组件设计
  2. 可复用性:通用组件和业务组件分离
  3. 可维护性:清晰的代码结构和规范
  4. 性能优化:懒加载、缓存、按需加载

2. 技术选型

2.1 核心技术栈

技术版本用途选择理由
Vue 33.4.x前端框架组合式API,性能优化,TypeScript支持
TypeScript5.x类型系统类型安全,IDE支持,可维护性
Vite5.x构建工具快速冷启动,HMR,ESM原生支持
Element Plus2.5.xUI组件库企业级组件,Vue3适配,文档完善
Pinia2.1.x状态管理Vue官方推荐,TypeScript友好
Vue Router4.x路由管理Vue3官方路由,组合式API支持
Axios1.6.xHTTP客户端拦截器,请求/响应处理
ECharts5.x图表库数据可视化,丰富图表类型

2.2 开发工具

工具版本用途
ESLint8.x代码规范检查
Prettier3.x代码格式化
Stylelint15.xCSS规范检查
Husky9.xGit钩子管理
Vitest1.x单元测试
Cypress13.xE2E测试

2.3 技术栈架构图

┌─────────────────────────────────────────────────────────────┐
│                      前端技术栈架构                          │
├─────────────────────────────────────────────────────────────┤
│  展示层                                                      │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │   Element   │ │   ECharts   │ │  自定义组件  │           │
│  │    Plus     │ │             │ │             │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
├─────────────────────────────────────────────────────────────┤
│  应用层                                                      │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │  Vue Router │ │    Pinia    │ │   Composables│          │
│  │   (路由)    │ │  (状态管理)  │ │  (组合函数)  │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
├─────────────────────────────────────────────────────────────┤
│  基础层                                                      │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │    Vue 3    │ │ TypeScript  │ │    Axios    │           │
│  │  (核心框架)  │ │  (类型系统)  │ │  (HTTP请求)  │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
├─────────────────────────────────────────────────────────────┤
│  工程化                                                      │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │
│  │    Vite     │ │  ESLint/    │ │  Vitest/    │           │
│  │  (构建工具)  │ │  Prettier   │ │  Cypress    │           │
│  └─────────────┘ └─────────────┘ └─────────────┘           │
└─────────────────────────────────────────────────────────────┘

3. 项目结构

3.1 目录结构

linsir-vue-admin/                    # 前端Monorepo根目录
├── packages/
│   └── @linsir/                     # 组织命名空间
│       ├── components/              # 通用组件库(项目依赖)
│       │   ├── src/
│       │   │   ├── AppTable/        # 表格组件
│       │   │   ├── AppForm/         # 表单组件
│       │   │   ├── AppSearch/       # 搜索组件
│       │   │   ├── AppDialog/       # 对话框组件
│       │   │   ├── AppTree/         # 树形组件
│       │   │   └── index.ts         # 组件库入口
│       │   ├── package.json
│       │   └── vite.config.ts
│       │
│       ├── composables/             # 通用组合函数库(项目依赖)
│       │   ├── src/
│       │   │   ├── useTable.ts      # 表格逻辑
│       │   │   ├── useForm.ts       # 表单逻辑
│       │   │   ├── useAuth.ts       # 权限逻辑
│       │   │   ├── useDict.ts       # 字典逻辑
│       │   │   └── index.ts         # 组合函数入口
│       │   ├── package.json
│       │   └── tsconfig.json
│       │
│       ├── directives/              # 通用指令库(项目依赖)
│       │   ├── src/
│       │   │   ├── permission.ts    # 权限指令
│       │   │   ├── debounce.ts      # 防抖指令
│       │   │   └── index.ts         # 指令入口
│       │   ├── package.json
│       │   └── tsconfig.json
│       │
│       └── utils/                   # 通用工具库(项目依赖)
│           ├── src/
│           │   ├── request.ts       # Axios封装
│           │   ├── storage.ts       # 存储封装
│           │   ├── validate.ts      # 验证工具
│           │   ├── format.ts        # 格式化工具
│           │   └── index.ts         # 工具入口
│           ├── package.json
│           └── tsconfig.json

├── apps/
│   └── linsir-web-system/           # 具体业务项目
│       ├── public/                  # 静态资源
│       │   ├── favicon.ico
│       │   └── logo.png
│       ├── src/
│       │   ├── api/                 # API接口
│       │   │   ├── system/          # System服务API
│       │   │   │   ├── user.ts
│       │   │   │   ├── role.ts
│       │   │   │   ├── dept.ts
│       │   │   │   └── auth.ts
│       │   │   ├── config/
│       │   │   └── audit/
│       │   │
│       │   ├── assets/              # 静态资源
│       │   │   ├── images/
│       │   │   ├── icons/
│       │   │   └── styles/
│       │   │       ├── variables.scss
│       │   │       ├── mixins.scss
│       │   │       └── index.scss
│       │   │
│       │   ├── components/          # 业务组件(仅本项目使用)
│       │   │   ├── UserSelect/      # 用户选择器
│       │   │   ├── DeptSelect/      # 部门选择器
│       │   │   └── RoleSelect/      # 角色选择器
│       │   │
│       │   ├── layouts/             # 布局组件
│       │   │   ├── default/
│       │   │   │   ├── index.vue
│       │   │   │   ├── components/
│       │   │   │   │   ├── Navbar.vue
│       │   │   │   │   ├── Sidebar.vue
│       │   │   │   │   ├── TagsView.vue
│       │   │   │   │   └── AppMain.vue
│       │   │   │   └── index.scss
│       │   │   └── blank/
│       │   │       └── index.vue
│       │   │
│       │   ├── router/              # 路由配置
│       │   │   ├── index.ts
│       │   │   ├── routes.ts
│       │   │   └── guards.ts
│       │   │
│       │   ├── stores/              # Pinia状态管理
│       │   │   ├── index.ts
│       │   │   ├── modules/
│       │   │   │   ├── user.ts
│       │   │   │   ├── app.ts
│       │   │   │   ├── permission.ts
│       │   │   │   └── tagsView.ts
│       │   │   └── plugins/
│       │   │       └── persist.ts
│       │   │
│       │   ├── types/               # TypeScript类型
│       │   │   ├── api.d.ts
│       │   │   ├── global.d.ts
│       │   │   └── components.d.ts
│       │   │
│       │   ├── views/               # 页面视图
│       │   │   ├── login/
│       │   │   ├── dashboard/
│       │   │   ├── system/
│       │   │   │   ├── user/
│       │   │   │   ├── role/
│       │   │   │   ├── dept/
│       │   │   │   └── menu/
│       │   │   ├── config/
│       │   │   └── audit/
│       │   │
│       │   ├── App.vue
│       │   ├── main.ts
│       │   └── env.d.ts
│       │
│       ├── tests/
│       ├── .env
│       ├── package.json
│       ├── tsconfig.json
│       └── vite.config.ts

├── package.json                     # 根package.json (pnpm workspace)
├── pnpm-workspace.yaml              # pnpm工作区配置
├── tsconfig.json
└── README.md

3.2 目录说明

3.2.1 公共库 (packages/@linsir/)

目录说明规范
components/通用组件库所有项目共享,独立发布
composables/通用组合函数库逻辑复用,命名useXxx
directives/通用指令库权限、防抖等通用指令
utils/通用工具库请求、存储、验证等工具

3.2.2 业务项目 (apps/linsir-web-system/)

目录说明规范
api/API接口定义按服务划分,统一错误处理
components/业务组件仅本项目使用的业务组件
layouts/布局组件支持多布局切换
router/路由配置动态路由,权限控制
stores/状态管理按模块划分,持久化配置
views/页面视图按功能模块划分

3.3 依赖关系

┌─────────────────────────────────────────────────────────────┐
│                      依赖关系图                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  apps/linsir-web-system/                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  package.json                                       │   │
│  │  {                                                  │   │
│  │    "dependencies": {                                │   │
│  │      "@linsir/components": "workspace:*",           │   │
│  │      "@linsir/composables": "workspace:*",          │   │
│  │      "@linsir/directives": "workspace:*",           │   │
│  │      "@linsir/utils": "workspace:*"                 │   │
│  │    }                                                │   │
│  │  }                                                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                            │                                │
│           ┌────────────────┼────────────────┐               │
│           ▼                ▼                ▼               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │ @linsir/    │  │ @linsir/    │  │ @linsir/    │         │
│  │ components  │  │ composables │  │ directives  │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.4 使用示例

typescript
// apps/linsir-web-system/src/main.ts
import { createApp } from 'vue'
import App from './App.vue'

// 引入公共库
import { AppTable, AppForm, AppDialog } from '@linsir/components'
import { useTable, useForm } from '@linsir/composables'
import { permission, debounce } from '@linsir/directives'
import { request, storage } from '@linsir/utils'

const app = createApp(App)

// 注册公共组件
app.component('AppTable', AppTable)
app.component('AppForm', AppForm)
app.component('AppDialog', AppDialog)

// 注册公共指令
app.directive('permission', permission)
app.directive('debounce', debounce)

app.mount('#app')

4. 组件设计

4.1 组件分类

┌─────────────────────────────────────────────────────────────┐
│                        组件体系                              │
├─────────────────────────────────────────────────────────────┤
│  基础组件 (Element Plus)                                     │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐           │
│  │ Button  │ │  Input  │ │ Select  │ │  Table  │           │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘           │
├─────────────────────────────────────────────────────────────┤
│  通用组件 (Common)                                           │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐           │
│  │AppTable │ │ AppForm │ │AppSearch│ │AppDialog│           │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘           │
├─────────────────────────────────────────────────────────────┤
│  业务组件 (Business)                                         │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐                       │
│  │UserSel  │ │DeptSel  │ │RoleSel  │                       │
│  └─────────┘ └─────────┘ └─────────┘                       │
├─────────────────────────────────────────────────────────────┤
│  布局组件 (Layout)                                           │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐           │
│  │ Navbar  │ │ Sidebar │ │TagsView │ │ AppMain │           │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘           │
└─────────────────────────────────────────────────────────────┘

4.2 通用组件规范

AppTable 表格组件

vue
<template>
  <AppTable
    :data="tableData"
    :columns="columns"
    :loading="loading"
    :pagination="pagination"
    @page-change="handlePageChange"
    @selection-change="handleSelectionChange"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import type { TableColumn } from '@/components/common/AppTable/types'

const columns: TableColumn[] = [
  { type: 'selection', width: 50 },
  { prop: 'username', label: '用户名', width: 120 },
  { prop: 'nickname', label: '昵称' },
  { prop: 'status', label: '状态', slot: 'status' },
  { prop: 'createTime', label: '创建时间', width: 180 },
  { type: 'action', label: '操作', width: 200, fixed: 'right' }
]
</script>

AppForm 表单组件

vue
<template>
  <AppForm
    :model="formModel"
    :fields="formFields"
    :rules="formRules"
    @submit="handleSubmit"
  />
</template>

<script setup lang="ts">
const formFields = [
  { type: 'input', prop: 'username', label: '用户名' },
  { type: 'input', prop: 'password', label: '密码', props: { type: 'password' } },
  { type: 'select', prop: 'status', label: '状态', options: statusOptions },
  { type: 'tree-select', prop: 'deptId', label: '部门', data: deptTree }
]
</script>

4.3 组件开发规范

规范项说明示例
命名PascalCaseUserSelect.vue
文件目录+index.vueUserSelect/index.vue
Props显式类型定义defineProps<{ name: string }>()
Emits显式类型定义defineEmits<{ submit: [data: FormData] }>()
样式Scoped + BEM.user-select__header

5. 状态管理

5.1 Pinia Store设计

┌─────────────────────────────────────────────────────────────┐
│                      Pinia Store架构                         │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    Store入口                          │   │
│  │                    stores/index.ts                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                            │                                │
│           ┌────────────────┼────────────────┐               │
│           ▼                ▼                ▼               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │  user.ts    │  │  app.ts     │  │permission.ts│         │
│  │  用户状态   │  │  应用状态   │  │  权限状态   │         │
│  │             │  │             │  │             │         │
│  │ • userInfo  │  │ • sidebar   │  │ • routes    │         │
│  │ • token     │  │ • device    │  │ • buttons   │         │
│  │ • roles     │  │ • language  │  │ • roles     │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
└─────────────────────────────────────────────────────────────┘

5.2 Store示例

User Store

typescript
// stores/modules/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { UserInfo } from '@/types/api'
import { login, getUserInfo } from '@/api/system/auth'

export const useUserStore = defineStore('user', () => {
  // State
  const token = ref<string>('')
  const userInfo = ref<UserInfo | null>(null)
  
  // Getters
  const isLoggedIn = computed(() => !!token.value)
  const username = computed(() => userInfo.value?.username || '')
  
  // Actions
  const loginAction = async (loginForm: LoginForm) => {
    const res = await login(loginForm)
    token.value = res.token
    return res
  }
  
  const getInfo = async () => {
    const res = await getUserInfo()
    userInfo.value = res
    return res
  }
  
  const logout = () => {
    token.value = ''
    userInfo.value = null
  }
  
  return {
    token,
    userInfo,
    isLoggedIn,
    username,
    loginAction,
    getInfo,
    logout
  }
}, {
  persist: {
    key: 'user',
    paths: ['token']
  }
})

5.3 状态持久化

typescript
// stores/plugins/persist.ts
import type { PiniaPluginContext } from 'pinia'

export function persistPlugin({ store }: PiniaPluginContext) {
  const persistKey = `pinia-${store.$id}`
  
  // 恢复状态
  const savedState = localStorage.getItem(persistKey)
  if (savedState) {
    store.$patch(JSON.parse(savedState))
  }
  
  // 监听变化
  store.$subscribe((mutation, state) => {
    localStorage.setItem(persistKey, JSON.stringify(state))
  })
}

6. 路由设计

6.1 路由结构

typescript
// router/routes.ts
import type { RouteRecordRaw } from 'vue-router'

export const constantRoutes: RouteRecordRaw[] = [
  {
    path: '/login',
    component: () => import('@/views/login/index.vue'),
    meta: { hidden: true }
  },
  {
    path: '/',
    component: () => import('@/layouts/default/index.vue'),
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index.vue'),
        meta: { title: '首页', icon: 'dashboard' }
      }
    ]
  }
]

export const asyncRoutes: RouteRecordRaw[] = [
  {
    path: '/system',
    component: Layout,
    redirect: '/system/user',
    meta: { title: '系统管理', icon: 'system' },
    children: [
      {
        path: 'user',
        component: () => import('@/views/system/user/index.vue'),
        meta: { title: '用户管理', permission: 'system:user:list' }
      },
      {
        path: 'role',
        component: () => import('@/views/system/role/index.vue'),
        meta: { title: '角色管理', permission: 'system:role:list' }
      },
      {
        path: 'dept',
        component: () => import('@/views/system/dept/index.vue'),
        meta: { title: '部门管理', permission: 'system:dept:list' }
      },
      {
        path: 'menu',
        component: () => import('@/views/system/menu/index.vue'),
        meta: { title: '菜单管理', permission: 'system:menu:list' }
      }
    ]
  }
]

6.2 路由守卫

typescript
// router/guards.ts
import type { Router } from 'vue-router'
import { useUserStore } from '@/stores/modules/user'
import { usePermissionStore } from '@/stores/modules/permission'

export function setupRouterGuards(router: Router) {
  // 白名单
  const whiteList = ['/login', '/404']
  
  router.beforeEach(async (to, from, next) => {
    const userStore = useUserStore()
    const permissionStore = usePermissionStore()
    
    // 设置页面标题
    document.title = to.meta.title ? `${to.meta.title} - System平台` : 'System平台'
    
    if (userStore.token) {
      if (to.path === '/login') {
        next('/')
      } else {
        // 获取用户信息和权限
        if (!userStore.userInfo) {
          await userStore.getInfo()
          const routes = await permissionStore.generateRoutes()
          routes.forEach(route => router.addRoute(route))
          next({ ...to, replace: true })
        } else {
          next()
        }
      }
    } else {
      if (whiteList.includes(to.path)) {
        next()
      } else {
        next(`/login?redirect=${to.path}`)
      }
    }
  })
}

7. HTTP请求封装

7.1 Axios封装

typescript
// utils/request.ts
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useUserStore } from '@/stores/modules/user'

const request: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
})

// 请求拦截器
request.interceptors.request.use(
  (config) => {
    const userStore = useUserStore()
    if (userStore.token) {
      config.headers.Authorization = `Bearer ${userStore.token}`
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
request.interceptors.response.use(
  (response) => {
    const { code, message, data } = response.data
    
    if (code === 200) {
      return data
    }
    
    // 业务错误
    ElMessage.error(message || '请求失败')
    return Promise.reject(new Error(message))
  },
  (error: AxiosError) => {
    const { response } = error
    
    if (response) {
      switch (response.status) {
        case 401:
          ElMessageBox.confirm('登录已过期,请重新登录', '提示', {
            confirmButtonText: '重新登录',
            type: 'warning'
          }).then(() => {
            const userStore = useUserStore()
            userStore.logout()
            location.reload()
          })
          break
        case 403:
          ElMessage.error('没有权限访问')
          break
        case 404:
          ElMessage.error('请求的资源不存在')
          break
        case 500:
          ElMessage.error('服务器错误')
          break
        default:
          ElMessage.error(response.data?.message || '网络错误')
      }
    }
    
    return Promise.reject(error)
  }
)

export default request

7.2 API定义示例

typescript
// api/system/user.ts
import request from '@/utils/request'
import type { User, UserQuery, UserForm } from '@/types/api'

export const getUserList = (params: UserQuery) => {
  return request.get<PageResult<User>>('/system/user/list', { params })
}

export const getUserById = (id: number) => {
  return request.get<User>(`/system/user/${id}`)
}

export const createUser = (data: UserForm) => {
  return request.post('/system/user', data)
}

export const updateUser = (id: number, data: UserForm) => {
  return request.put(`/system/user/${id}`, data)
}

export const deleteUser = (id: number) => {
  return request.delete(`/system/user/${id}`)
}

export const exportUser = (params: UserQuery) => {
  return request.get('/system/user/export', { 
    params,
    responseType: 'blob'
  })
}

8. 权限控制

8.1 权限指令

typescript
// directives/permission.ts
import type { Directive } from 'vue'
import { useUserStore } from '@/stores/modules/user'

export const permission: Directive = {
  mounted(el, binding) {
    const { value } = binding
    const userStore = useUserStore()
    const permissions = userStore.userInfo?.permissions || []
    
    if (value && !permissions.includes(value)) {
      el.parentNode?.removeChild(el)
    }
  }
}

// 使用
// <el-button v-permission="'system:user:create'">新增</el-button>

8.2 权限判断

typescript
// composables/useAuth.ts
import { computed } from 'vue'
import { useUserStore } from '@/stores/modules/user'

export function useAuth() {
  const userStore = useUserStore()
  
  const hasPermission = (permission: string) => {
    const permissions = userStore.userInfo?.permissions || []
    return permissions.includes(permission)
  }
  
  const hasRole = (role: string) => {
    const roles = userStore.userInfo?.roles || []
    return roles.includes(role)
  }
  
  const hasAnyPermission = (permissions: string[]) => {
    return permissions.some(p => hasPermission(p))
  }
  
  return {
    hasPermission,
    hasRole,
    hasAnyPermission
  }
}

9. 性能优化

9.1 优化策略

优化项策略实现
路由懒加载按需加载页面组件() => import('@/views/xxx')
组件懒加载按需加载大型组件defineAsyncComponent
资源优化图片压缩、CDN加速Vite插件
缓存优化接口缓存、状态持久化Pinia持久化
代码分割按路由分割代码Vite自动处理

9.2 性能监控

typescript
// utils/performance.ts
export function measurePerformance() {
  // 页面加载时间
  window.addEventListener('load', () => {
    const timing = performance.timing
    const pageLoadTime = timing.loadEventEnd - timing.navigationStart
    console.log(`页面加载时间: ${pageLoadTime}ms`)
  })
  
  // 资源加载时间
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (entry.entryType === 'resource') {
        console.log(`${entry.name}: ${entry.duration}ms`)
      }
    }
  })
  observer.observe({ entryTypes: ['resource'] })
}

10. 开发规范

10.1 代码规范

  • ESLint: 使用@antfu/eslint-config配置
  • Prettier: 统一代码格式
  • Stylelint: CSS/SCSS规范检查
  • Commitizen: 规范提交信息

10.2 命名规范

类型规范示例
组件PascalCaseUserSelect.vue
组合函数camelCase + use前缀useTable.ts
StorecamelCase + use前缀useUserStore.ts
类型PascalCaseUserInfo
常量UPPER_SNAKE_CASEAPI_BASE_URL

10.3 文件组织

src/
├── api/           # API接口
├── assets/        # 静态资源
├── components/    # 组件
│   ├── common/    # 通用组件
│   └── business/  # 业务组件
├── composables/   # 组合函数
├── directives/    # 自定义指令
├── layouts/       # 布局组件
├── router/        # 路由配置
├── stores/        # 状态管理
├── types/         # TypeScript类型
├── utils/         # 工具函数
└── views/         # 页面视图

11. 相关文档


12. 修订记录

版本日期作者变更内容
1.02026-03-08架构师初始版本,定义前端技术架构

Released under the MIT License.