Skip to content

技术栈介绍

概述

RuoYi-Plus-UniApp 前端框架采用现代化的技术栈构建,基于 Vue 3 生态系统,结合 TypeScript、Vite 等先进技术,为企业级应用提供高性能、高可维护性的开发解决方案。本文档详细介绍项目所使用的全部技术栈、版本信息、配置方式及最佳实践。

核心特性:

  • Vue 3 Composition API - 采用组合式 API 实现更好的逻辑复用和代码组织
  • TypeScript 5.8 - 全面的类型安全保障,提升开发效率和代码质量
  • Vite 6.3 - 极速的开发服务器和构建工具,毫秒级热更新
  • Element Plus 2.9 - 成熟的企业级 UI 组件库
  • UnoCSS - 即时原子化 CSS 引擎,按需生成样式
  • Pinia 3.0 - Vue 3 官方推荐的状态管理库

技术栈版本总览

核心依赖版本

技术版本说明
Vue3.5.13渐进式 JavaScript 框架
Vue Router4.5.0Vue 官方路由
Pinia3.0.2Vue 官方状态管理
TypeScript5.8.3JavaScript 超集
Vite6.3.2下一代前端构建工具
Element Plus2.9.8Vue 3 企业级 UI 组件库
UnoCSS66.5.2原子化 CSS 引擎
Axios1.8.4HTTP 客户端
VueUse13.1.0Vue 组合式 API 工具集
Vue I18n11.1.3国际化解决方案
ECharts5.6.0数据可视化图表库
Sass1.87.0CSS 预处理器

开发依赖版本

技术版本说明
ESLint9.21.0代码规范检查
Prettier3.5.2代码格式化
Vitest3.1.2单元测试框架
unplugin-auto-import19.1.2API 自动导入
unplugin-vue-components28.5.0组件自动导入
unplugin-icons22.1.0图标自动导入
vite-plugin-compression0.5.1资源压缩插件
vite-plugin-vue-devtools7.7.5Vue 开发工具

环境要求

json
{
  "engines": {
    "node": ">=18.18.0",
    "npm": ">=8.9.0",
    "pnpm": ">=7.30"
  },
  "browserslist": [
    "Chrome >= 87",
    "Edge >= 88",
    "Safari >= 14",
    "Firefox >= 78"
  ]
}

核心框架技术

Vue 3

Vue 3 是项目的核心框架,采用全新的 Composition API 架构,提供更好的逻辑复用和代码组织能力。

响应式系统

Vue 3 基于 Proxy 实现全新的响应式系统,相比 Vue 2 的 Object.defineProperty 具有更好的性能和更完整的响应式支持。

typescript
// 基础响应式示例
import { ref, reactive, computed, watch } from 'vue'

// ref - 用于基本类型
const count = ref(0)

// reactive - 用于对象类型
const state = reactive({
  user: null,
  loading: false,
  error: null
})

// computed - 计算属性
const doubleCount = computed(() => count.value * 2)

// watch - 侦听器
watch(count, (newVal, oldVal) => {
  console.log(`count changed from ${oldVal} to ${newVal}`)
})

Composition API

Composition API 是 Vue 3 的核心特性,通过组合式函数实现逻辑复用。

vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'

// 组件状态
const isLoading = ref(true)
const data = ref<UserInfo | null>(null)

// 生命周期钩子
onMounted(async () => {
  try {
    data.value = await fetchUserInfo()
  } finally {
    isLoading.value = false
  }
})

onUnmounted(() => {
  // 清理资源
})
</script>

<template>
  <div v-if="isLoading">加载中...</div>
  <div v-else>{{ data?.name }}</div>
</template>

组合式函数 (Composables)

项目采用组合式函数模式封装可复用的逻辑。

typescript
// src/composables/useUser.ts
export function useUser() {
  const userInfo = ref<UserInfo | null>(null)
  const loading = ref(false)
  const error = ref<Error | null>(null)

  const fetchUser = async () => {
    loading.value = true
    error.value = null
    try {
      userInfo.value = await getUserInfo()
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  const logout = async () => {
    await logoutApi()
    userInfo.value = null
  }

  return {
    userInfo,
    loading,
    error,
    fetchUser,
    logout
  }
}

Vue Router 4

Vue Router 4 是 Vue 3 的官方路由解决方案,提供完整的路由功能支持。

路由配置

typescript
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/login/index.vue'),
    meta: {
      title: '登录',
      hidden: true
    }
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        name: 'Dashboard',
        component: () => import('@/views/dashboard/index.vue'),
        meta: {
          title: '首页',
          icon: 'dashboard',
          affix: true
        }
      }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.VITE_APP_CONTEXT_PATH),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    }
    return { top: 0 }
  }
})

