前端架构概览
介绍
RuoYi-Plus-UniApp 前端管理端(plus-ui)采用现代化的前端架构设计,基于 Vue 3 生态系统构建,结合 TypeScript 提供完整的类型安全保障。项目遵循模块化、组件化的设计理念,通过清晰的分层架构和统一的开发规范,为企业级应用提供稳定、高效、可维护的技术解决方案。
架构核心理念:
- 渐进式增强 - 采用 Vue 3 渐进式框架,支持按需引入和灵活扩展
- 类型安全 - 全面使用 TypeScript,提供完整的类型推导和检查
- 模块化设计 - 按功能模块组织代码,高内聚低耦合
- 组件化开发 - 可复用的组件体系,提高开发效率
- 工程化规范 - 统一的代码规范、构建流程和部署策略
- 性能优先 - 代码分割、懒加载、缓存优化等性能最佳实践
- 开发友好 - 热更新、类型提示、代码提示等优秀的开发体验
技术栈
核心框架
Vue 3.5.13
Vue 3 是下一代渐进式 JavaScript 框架,提供了更小的包体积、更快的渲染速度和更好的 TypeScript 支持。
核心特性:
- Composition API - 更灵活的逻辑组织方式,替代 Options API
- 响应式系统 - 基于 Proxy 的响应式系统,性能更优
- Teleport - 组件渲染到 DOM 树的任意位置
- Suspense - 异步组件加载的优雅处理
- 多根节点组件 - 支持模板中多个根节点
- Fragment - 无需额外包裹元素
使用示例:
<template>
<div class="user-list">
<h1>{{ title }}</h1>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'
import { listUser } from '@/api/system/user/userApi'
import type { UserVo } from '@/api/system/user/userTypes'
// 响应式数据
const title = ref('用户列表')
const users = ref<UserVo[]>([])
const loading = ref(false)
// 计算属性
const userCount = computed(() => users.value.length)
// 生命周期钩子
onMounted(async () => {
loading.value = true
const [err, data] = await listUser({ pageNum: 1, pageSize: 10 })
if (!err) {
users.value = data.records
}
loading.value = false
})
</script>TypeScript 5.8.3
TypeScript 是 JavaScript 的超集,为项目提供静态类型检查和强大的 IDE 支持。
类型系统优势:
- 编译时类型检查 - 在开发阶段发现潜在错误
- 智能代码提示 - IDE 提供精准的代码补全
- 重构支持 - 安全地重构代码,自动更新引用
- 接口定义 - 清晰的 API 接口类型定义
- 泛型支持 - 灵活的类型复用和约束
类型定义示例:
// 用户查询对象
export interface UserQuery {
pageNum: number
pageSize: number
userName?: string
phone?: string
status?: string
deptId?: number
beginTime?: string
endTime?: string
}
// 用户视图对象
export interface UserVo {
userId: number
userName: string
nickName: string
email: string
phone: string
sex: string
avatar: string
status: string
deptId: number
deptName: string
createTime: string
}
// API 响应结果
export type Result<T = any> = Promise<[Error | null, T | null]>
// 分页结果
export interface PageResult<T = any> {
records: T[]
total: number
pages: number
current: number
size: number
}Vite 6.3.2
Vite 是新一代前端构建工具,基于原生 ES 模块提供极速的开发体验。
核心优势:
- 极速冷启动 - 基于 ESM 的 Dev Server,无需打包即可启动
- 即时热更新 - 无论应用大小,HMR 始终快速
- 按需编译 - 只编译当前屏幕上实际导入的代码
- 优化构建 - 内置 Rollup 打包,生产构建高度优化
- 丰富的插件 - 兼容 Rollup 插件,生态丰富
- TypeScript 支持 - 开箱即用的 TypeScript 支持
Vite 配置核心:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
// 环境变量目录
envDir: './env',
// 基础路径
base: '/',
// 路径别名
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
// 插件
plugins: [vue()],
// 开发服务器
server: {
host: '0.0.0.0',
port: 80,
open: true,
proxy: {
'/dev-api': {
target: 'http://localhost:8080',
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(/^\/dev-api/, '')
}
}
},
// CSS 配置
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
// 依赖优化
optimizeDeps: {
include: [
'vue',
'vue-router',
'pinia',
'axios',
'@vueuse/core',
'echarts',
'element-plus'
]
}
})Vue Router 4.5.0
Vue Router 是 Vue.js 官方的路由管理器,提供声明式路由和编程式导航。
核心功能:
- 嵌套路由 - 支持多层嵌套的路由结构
- 路由参数 - 动态路由参数匹配
- 命名路由 - 通过名称引用路由
- 命名视图 - 同级展示多个视图
- 重定向和别名 - 灵活的路由映射
- 路由守卫 - 全局、路由独享、组件内守卫
- 路由元信息 - 自定义路由元数据
- 滚动行为 - 自定义路由切换时的滚动行为
路由配置示例:
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
// 静态路由
export const constantRoutes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('@/layouts/Layout.vue'),
redirect: '/index',
children: [
{
path: 'index',
name: 'Index',
component: () => import('@/views/index/index.vue'),
meta: { title: '首页', icon: 'dashboard', affix: true }
}
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/common/login.vue'),
meta: { title: '登录', hidden: true }
}
]
// 创建路由实例
const router = createRouter({
history: createWebHistory(),
routes: constantRoutes,
scrollBehavior: () => ({ left: 0, top: 0 })
})
export default router路由守卫实现:
import NProgress from 'nprogress'
import { useUserStore } from '@/stores/modules/user'
import { usePermissionStore } from '@/stores/modules/permission'
// 白名单 - 无需登录即可访问
const WHITE_LIST = ['/login', '/register', '/401', '/403', '/404']
// 路由前置守卫
router.beforeEach(async (to, from, next) => {
// 开始进度条
NProgress.start()
const userStore = useUserStore()
const permissionStore = usePermissionStore()
// 检查登录状态
if (!userStore.token) {
// 白名单直接通过
if (WHITE_LIST.includes(to.path)) {
return next()
}
// 重定向到登录页
return next(`/login?redirect=${to.fullPath}`)
}
// 已登录访问登录页,重定向到首页
if (to.path === '/login') {
return next({ path: '/' })
}
// 检查是否已获取用户信息
if (userStore.roles.length === 0) {
// 获取用户信息
await userStore.fetchUserInfo()
// 生成动态路由
const accessRoutes = await permissionStore.generateRoutes()
// 添加动态路由
accessRoutes.forEach(route => {
router.addRoute(route)
})
// 确保动态路由已加载
next({ ...to, replace: true })
} else {
next()
}
})
// 路由后置守卫
router.afterEach((to) => {
// 结束进度条
NProgress.done()
// 设置页面标题
document.title = to.meta.title || '管理系统'
})Pinia 3.0.2
Pinia 是 Vue 3 官方推荐的状态管理库,提供简洁的 API 和完整的 TypeScript 支持。
核心特性:
- 轻量级 - 体积小巧,约 1KB
- DevTools 支持 - 完整的 Vue DevTools 支持
- 模块化 - 每个 Store 都是独立模块
- TypeScript - 完整的 TypeScript 类型推导
- 插件系统 - 支持扩展功能(如持久化)
- 服务端渲染 - 支持 SSR
Store 定义示例:
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { UserInfo } from '@/types/user'
import { getUserInfo, logout } from '@/api/system/user/userApi'
export const useUserStore = defineStore('user', () => {
// 状态
const userInfo = ref<UserInfo | null>(null)
const token = ref<string>('')
const roles = ref<string[]>([])
const permissions = ref<string[]>([])
// 计算属性
const isLogin = computed(() => !!token.value)
const userName = computed(() => userInfo.value?.userName || '')
const userId = computed(() => userInfo.value?.userId || 0)
// 方法
const setToken = (newToken: string) => {
token.value = newToken
localStorage.setItem('token', newToken)
}
const setUserInfo = (info: UserInfo) => {
userInfo.value = info
roles.value = info.roles
permissions.value = info.permissions
}
const fetchUserInfo = async () => {
const [err, data] = await getUserInfo()
if (!err) {
setUserInfo(data)
}
return [err, data]
}
const logoutUser = async () => {
const [err] = await logout()
if (!err) {
userInfo.value = null
token.value = ''
roles.value = []
permissions.value = []
localStorage.removeItem('token')
}
return [err]
}
return {
// 状态
userInfo,
token,
roles,
permissions,
// 计算属性
isLogin,
userName,
userId,
// 方法
setToken,
setUserInfo,
fetchUserInfo,
logoutUser
}
}, {
// 持久化配置
persist: {
key: 'user-store',
storage: localStorage,
paths: ['userInfo', 'token', 'roles', 'permissions']
}
})UI 组件库
Element Plus 2.9.8
Element Plus 是 Vue 3 版本的 Element UI,提供丰富的企业级 UI 组件。
组件分类:
- 基础组件 - Button、Icon、Layout、Container、Border、Link
- 表单组件 - Form、Input、Select、Radio、Checkbox、Switch、DatePicker、Upload
- 数据展示 - Table、Tag、Progress、Tree、Pagination、Badge、Avatar、Card、Carousel
- 通知反馈 - Alert、Message、MessageBox、Notification、Dialog、Drawer、Popconfirm、Tooltip
- 导航菜单 - Menu、Tabs、Breadcrumb、PageHeader、Dropdown、Steps
- 其他 - Dialog、Drawer、Popover、Tooltip、Popconfirm、Calendar、Image、Descriptions
按需引入配置:
// vite/plugins/components.ts
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default function createComponentsPlugin() {
return Components({
resolvers: [ElementPlusResolver()],
dts: 'src/types/components.d.ts'
})
}使用示例:
<template>
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<el-form-item label="用户名" prop="userName">
<el-input v-model="form.userName" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="角色" prop="roleIds">
<el-select v-model="form.roleIds" multiple placeholder="请选择角色">
<el-option
v-for="role in roles"
:key="role.roleId"
:label="role.roleName"
:value="role.roleId"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
const formRef = ref<FormInstance>()
const form = reactive({
userName: '',
email: '',
roleIds: []
})
const rules: FormRules = {
userName: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
]
}
const handleSubmit = () => {
formRef.value?.validate((valid) => {
if (valid) {
console.log('表单提交', form)
}
})
}
const handleReset = () => {
formRef.value?.resetFields()
}
</script>UnoCSS 66.5.2
UnoCSS 是即时原子化 CSS 引擎,提供高性能的原子化 CSS 解决方案。
核心特性:
- 即时生成 - 按需生成 CSS,极快的构建速度
- 完全可定制 - 灵活的配置和扩展
- 预设系统 - 内置多种预设(Tailwind、Windi、Attributify)
- 图标支持 - 集成 Iconify,支持 10万+ 图标
- 变体系统 - 支持响应式、暗黑模式等变体
配置示例:
import { defineConfig, presetAttributify, presetIcons, presetUno } from 'unocss'
export default defineConfig({
// 预设
presets: [
presetUno(), // Tailwind/Windi CSS 预设
presetAttributify(), // 属性化模式
presetIcons({ // 图标预设
scale: 1.2,
warn: true,
collections: {
// 自定义图标集
}
})
],
// 自定义规则
rules: [
['flex-center', { display: 'flex', 'align-items': 'center', 'justify-content': 'center' }],
['flex-between', { display: 'flex', 'align-items': 'center', 'justify-content': 'space-between' }]
],
// 快捷方式
shortcuts: {
'text-ellipsis': 'overflow-hidden text-overflow-ellipsis whitespace-nowrap',
'text-ellipsis-2': 'overflow-hidden line-clamp-2'
},
// 主题
theme: {
colors: {
primary: '#409eff',
success: '#67c23a',
warning: '#e6a23c',
danger: '#f56c6c',
info: '#909399'
}
}
})使用示例:
<template>
<!-- 传统 class 方式 -->
<div class="flex items-center justify-between px-4 py-2 bg-gray-100 rounded">
<span class="text-lg font-bold text-primary">标题</span>
<el-button type="primary" size="small">操作</el-button>
</div>
<!-- 属性化模式 -->
<div flex="~ items-center justify-between" px-4 py-2 bg-gray-100 rounded>
<span text-lg font-bold text-primary>标题</span>
<el-button type="primary" size="small">操作</el-button>
</div>
<!-- 使用快捷方式 -->
<div class="flex-between px-4 py-2">
<span class="text-ellipsis">这是一段很长的文本内容</span>
</div>
<!-- 使用图标 -->
<div class="i-carbon-user text-2xl text-primary" />
<div class="i-carbon-settings text-2xl text-gray-600" />
</template>工具链
ESLint 9.21.0
ESLint 是 JavaScript 代码检查工具,帮助发现和修复代码问题。
配置示例:
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
export default [
eslint.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/recommended'],
{
files: ['**/*.{js,mjs,cjs,ts,vue}'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
'vue/require-default-prop': 'off'
}
}
]Prettier 3.5.2
Prettier 是代码格式化工具,统一代码风格。
配置示例:
module.exports = {
// 每行最大长度
printWidth: 120,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用 tab 缩进
useTabs: false,
// 语句末尾添加分号
semi: false,
// 使用单引号
singleQuote: true,
// 对象属性引号
quoteProps: 'as-needed',
// JSX 使用单引号
jsxSingleQuote: false,
// 尾随逗号
trailingComma: 'none',
// 对象括号空格
bracketSpacing: true,
// 箭头函数参数括号
arrowParens: 'always',
// 换行符
endOfLine: 'lf'
}其他依赖
Axios 1.8.4
Axios 是基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。
请求封装:
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/stores/modules/user'
// 创建 axios 实例
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 10000
})
// 请求拦截器
service.interceptors.request.use(
(config) => {
const userStore = useUserStore()
// 添加 token
if (userStore.token) {
config.headers['Authorization'] = `Bearer ${userStore.token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const { code, data, msg } = response.data
// 成功响应
if (code === 200) {
return [null, data]
}
// 业务错误
ElMessage.error(msg || '请求失败')
return [new Error(msg), null]
},
(error) => {
// 网络错误
let message = error.message || '网络错误'
if (error.response) {
switch (error.response.status) {
case 401:
message = '未授权,请重新登录'
// 清除 token,跳转登录页
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求地址不存在'
break
case 500:
message = '服务器错误'
break
default:
message = `连接错误${error.response.status}`
}
}
ElMessage.error(message)
return [error, null]
}
)
export default serviceECharts 5.6.0
ECharts 是百度开源的数据可视化图表库。
使用示例:
<template>
<div ref="chartRef" class="chart" style="width: 100%; height: 400px"></div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import type { EChartsOption } from 'echarts'
const chartRef = ref<HTMLElement>()
let chartInstance: echarts.ECharts | null = null
onMounted(() => {
if (!chartRef.value) return
chartInstance = echarts.init(chartRef.value)
const option: EChartsOption = {
title: {
text: '销售统计'
},
tooltip: {},
xAxis: {
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
}
chartInstance.setOption(option)
// 自适应大小
window.addEventListener('resize', handleResize)
})
const handleResize = () => {
chartInstance?.resize()
}
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
chartInstance?.dispose()
})
</script>@vueuse/core 13.1.0
VueUse 是基于 Composition API 的实用函数集合。
常用函数:
import { useStorage, useDark, useToggle, useDebounce, useThrottle } from '@vueuse/core'
// 持久化存储
const userName = useStorage('user-name', '')
// 暗黑模式
const isDark = useDark()
const toggleDark = useToggle(isDark)
// 防抖
const debouncedValue = useDebounce(searchText, 500)
// 节流
const throttledFn = useThrottle(() => {
console.log('throttled')
}, 1000)项目结构
目录组织
plus-ui/
├── env/ # 环境变量配置
│ ├── .env.development # 开发环境
│ ├── .env.production # 生产环境
│ └── .env.test # 测试环境
├── public/ # 静态资源(不经过 Vite 处理)
│ ├── favicon.ico # 网站图标
│ └── logo.png # Logo
├── src/ # 源代码目录
│ ├── api/ # API 接口定义
│ │ ├── business/ # 业务模块 API
│ │ ├── common/ # 通用 API
│ │ ├── system/ # 系统模块 API
│ │ ├── tool/ # 工具模块 API
│ │ └── workflow/ # 工作流 API
│ ├── assets/ # 资源文件(经过 Vite 处理)
│ │ ├── icons/ # SVG 图标
│ │ ├── images/ # 图片资源
│ │ └── styles/ # 全局样式
│ ├── components/ # 公共组件
│ │ ├── AAi/ # AI 对话组件
│ │ ├── ACard/ # 卡片组件
│ │ ├── AForm/ # 表单组件
│ │ ├── AModal/ # 弹窗组件
│ │ ├── ASearchForm/ # 搜索表单
│ │ └── ... # 其他组件
│ ├── composables/ # 组合式函数
│ │ ├── useAuth.ts # 权限验证
│ │ ├── useDialog.ts # 弹窗管理
│ │ ├── useDict.ts # 字典管理
│ │ ├── useTheme.ts # 主题切换
│ │ └── ... # 其他 Composables
│ ├── directives/ # 自定义指令
│ │ ├── permission/ # 权限指令
│ │ ├── dialog/ # 弹窗指令
│ │ └── common/ # 通用指令
│ ├── layouts/ # 布局组件
│ │ ├── components/ # 布局子组件
│ │ ├── Layout.vue # 主布局
│ │ ├── HomeLayout.vue # 首页布局
│ │ └── BlankLayout.vue # 空白布局
│ ├── locales/ # 国际化
│ │ ├── lang/ # 语言文件
│ │ └── i18n.ts # i18n 配置
│ ├── plugins/ # 插件配置
│ │ ├── element-plus.ts # Element Plus
│ │ ├── echarts.ts # ECharts
│ │ └── index.ts # 插件导出
│ ├── router/ # 路由配置
│ │ ├── modules/ # 路由模块
│ │ ├── utils/ # 路由工具
│ │ ├── guard.ts # 路由守卫
│ │ └── router.ts # 路由实例
│ ├── stores/ # Pinia 状态管理
│ │ ├── modules/ # Store 模块
│ │ │ ├── user.ts # 用户状态
│ │ │ ├── permission.ts # 权限状态
│ │ │ ├── dict.ts # 字典状态
│ │ │ └── ... # 其他 Store
│ │ └── store.ts # Pinia 实例
│ ├── types/ # TypeScript 类型定义
│ │ ├── global.d.ts # 全局类型
│ │ ├── env.d.ts # 环境变量类型
│ │ ├── auto-imports.d.ts # 自动导入类型
│ │ └── components.d.ts # 组件类型
│ ├── utils/ # 工具函数
│ │ ├── boolean.ts # 布尔工具
│ │ ├── cache.ts # 缓存工具
│ │ ├── crypto.ts # 加密工具
│ │ ├── date.ts # 日期工具
│ │ ├── format.ts # 格式化工具
│ │ ├── modal.ts # 弹窗工具
│ │ ├── string.ts # 字符串工具
│ │ ├── tree.ts # 树形工具
│ │ └── validators.ts # 验证工具
│ ├── views/ # 页面组件
│ │ ├── business/ # 业务模块页面
│ │ ├── common/ # 通用页面
│ │ ├── system/ # 系统模块页面
│ │ ├── tool/ # 工具模块页面
│ │ └── workflow/ # 工作流页面
│ ├── App.vue # 根组件
│ ├── main.ts # 应用入口
│ └── systemConfig.ts # 系统配置
├── vite/ # Vite 插件配置
│ └── plugins/ # 自定义插件
│ ├── auto-imports.ts # 自动导入
│ ├── components.ts # 组件自动注册
│ ├── compression.ts # 压缩插件
│ ├── icons.ts # 图标插件
│ └── unocss.ts # UnoCSS 插件
├── .editorconfig # 编辑器配置
├── .env.production # 生产环境变量
├── .gitignore # Git 忽略文件
├── .npmrc # npm 配置
├── .prettierignore # Prettier 忽略文件
├── .prettierrc.js # Prettier 配置
├── eslint.config.ts # ESLint 配置
├── index.html # HTML 模板
├── package.json # 项目依赖
├── pnpm-lock.yaml # 依赖锁定文件
├── README.md # 项目说明
├── tsconfig.json # TypeScript 配置
├── uno.config.ts # UnoCSS 配置
└── vite.config.ts # Vite 配置分层架构
RuoYi-Plus-UniApp 采用清晰的分层架构:
┌─────────────────────────────────────┐
│ 视图层 (View Layer) │
│ Vue 组件、页面、布局 │
└─────────────────────────────────────┘
↓↑
┌─────────────────────────────────────┐
│ 业务逻辑层 (Business Layer) │
│ Composables、Stores、自定义指令 │
└─────────────────────────────────────┘
↓↑
┌─────────────────────────────────────┐
│ 数据访问层 (Data Access Layer) │
│ API 接口、HTTP 请求 │
└─────────────────────────────────────┘
↓↑
┌─────────────────────────────────────┐
│ 工具层 (Utility Layer) │
│ 工具函数、常量、类型定义 │
└─────────────────────────────────────┘各层职责:
视图层 - 负责用户界面展示和交互
- Vue 组件
- 页面布局
- 模板和样式
业务逻辑层 - 负责业务逻辑处理
- Composables 封装可复用逻辑
- Stores 管理全局状态
- 自定义指令扩展 DOM 行为
数据访问层 - 负责数据获取和处理
- API 接口定义
- HTTP 请求封装
- 数据转换和映射
工具层 - 负责提供通用工具
- 工具函数
- 常量定义
- 类型定义
核心特性
1. 模块化设计
功能模块划分:
项目按照业务功能划分为多个模块,每个模块包含独立的 API、组件、页面和状态管理。
system/ # 系统管理模块
├── core/ # 核心功能
│ ├── user/ # 用户管理
│ ├── role/ # 角色管理
│ ├── menu/ # 菜单管理
│ ├── dept/ # 部门管理
│ └── ...
├── log/ # 日志管理
└── monitor/ # 系统监控
business/ # 业务模块
├── base/ # 基础业务
│ ├── ad/ # 广告管理
│ ├── payment/ # 支付管理
│ └── ...
└── mall/ # 商城业务
├── goods/ # 商品管理
├── order/ # 订单管理
└── ...
tool/ # 工具模块
├── gen/ # 代码生成
└── swagger/ # 接口文档
workflow/ # 工作流模块
├── definition/ # 流程定义
├── instance/ # 流程实例
└── task/ # 任务管理2. 类型安全
全面的 TypeScript 支持:
// API 类型定义
export interface UserQuery {
pageNum: number
pageSize: number
userName?: string
status?: string
}
export interface UserVo {
userId: number
userName: string
nickName: string
email: string
phone: string
status: string
}
// API 函数定义
export const listUser = (query: UserQuery): Result<PageResult<UserVo>> => {
return request({
url: '/system/user/list',
method: 'get',
params: query
})
}
// 组件 Props 类型
interface ASearchFormProps {
modelValue: Record<string, any>
visible?: boolean
inline?: boolean
labelWidth?: string
collapsible?: boolean
}
// 组件 Emits 类型
interface ASearchFormEmits {
'update:modelValue': [value: Record<string, any>]
'search': []
'reset': []
}3. 状态管理
Pinia 模块化状态管理:
// User Store
export const useUserStore = defineStore('user', () => {
const userInfo = ref<UserInfo | null>(null)
const token = ref<string>('')
const roles = ref<string[]>([])
const permissions = ref<string[]>([])
// ... 状态和方法
return { userInfo, token, roles, permissions, /* ... */ }
})
// Permission Store
export const usePermissionStore = defineStore('permission', () => {
const routes = ref<RouteRecordRaw[]>([])
const dynamicRoutes = ref<RouteRecordRaw[]>([])
// ... 状态和方法
return { routes, dynamicRoutes, /* ... */ }
})
// Dict Store
export const useDictStore = defineStore('dict', () => {
const dicts = ref<Map<string, DictItem[]>>(new Map())
// ... 状态和方法
return { dicts, /* ... */ }
})4. 路由管理
动态路由生成:
基于用户权限动态生成路由,实现细粒度的权限控制。
export const usePermissionStore = defineStore('permission', () => {
const routes = ref<RouteRecordRaw[]>([])
// 生成动态路由
const generateRoutes = async (): Promise<[Error | null, RouteRecordRaw[] | null]> => {
const userStore = useUserStore()
const roles = userStore.roles
// 获取后端路由配置
const [err, data] = await getRouters()
if (err) {
return [err, null]
}
// 过滤路由
const accessRoutes = filterRoutes(data, roles)
// 保存路由
routes.value = constantRoutes.concat(accessRoutes)
return [null, accessRoutes]
}
return { routes, generateRoutes }
})路由守卫实现:
// 路由前置守卫
router.beforeEach(async (to, from, next) => {
NProgress.start()
const userStore = useUserStore()
const permissionStore = usePermissionStore()
// 未登录
if (!userStore.token) {
if (WHITE_LIST.includes(to.path)) {
return next()
}
return next(`/login?redirect=${to.fullPath}`)
}
// 已登录访问登录页
if (to.path === '/login') {
return next({ path: '/' })
}
// 获取用户信息和生成动态路由
if (userStore.roles.length === 0) {
await userStore.fetchUserInfo()
const accessRoutes = await permissionStore.generateRoutes()
accessRoutes.forEach(route => router.addRoute(route))
next({ ...to, replace: true })
} else {
next()
}
})5. HTTP 请求
统一的请求封装:
// 请求拦截器
service.interceptors.request.use(
(config) => {
const userStore = useUserStore()
// 添加 Token
if (userStore.token) {
config.headers['Authorization'] = `Bearer ${userStore.token}`
}
// 添加时间戳防止缓存
if (config.method === 'get') {
config.params = {
...config.params,
_t: Date.now()
}
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response) => {
const { code, data, msg } = response.data
if (code === 200) {
return [null, data]
}
// 处理业务错误
if (code === 401) {
// Token 过期,清除登录状态
const userStore = useUserStore()
userStore.logout()
router.push('/login')
}
ElMessage.error(msg || '请求失败')
return [new Error(msg), null]
},
(error) => {
// 处理网络错误
ElMessage.error(error.message || '网络错误')
return [error, null]
}
)6. 权限控制
多维度权限控制:
// 1. 路由权限 - 在路由元信息中配置
{
path: '/system/user',
component: () => import('@/views/system/user/user.vue'),
meta: {
title: '用户管理',
perms: ['system:user:list']
}
}
// 2. 按钮权限 - 使用自定义指令
<el-button v-hasPermi="['system:user:add']">新增</el-button>
<el-button v-hasPermi="['system:user:edit']">编辑</el-button>
<el-button v-hasPermi="['system:user:remove']">删除</el-button>
// 3. 接口权限 - 后端验证
export const addUser = (data: UserForm): Result<void> => {
return request({
url: '/system/user',
method: 'post',
data
})
}
// 4. 数据权限 - 基于角色和部门
const userStore = useUserStore()
const hasDataScope = (deptId: number) => {
// 检查用户是否有权限访问该部门数据
return userStore.dataScope.includes(deptId)
}设计原则
1. 单一职责原则 (SRP)
每个模块、组件、函数都应该有一个明确的职责。
示例:
// ❌ 不推荐: 一个函数做太多事情
const handleUser = (user: User) => {
// 验证
if (!user.userName) throw new Error('用户名不能为空')
// 格式化
user.userName = user.userName.trim()
// 保存
saveUser(user)
// 发送通知
sendNotification(user)
}
// ✅ 推荐: 职责分离
const validateUser = (user: User) => {
if (!user.userName) throw new Error('用户名不能为空')
}
const formatUser = (user: User) => {
return {
...user,
userName: user.userName.trim()
}
}
const handleUser = async (user: User) => {
validateUser(user)
const formatted = formatUser(user)
await saveUser(formatted)
await sendNotification(formatted)
}2. 开放封闭原则 (OCP)
对扩展开放,对修改封闭。
示例:
// ❌ 不推荐: 每次添加新类型都要修改代码
const getButtonClass = (type: string) => {
if (type === 'primary') return 'btn-primary'
if (type === 'success') return 'btn-success'
if (type === 'danger') return 'btn-danger'
// 添加新类型需要修改这里
}
// ✅ 推荐: 使用配置对象,易于扩展
const BUTTON_CLASS_MAP = {
primary: 'btn-primary',
success: 'btn-success',
danger: 'btn-danger'
// 添加新类型只需添加配置
}
const getButtonClass = (type: string) => {
return BUTTON_CLASS_MAP[type] || 'btn-default'
}3. 依赖倒置原则 (DIP)
高层模块不应该依赖低层模块,两者都应该依赖抽象。
示例:
// ❌ 不推荐: 直接依赖具体实现
class UserService {
private api = new HttpClient()
async getUser(id: number) {
return this.api.get(`/user/${id}`)
}
}
// ✅ 推荐: 依赖抽象接口
interface IHttpClient {
get(url: string): Promise<any>
post(url: string, data: any): Promise<any>
}
class UserService {
constructor(private api: IHttpClient) {}
async getUser(id: number) {
return this.api.get(`/user/${id}`)
}
}4. 接口隔离原则 (ISP)
客户端不应该依赖它不需要的接口。
示例:
// ❌ 不推荐: 接口太大,包含不必要的方法
interface IUser {
login(): void
logout(): void
register(): void
updateProfile(): void
changePassword(): void
deleteAccount(): void
}
// ✅ 推荐: 接口分离
interface IAuth {
login(): void
logout(): void
}
interface IUserProfile {
updateProfile(): void
changePassword(): void
}
interface IUserManagement {
register(): void
deleteAccount(): void
}性能优化
1. 代码分割
路由懒加载:
const routes: RouteRecordRaw[] = [
{
path: '/system/user',
component: () => import('@/views/system/user/user.vue')
},
{
path: '/system/role',
component: () => import('@/views/system/role/role.vue')
}
]组件懒加载:
<script setup>
import { defineAsyncComponent } from 'vue'
const HeavyComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
</script>动态导入:
const loadModule = async (moduleName: string) => {
const module = await import(`./modules/${moduleName}.ts`)
return module.default
}2. 资源优化
图片懒加载:
<template>
<img v-lazy="imageUrl" alt="懒加载图片" />
</template>Vite 静态资源处理:
// vite.config.ts
export default defineConfig({
build: {
assetsInlineLimit: 4096, // 小于 4KB 的资源内联为 base64
rollupOptions: {
output: {
// 静态资源分类打包
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]'
}
}
}
})3. 渲染优化
虚拟列表:
<template>
<virtual-list
:data="longList"
:data-key="'id'"
:data-sources="longList"
:data-component="itemComponent"
:estimate-size="50"
/>
</template>keep-alive 缓存:
<template>
<router-view v-slot="{ Component }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="$route.path" />
</keep-alive>
</router-view>
</template>防抖节流:
import { useDebounceFn, useThrottleFn } from '@vueuse/core'
// 防抖
const debouncedSearch = useDebounceFn(() => {
search()
}, 500)
// 节流
const throttledScroll = useThrottleFn(() => {
handleScroll()
}, 200)4. 构建优化
Vite 构建优化:
export default defineConfig({
build: {
// 生产环境移除 console
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
// Gzip 压缩
minify: 'terser',
// 分包策略
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'element-plus': ['element-plus'],
'echarts': ['echarts']
}
}
}
}
})安全考虑
1. XSS 防护
输入过滤:
// 过滤 HTML 标签
const sanitizeHtml = (html: string) => {
const div = document.createElement('div')
div.textContent = html
return div.innerHTML
}
// 使用 DOMPurify 库
import DOMPurify from 'dompurify'
const cleanHtml = DOMPurify.sanitize(dirtyHtml)CSP 策略:
<!-- index.html -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
">2. CSRF 防护
Token 验证:
// 请求拦截器添加 CSRF Token
service.interceptors.request.use(config => {
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content
if (csrfToken) {
config.headers['X-CSRF-Token'] = csrfToken
}
return config
})3. 权限控制
前端权限验证:
// 检查权限
export const hasPermission = (perms: string[]) => {
const userStore = useUserStore()
return perms.some(perm => userStore.permissions.includes(perm))
}
// 权限指令
app.directive('hasPermi', {
mounted(el, binding) {
const { value } = binding
if (!hasPermission(value)) {
el.parentNode?.removeChild(el)
}
}
})部署策略
1. 构建命令
# 开发环境构建
npm run build:dev
# 生产环境构建
npm run build:prod
# 预览构建结果
npm run preview2. Nginx 配置
server {
listen 80;
server_name example.com;
# 前端资源
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# API 代理
location /api/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
}3. Docker 部署
# 构建阶段
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build:prod
# 运行阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]总结
RuoYi-Plus-UniApp 前端架构采用现代化的技术栈和工程化方案,通过清晰的分层设计、模块化开发、组件化封装,为企业级应用提供了稳定、高效、可维护的技术基础。
关键要点:
- 现代化技术栈 - Vue 3 + TypeScript + Vite,提供优秀的开发体验
- 完整的类型系统 - TypeScript 全覆盖,编译时类型检查
- 模块化架构 - 按功能模块组织,高内聚低耦合
- 组件化开发 - 可复用组件体系,提高开发效率
- 性能优化 - 代码分割、懒加载、缓存策略
- 安全保障 - XSS、CSRF 防护,权限控制
- 工程化规范 - 统一的代码规范和构建流程