export default router

路由守卫

typescript
// src/router/permission.ts
import router from './index'
import { useUserStore } from '@/stores/modules/user'
import { usePermissionStore } from '@/stores/modules/permission'

// 白名单路由
const whiteList = ['/login', '/register', '/forget-password']

router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore()
  const permissionStore = usePermissionStore()

  // 设置页面标题
  document.title = `${to.meta.title || ''} - ${import.meta.env.VITE_APP_TITLE}`

  // 判断是否已登录
  if (userStore.token) {
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      // 判断是否已加载路由
      if (permissionStore.routes.length === 0) {
        try {
          // 获取用户信息和权限
          await userStore.getInfo()
          // 生成动态路由
          const accessRoutes = await permissionStore.generateRoutes()
          // 动态添加路由
          accessRoutes.forEach((route) => {
            router.addRoute(route)
          })
          next({ ...to, replace: true })
        } catch (error) {
          // 获取信息失败,重新登录
          await userStore.logout()
          next(`/login?redirect=${to.path}`)
        }
      } else {
        next()
      }
    }
  } else {
    // 未登录
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
    }
  }
})

动态路由

项目支持基于后端权限的动态路由生成。

typescript
// src/stores/modules/permission.ts
export const usePermissionStore = defineStore('permission', () => {
  const routes = ref<RouteRecordRaw[]>([])
  const addRoutes = ref<RouteRecordRaw[]>([])

  // 根据后端返回的菜单生成路由
  const generateRoutes = async () => {
    const { data } = await getRouters()
    const asyncRoutes = filterAsyncRoutes(data)

    addRoutes.value = asyncRoutes
    routes.value = constantRoutes.concat(asyncRoutes)

    return asyncRoutes
  }

  // 过滤异步路由
  const filterAsyncRoutes = (routes: any[], roles?: string[]) => {
    const res: RouteRecordRaw[] = []

    routes.forEach((route) => {
      const tmp = { ...route }
      // 处理组件
      if (tmp.component === 'Layout') {
        tmp.component = Layout
      } else if (tmp.component === 'ParentView') {
        tmp.component = ParentView
      } else if (tmp.component === 'InnerLink') {
        tmp.component = InnerLink
      } else {
        tmp.component = loadView(tmp.component)
      }

      // 递归处理子路由
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }

      res.push(tmp)
    })

    return res
  }

  return {
    routes,
    addRoutes,
    generateRoutes
  }
})

Pinia 状态管理

Pinia 是 Vue 3 官方推荐的状态管理库,提供更简洁的 API 和更好的 TypeScript 支持。

Store 定义

typescript
// src/stores/modules/user.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { login, logout, getInfo } from '@/api/auth'
import type { LoginData, UserInfo } from '@/types/auth'

export const useUserStore = defineStore('user', () => {
  // 状态
  const token = ref<string>(getToken() || '')
  const userInfo = ref<UserInfo | null>(null)
  const roles = ref<string[]>([])
  const permissions = ref<string[]>([])

  // 登录
  const loginAction = async (loginData: LoginData) => {
    const { data } = await login(loginData)
    token.value = data.access_token
    setToken(data.access_token)
  }

  // 获取用户信息
  const getInfoAction = async () => {
    const { data } = await getInfo()
    userInfo.value = data.user
    roles.value = data.roles
    permissions.value = data.permissions
    return data
  }

  // 登出
  const logoutAction = async () => {
    await logout()
    token.value = ''
    userInfo.value = null
    roles.value = []
    permissions.value = []
    removeToken()
  }

  // 重置状态
  const resetState = () => {
    token.value = ''
    userInfo.value = null
    roles.value = []
    permissions.value = []
  }

  return {
    token,
    userInfo,
    roles,
    permissions,
    loginAction,
    getInfoAction,
    logoutAction,
    resetState
  }
}, {
  persist: {
    // 持久化配置
    key: 'user-store',
    storage: localStorage,
    pick: ['token']
  }
})

Store 使用

vue
<script setup lang="ts">
import { useUserStore } from '@/stores/modules/user'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()

// 使用 storeToRefs 保持响应式
const { userInfo, roles } = storeToRefs(userStore)

// 直接调用 action
const handleLogout = async () => {
  await userStore.logoutAction()
  router.push('/login')
}
</script>

<template>
  <div class="user-info">
    <span>{{ userInfo?.nickName }}</span>
    <el-button @click="handleLogout">退出登录</el-button>
  </div>
</template>

Store 持久化

typescript
// src/stores/index.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()

// 使用持久化插件
pinia.use(piniaPluginPersistedstate)

export default pinia

构建工具链

Vite

Vite 是项目的核心构建工具,提供极速的开发服务器和优化的生产构建。

基础配置

typescript
// vite.config.ts
import { defineConfig, loadEnv, ConfigEnv, UserConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default async ({ command, mode }: ConfigEnv): Promise<UserConfig> => {
  const env = loadEnv(mode, path.resolve(process.cwd(), 'env'))

  return defineConfig({
    // 环境变量目录
    envDir: './env',

    // 基础路径
    base: env.VITE_APP_CONTEXT_PATH,

    // 路径别名
    resolve: {
      alias: {
        '@': path.join(process.cwd(), './src')
      },
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
    },

    // 开发服务器配置
    server: {
      host: '0.0.0.0',
      port: Number(env.VITE_APP_PORT),
      strictPort: false,
      open: true,
      proxy: {
        [env.VITE_APP_BASE_API]: {
          target: 'http://127.0.0.1:' + env.VITE_APP_BASE_API_PORT,
          changeOrigin: true,
          ws: true,
          rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
        }
      }
    },

    // CSS 配置
    css: {
      preprocessorOptions: {
        scss: {
          api: 'modern-compiler'
        }
      },
      postcss: {
        plugins: [
          autoprefixer()
        ]
      }
    },

    // 依赖预构建
    optimizeDeps: {
      include: [
        'vue',
        'vue-router',
        'pinia',
        'axios',
        '@vueuse/core',
        'echarts',
        'vue-i18n',
        'element-plus/es/components/**/css'
      ]
    }
  })
}

插件系统

项目使用模块化的插件配置方式。

typescript
// vite/plugins/index.ts
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import createUnoCss from './unocss'
import createAutoImport from './auto-imports'
import createComponents from './components'
import createIcons from './icons'
import createCompression from './compression'
import createSetupExtend from './setup-extend'
import createOpenApiPlugin from './openapi'
import { createHmrControlPlugin } from './hmrControl'

export default (viteEnv: any, isBuild = false): [] => {
  const vitePlugins: any = []

  // Vue 官方插件
  vitePlugins.push(vue())

  // Vue 开发工具
  vitePlugins.push(vueDevTools())

  // UnoCSS 原子化 CSS
  vitePlugins.push(createUnoCss())

  // API 自动导入
  vitePlugins.push(createAutoImport(path))

  // 组件自动导入
  vitePlugins.push(createComponents(path))

  // 资源压缩
  vitePlugins.push(createCompression(viteEnv))

  // 图标自动导入
  vitePlugins.push(createIcons())

  // OpenAPI 代码生成
  vitePlugins.push(createOpenApiPlugin({
    input: `http://127.0.0.1:${apiPort}/v3/api-docs/business`,
    output: 'src/api',
    mode: 'manual',
    enabled: true
  }))

  // HMR 控制(开发模式)
  if (!isBuild) {
    vitePlugins.push(createHmrControlPlugin())
  }

  return vitePlugins
}

TypeScript

项目采用 TypeScript 进行开发,提供完整的类型安全保障。

TypeScript 配置

json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "baseUrl": ".",
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ESNext", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,
    "strict": true,
    "allowJs": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "noEmit": true,
    "paths": {
      "@/*": ["./src/*"]
    },
    "types": ["node", "vite/client"],
    "noImplicitAny": false,
    "removeComments": true,
    "experimentalDecorators": true,
    "strictFunctionTypes": false,
    "strictNullChecks": false,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.vue",
    "vite.config.ts",
    "vitest.config.ts",
    "eslint.config.ts",
    "src/**/*.d.ts"
  ],
  "exclude": ["node_modules", "dist", "src/**/__tests__/*"]
}

类型定义示例

typescript
// src/types/auth.d.ts
export interface LoginData {
  username: string
  password: string
  code?: string
  uuid?: string
  rememberMe?: boolean
}

export interface LoginResult {
  access_token: string
  expires_in: number
}

export interface UserInfo {
  userId: number
  userName: string
  nickName: string
  avatar: string
  email: string
  phonenumber: string
  sex: string
  deptId: number
  deptName: string
}

export interface UserInfoResult {
  user: UserInfo
  roles: string[]
  permissions: string[]
}

自动导入系统

项目配置了完整的自动导入系统,减少手动 import 的工作量。

API 自动导入

typescript
// vite/plugins/auto-imports.ts
import AutoImport from 'unplugin-auto-import/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default (path: any) => {
  return AutoImport({
    // 自动导入的库
    imports: ['vue', 'vue-router', '@vueuse/core', 'pinia'],

    // 自动导入目录
    dirs: ['src/composables', 'src/stores/modules'],

    // ESLint 配置
    eslintrc: {
      enabled: true,
      filepath: './.eslintrc-auto-import.json',
      globalsPropValue: true
    },

    // Element Plus 解析器
    resolvers: [ElementPlusResolver()],

    // Vue 模板中自动导入
    vueTemplate: true,

    // 类型声明文件
    dts: 'src/types/auto-imports.d.ts'
  })
}

组件自动导入

typescript
// vite/plugins/components.ts
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import IconsResolver from 'unplugin-icons/resolver'

export default (path: any) => {
  return Components({
    resolvers: [
      // Element Plus 组件自动导入
      ElementPlusResolver(),

      // 图标组件自动导入
      IconsResolver({
        enabledCollections: ['ep']
      })
    ],

    // 类型声明文件
    dts: path.resolve(path.join(process.cwd(), './src'), 'types', 'components.d.ts')
  })
}

使用示例

vue
<script setup lang="ts">
// 无需手动导入 Vue API
const count = ref(0)
const doubled = computed(() => count.value * 2)

// 无需手动导入 VueUse
const { width, height } = useWindowSize()

// 无需手动导入 Pinia Store
const userStore = useUserStore()
</script>

<template>
  <!-- 无需手动导入 Element Plus 组件 -->
  <el-button type="primary" @click="count++">
    点击次数: {{ count }}
  </el-button>

  <!-- 无需手动导入图标组件 -->
  <i-ep-search />
</template>

UI 组件与样式

Element Plus

Element Plus 是项目的核心 UI 组件库,提供丰富的企业级组件。

组件按需导入

项目采用自动按需导入方式,只打包使用到的组件。

typescript
// 自动导入配置已在 vite 插件中完成
// 直接在模板中使用即可
<template>
  <el-form :model="form" :rules="rules" ref="formRef">
    <el-form-item label="用户名" prop="username">
      <el-input v-model="form.username" />
    </el-form-item>
    <el-form-item label="密码" prop="password">
      <el-input v-model="form.password" type="password" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="handleSubmit">提交</el-button>
    </el-form-item>
  </el-form>
</template>

主题定制

scss
// src/assets/styles/element-plus.scss
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      'base': #409eff
    ),
    'success': (
      'base': #67c23a
    ),
    'warning': (
      'base': #e6a23c
    ),
    'danger': (
      'base': #f56c6c
    ),
    'info': (
      'base': #909399
    )
  )
);

// 自定义组件样式
.el-button {
  --el-button-border-radius: 4px;
}

.el-card {
  --el-card-border-radius: 8px;
}

图标使用

vue
<template>
  <!-- 方式一: 自动导入 -->
  <i-ep-search />
  <i-ep-edit />
  <i-ep-delete />

  <!-- 方式二: Element Plus Icon 组件 -->
  <el-icon><Search /></el-icon>

  <!-- 方式三: 在按钮中使用 -->
  <el-button type="primary" :icon="Search">搜索</el-button>
</template>

<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
</script>

UnoCSS 原子化 CSS

UnoCSS 是项目的原子化 CSS 引擎,提供即时生成的工具类。

配置详解

typescript
// uno.config.ts
import {
  defineConfig,
  presetAttributify,
  presetIcons,
  presetTypography,
  presetUno,
  presetWebFonts,
  transformerDirectives,
  transformerVariantGroup
} from 'unocss'

export default defineConfig({
  // 快捷方式
  shortcuts: {
    'panel-title': 'pb-[5px] font-sans leading-[1.1] font-medium text-base text-[#6379bb] border-b border-b-solid border-[var(--el-border-color-light)] mb-5 mt-0',
    'flex-center': 'flex items-center justify-center',
    'flex-between': 'flex items-center justify-between',
    'absolute-center': 'absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2',
    'card': 'bg-white dark:bg-dark-800 rounded shadow p-4',
    'tag': 'inline-block px-2 py-1 text-xs rounded'
  },

  // 主题配置
  theme: {
    colors: {
      'primary': 'var(--el-color-primary)',
      'primary_dark': 'var(--el-color-primary-light-5)',
      'success': 'var(--color-success)',
      'warning': 'var(--color-warning)',
      'danger': 'var(--color-danger)',
      'info': 'var(--color-info)',
      'text-base': 'var(--text-color)',
      'text-secondary': 'var(--text-color-secondary)',
      'border': 'var(--border-color)',
      'bg-base': 'var(--bg-color)',
      'bg-page': 'var(--bg-color-page)',
      'menu-bg': 'var(--menu-bg)',
      'menu-text': 'var(--menu-color)'
    },
    spacing: {
      'sidebar': 'var(--sidebar-width)',
      'header': 'var(--header-height)',
      'tags-view': 'var(--tags-view-height)'
    }
  },

  // 自定义规则
  rules: [
    ['sidebar-width', { 'width': 'var(--sidebar-width)' }],
    ['header-height', { 'height': 'var(--header-height)' }],
    ['scrollbar', { 'overflow': 'auto' }],
    ['scrollbar-y', { 'overflow-y': 'auto', 'overflow-x': 'hidden' }],
    ['text-ellipsis', { 'white-space': 'nowrap', 'overflow': 'hidden', 'text-overflow': 'ellipsis' }]
  ],

  // 预设
  presets: [
    presetUno(),
    presetAttributify(),
    presetIcons({}),
    presetTypography(),
    presetWebFonts({})
  ],

  // 转换器
  transformers: [
    transformerDirectives(),
    transformerVariantGroup()
  ]
})

使用示例

vue
<template>
  <!-- 基础工具类 -->
  <div class="flex items-center justify-between p-4 bg-white rounded shadow">
    <span class="text-lg font-bold text-gray-800">标题</span>
    <span class="text-sm text-gray-500">描述文本</span>
  </div>

  <!-- 快捷方式 -->
  <div class="flex-center h-40 bg-primary text-white">
    居中内容
  </div>

  <!-- 变体组 -->
  <button class="hover:(bg-blue-500 text-white scale-105) transition-all">
    悬停效果
  </button>

  <!-- 属性化模式 -->
  <div text="xl blue-500" p="4" m="2" bg="gray-100">
    属性化写法
  </div>

  <!-- 响应式 -->
  <div class="w-full md:w-1/2 lg:w-1/3">
    响应式宽度
  </div>

  <!-- 暗色模式 -->
  <div class="bg-white dark:bg-gray-800 text-black dark:text-white">
    暗色模式支持
  </div>
</template>

Sass 样式

项目使用 Sass 作为 CSS 预处理器,采用模块化的样式架构。

样式目录结构

src/assets/styles/
├── abstracts/           # 抽象层(变量、混入、函数)
│   ├── _variables.scss  # CSS 变量定义
│   ├── _mixins.scss     # 混入定义
│   └── _functions.scss  # Sass 函数
├── base/                # 基础样式
│   ├── _reset.scss      # 样式重置
│   ├── _typography.scss # 排版样式
│   └── _animations.scss # 动画定义
├── components/          # 组件样式
│   ├── _button.scss     # 按钮样式
│   ├── _card.scss       # 卡片样式
│   └── _form.scss       # 表单样式
├── layout/              # 布局样式
│   ├── _header.scss     # 头部布局
│   ├── _sidebar.scss    # 侧边栏布局
│   └── _content.scss    # 内容区布局
├── themes/              # 主题样式
│   ├── _light.scss      # 亮色主题
│   └── _dark.scss       # 暗色主题
├── vendors/             # 第三方样式
│   └── _element-plus.scss
└── index.scss           # 样式入口

变量定义

scss
// src/assets/styles/abstracts/_variables.scss
:root {
  // 颜色变量
  --primary-color: #409eff;
  --success-color: #67c23a;
  --warning-color: #e6a23c;
  --danger-color: #f56c6c;
  --info-color: #909399;

  // 文本颜色
  --text-color: #303133;
  --text-color-secondary: #606266;
  --text-color-placeholder: #c0c4cc;

  // 边框颜色
  --border-color: #dcdfe6;
  --border-color-light: #e4e7ed;
  --border-color-lighter: #ebeef5;

  // 背景颜色
  --bg-color: #ffffff;
  --bg-color-page: #f2f3f5;
  --bg-color-overlay: #ffffff;

  // 布局尺寸
  --sidebar-width: 210px;
  --sidebar-width-collapsed: 64px;
  --header-height: 50px;
  --tags-view-height: 34px;

  // 圆角
  --border-radius-base: 4px;
  --border-radius-small: 2px;
  --border-radius-large: 8px;

  // 阴影
  --shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
  --shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1);

  // 过渡
  --transition-duration: 0.3s;
  --transition-timing: ease-in-out;
}

// 暗色主题
[data-theme='dark'] {
  --text-color: #e5eaf3;
  --text-color-secondary: #a3a6ad;
  --bg-color: #141414;
  --bg-color-page: #0a0a0a;
  --bg-color-overlay: #1d1e1f;
  --border-color: #4c4d4f;
}

开发工具与规范

ESLint 代码规范

项目采用 ESLint 9 的扁平化配置方式。

typescript
// eslint.config.ts
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
import eslintConfigPrettier from '@vue/eslint-config-prettier'

export default [
  // 基础 JavaScript 规则
  eslint.configs.recommended,

  // TypeScript 规则
  ...tseslint.configs.recommended,

  // Vue 规则
  ...pluginVue.configs['flat/recommended'],

  // Prettier 兼容
  eslintConfigPrettier,

  // 自定义规则
  {
    files: ['**/*.{js,ts,vue}'],
    rules: {
      // Vue 规则
      'vue/multi-word-component-names': 'off',
      'vue/no-v-html': 'off',
      'vue/require-default-prop': 'off',

      // TypeScript 规则
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-unused-vars': 'warn',

      // 通用规则
      'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
      'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
    }
  },

  // 忽略文件
  {
    ignores: [
      'node_modules/**',
      'dist/**',
      '*.d.ts',
      'src/types/auto-imports.d.ts',
      'src/types/components.d.ts'
    ]
  }
]

Prettier 代码格式化

json
{
  "semi": false,
  "singleQuote": true,
  "printWidth": 120,
  "tabWidth": 2,
  "useTabs": false,
  "trailingComma": "none",
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "auto",
  "vueIndentScriptAndStyle": false,
  "htmlWhitespaceSensitivity": "ignore"
}

开发脚本

json
{
  "scripts": {
    "dev": "vite serve --mode development",
    "build:prod": "vite build --mode production",
    "build:dev": "vite build --mode development",
    "preview": "vite preview",
    "lint:eslint": "eslint --max-warnings=0 --timeout=60000",
    "lint:eslint:fix": "eslint --fix --timeout=60000",
    "prettier": "prettier --write ."
  }
}

功能增强库

VueUse 组合式工具

VueUse 提供了 200+ 组合式函数,极大提升开发效率。

typescript
// 常用组合式函数示例
import {
  useWindowSize,      // 窗口尺寸
  useLocalStorage,    // 本地存储
  useDebounceFn,      // 防抖函数
  useThrottleFn,      // 节流函数
  useEventListener,   // 事件监听
  useClipboard,       // 剪贴板
  useDark,            // 暗色模式
  useFullscreen,      // 全屏
  useTitle,           // 页面标题
  useOnline           // 网络状态
} from '@vueuse/core'

// 使用示例
const { width, height } = useWindowSize()
const storedValue = useLocalStorage('key', 'default')
const isDark = useDark()
const { toggle: toggleFullscreen } = useFullscreen()
const { copy, copied } = useClipboard()

Axios HTTP 客户端

项目对 Axios 进行了完整封装,提供统一的请求/响应处理。

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

// 创建 axios 实例
const service: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    const userStore = useUserStore()

    // 添加 token
    if (userStore.token) {
      config.headers['Authorization'] = `Bearer ${userStore.token}`
    }

    // 添加租户 ID
    const tenantId = getTenantId()
    if (tenantId) {
      config.headers['X-Tenant-Id'] = tenantId
    }

    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    const { code, msg, data } = response.data

    // 成功响应
    if (code === 200) {
      return response.data
    }

    // 业务错误
    ElMessage.error(msg || '请求失败')
    return Promise.reject(new Error(msg || '请求失败'))
  },
  (error) => {
    const { response } = error

    // 处理 HTTP 错误
    if (response) {
      switch (response.status) {
        case 401:
          // 未授权,跳转登录
          handleUnauthorized()
          break
        case 403:
          ElMessage.error('没有权限访问')
          break
        case 404:
          ElMessage.error('请求资源不存在')
          break
        case 500:
          ElMessage.error('服务器内部错误')
          break
        default:
          ElMessage.error(response.data?.msg || '请求失败')
      }
    } else {
      ElMessage.error('网络连接异常')
    }

    return Promise.reject(error)
  }
)

export default service

ECharts 数据可视化

typescript
// src/composables/useECharts.ts
import * as echarts from 'echarts'
import { onMounted, onUnmounted, ref, Ref, watch } from 'vue'
import { useDark, useResizeObserver } from '@vueuse/core'

export function useECharts(
  chartRef: Ref<HTMLElement | null>,
  options: Ref<echarts.EChartsOption>
) {
  let chartInstance: echarts.ECharts | null = null
  const isDark = useDark()

  // 初始化图表
  const initChart = () => {
    if (!chartRef.value) return

    chartInstance = echarts.init(
      chartRef.value,
      isDark.value ? 'dark' : 'light'
    )
    chartInstance.setOption(options.value)
  }

  // 更新图表
  const updateChart = () => {
    chartInstance?.setOption(options.value)
  }

  // 调整大小
  const resize = () => {
    chartInstance?.resize()
  }

  // 监听主题变化
  watch(isDark, () => {
    chartInstance?.dispose()
    initChart()
  })

  // 监听配置变化
  watch(options, updateChart, { deep: true })

  // 监听容器大小变化
  useResizeObserver(chartRef, resize)

  onMounted(initChart)

  onUnmounted(() => {
    chartInstance?.dispose()
    chartInstance = null
  })

  return {
    chartInstance,
    resize
  }
}

Vue I18n 国际化

typescript
// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
import zhCn from './locales/zh-cn'
import en from './locales/en'

const i18n = createI18n({
  legacy: false,
  locale: localStorage.getItem('language') || 'zh-cn',
  fallbackLocale: 'zh-cn',
  messages: {
    'zh-cn': zhCn,
    'en': en
  }
})

export default i18n
vue
<script setup lang="ts">
import { useI18n } from 'vue-i18n'

const { t, locale } = useI18n()

const changeLanguage = (lang: string) => {
  locale.value = lang
  localStorage.setItem('language', lang)
}
</script>

<template>
  <div>
    <h1>{{ t('common.title') }}</h1>
    <el-select v-model="locale" @change="changeLanguage">
      <el-option label="中文" value="zh-cn" />
      <el-option label="English" value="en" />
    </el-select>
  </div>
</template>

加密库

项目使用 CryptoJS 和 JSEncrypt 实现数据加密。

typescript
// src/utils/crypto.ts
import CryptoJS from 'crypto-js'
import JSEncrypt from 'jsencrypt'

// AES 加密
export function encryptAES(data: string, key: string): string {
  const keyHex = CryptoJS.enc.Utf8.parse(key)
  const encrypted = CryptoJS.AES.encrypt(data, keyHex, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  })
  return encrypted.toString()
}

// AES 解密
export function decryptAES(encryptedData: string, key: string): string {
  const keyHex = CryptoJS.enc.Utf8.parse(key)
  const decrypted = CryptoJS.AES.decrypt(encryptedData, keyHex, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  })
  return decrypted.toString(CryptoJS.enc.Utf8)
}

// RSA 加密
export function encryptRSA(data: string, publicKey: string): string {
  const encrypt = new JSEncrypt()
  encrypt.setPublicKey(publicKey)
  return encrypt.encrypt(data) || ''
}

// 生成随机 AES 密钥
export function generateAESKey(length = 16): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  let result = ''
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length))
  }
  return result
}

技术选型对比

Vue 3 vs Vue 2

特性Vue 3Vue 2
响应式系统ProxyObject.defineProperty
API 风格Composition API + Options APIOptions API
TypeScript原生支持需要额外配置
性能更快相对较慢
包体积更小(Tree-shaking)较大
生命周期setup + 生命周期钩子选项式生命周期

Pinia vs Vuex

特性PiniaVuex
API 复杂度简单相对复杂
TypeScript原生支持需要额外类型定义
模块化天然支持需要手动配置
DevTools完整支持完整支持
体积更小较大
Mutations不需要必须

Vite vs Webpack

特性ViteWebpack
启动速度极快(毫秒级)较慢(秒级)
热更新极快较慢
配置复杂度简单复杂
插件生态快速增长成熟完善
生产构建RollupWebpack
ESM 支持原生支持需要配置

UnoCSS vs Tailwind CSS

特性UnoCSSTailwind CSS
构建速度极快较快
按需生成实时按需预生成 + Purge
自定义规则灵活简单相对复杂
预设系统丰富插件系统
属性化模式内置支持需要插件
打包体积极小较小

最佳实践

1. 组件设计原则

vue
<!-- 推荐: 使用 Composition API + TypeScript -->
<script setup lang="ts">
interface Props {
  title: string
  loading?: boolean
  data?: Record<string, any>[]
}

const props = withDefaults(defineProps<Props>(), {
  loading: false,
  data: () => []
})

const emit = defineEmits<{
  (e: 'update', value: Record<string, any>): void
  (e: 'delete', id: number): void
}>()
</script>

<template>
  <div class="component-wrapper">
    <h3>{{ title }}</h3>
    <el-skeleton v-if="loading" :rows="3" />
    <template v-else>
      <div v-for="item in data" :key="item.id">
        {{ item.name }}
      </div>
    </template>
  </div>
</template>

2. 状态管理规范

typescript
// 推荐: 使用 setup store 语法
export const useExampleStore = defineStore('example', () => {
  // 状态
  const list = ref<ExampleItem[]>([])
  const loading = ref(false)
  const current = ref<ExampleItem | null>(null)

  // Getter
  const total = computed(() => list.value.length)
  const isEmpty = computed(() => list.value.length === 0)

  // Action
  const fetchList = async (params?: QueryParams) => {
    loading.value = true
    try {
      const { data } = await getListApi(params)
      list.value = data.rows
    } finally {
      loading.value = false
    }
  }

  const reset = () => {
    list.value = []
    current.value = null
  }

  return {
    list,
    loading,
    current,
    total,
    isEmpty,
    fetchList,
    reset
  }
})

3. API 请求封装

typescript
// 推荐: 统一的 API 模块结构
// src/api/system/user.ts
import request from '@/utils/request'
import type { UserQuery, UserForm, UserInfo } from '@/types/system/user'

// 查询用户列表
export function listUser(query: UserQuery) {
  return request<PageResult<UserInfo>>({
    url: '/system/user/list',
    method: 'get',
    params: query
  })
}

// 获取用户详情
export function getUser(userId: number) {
  return request<UserInfo>({
    url: `/system/user/${userId}`,
    method: 'get'
  })
}

// 新增用户
export function addUser(data: UserForm) {
  return request({
    url: '/system/user',
    method: 'post',
    data
  })
}

// 修改用户
export function updateUser(data: UserForm) {
  return request({
    url: '/system/user',
    method: 'put',
    data
  })
}

// 删除用户
export function delUser(userIds: number | number[]) {
  return request({
    url: `/system/user/${userIds}`,
    method: 'delete'
  })
}

4. 样式编写规范

scss
// 推荐: 使用 BEM 命名 + CSS 变量
.user-card {
  padding: var(--spacing-md);
  background: var(--bg-color);
  border-radius: var(--border-radius-base);
  box-shadow: var(--shadow-light);

  &__header {
    display: flex;
    align-items: center;
    margin-bottom: var(--spacing-sm);
  }

  &__title {
    font-size: var(--font-size-lg);
    color: var(--heading-color);
  }

  &__content {
    color: var(--text-color);
  }

  &--active {
    border-color: var(--primary-color);
  }
}

常见问题

1. 开发服务器启动慢

问题原因:

  • 依赖预构建未完成
  • 大量依赖需要转换

解决方案:

typescript
// vite.config.ts
optimizeDeps: {
  include: [
    'vue',
    'vue-router',
    'pinia',
    'axios',
    '@vueuse/core',
    'echarts',
    'element-plus/es/components/**/css'
  ]
}

2. 打包体积过大

问题原因:

  • 未按需加载组件
  • 第三方库未进行 Tree-shaking

解决方案:

typescript
// 使用动态导入
const ECharts = defineAsyncComponent(() => import('echarts'))

// 路由懒加载
{
  path: '/dashboard',
  component: () => import('@/views/dashboard/index.vue')
}

3. 类型推导不准确

问题原因:

  • 自动导入的类型声明未生成
  • tsconfig 配置问题

解决方案:

json
{
  "compilerOptions": {
    "types": ["node", "vite/client"],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.vue",
    "src/**/*.d.ts"
  ]
}

4. 热更新失效

问题原因:

  • 文件监听数量超限
  • 组件未正确声明

解决方案:

typescript
// vite.config.ts
server: {
  watch: {
    usePolling: true,
    interval: 100
  }
}

5. Element Plus 样式丢失

问题原因:

  • 自动导入配置不完整
  • CSS 导入顺序问题

解决方案:

typescript
// vite/plugins/components.ts
ElementPlusResolver({
  importStyle: 'sass'
})

总结

RuoYi-Plus-UniApp 前端框架基于现代化的技术栈构建,通过合理的技术选型和架构设计,实现了高性能、高可维护性的企业级应用开发方案。主要技术特点包括:

  1. Vue 3 + TypeScript - 提供完整的类型安全和更好的开发体验
  2. Vite - 极速的开发服务器和优化的生产构建
  3. Element Plus - 成熟稳定的企业级 UI 组件库
  4. UnoCSS - 高性能的原子化 CSS 解决方案
  5. Pinia - 简洁高效的状态管理
  6. 自动导入 - 减少样板代码,提升开发效率

无论是中小型项目还是大型企业应用,该技术栈都能提供稳定可靠的技术支撑,是现代前端开发的优秀实践。