Vue 3 + TypeScript 企业级应用开发实战

Vue 3 + TypeScript 企业级应用开发实战

1. 引言

在现代前端开发中,构建企业级应用需要考虑诸多因素,包括项目结构、代码质量、团队协作、测试覆盖、部署流程等。Vue 3 和 TypeScript 的结合为企业级应用开发提供了强大的工具和框架支持,能够帮助开发团队构建更加可靠、可维护的应用。

本文将从项目初始化和配置、核心功能实现、团队协作和代码规范、测试和质量保证、部署和监控五个方面,详细介绍 Vue 3 + TypeScript 企业级应用开发的实战经验。我们将提供完整的代码示例和详细的注释,帮助你理解每个环节的实现原理和最佳实践。

2. 项目初始化和配置

2.1 完整的项目脚手架搭建

初始化 Vue 3 + TypeScript 项目
typescript 复制代码
// 使用 Vite 初始化 Vue 3 + TypeScript 项目
// 设计意图:快速搭建基础项目结构,配置必要的依赖和工具

// 步骤 1:初始化项目
npm create vite@latest my-enterprise-app -- --template vue-ts

// 步骤 2:安装核心依赖
npm install vue-router@4 pinia axios

// 步骤 3:安装开发依赖
npm install -D eslint prettier @vue/eslint-config-prettier @vue/eslint-config-typescript typescript eslint-plugin-vue vite-plugin-checker

// 步骤 4:创建项目目录结构
src/
├── assets/        # 静态资源
├── components/    # 通用组件
├── composables/   # 组合式函数
├── router/        # 路由配置
├── stores/        # 状态管理
├── services/      # API 服务
├── utils/         # 工具函数
├── views/         # 页面组件
├── types/         # TypeScript 类型定义
├── constants/     # 常量定义
├── main.ts        # 应用入口
└── App.vue        # 根组件
配置文件设置
typescript 复制代码
// vite.config.ts
// Vite 配置文件
// 设计意图:配置构建选项、插件和开发服务器,优化开发和构建体验
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import checker from 'vite-plugin-checker'

// 路径别名配置
const pathResolve = (path: string) => {
  return resolve(__dirname, path)
}

export default defineConfig({
  // 插件配置
  plugins: [
    vue(),
    // 类型检查插件
    checker({
      typescript: true,
      vueTsc: true,
      eslint: {
        lintCommand: 'eslint ./src --ext .vue,.ts,.tsx'
      }
    })
  ],
  // 解析配置
  resolve: {
    // 路径别名
    alias: {
      '@': pathResolve('./src'),
      components: pathResolve('./src/components'),
      composables: pathResolve('./src/composables'),
      router: pathResolve('./src/router'),
      stores: pathResolve('./src/stores'),
      services: pathResolve('./src/services'),
      utils: pathResolve('./src/utils'),
      views: pathResolve('./src/views'),
      types: pathResolve('./src/types'),
      constants: pathResolve('./src/constants')
    },
    // 扩展名配置,减少导入时的后缀
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
  },
  // 服务器配置
  server: {
    // 开发服务器端口
    port: 3000,
    // 自动打开浏览器
    open: true,
    // 代理配置,解决跨域问题
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  // 构建配置
  build: {
    // 输出目录
    outDir: 'dist',
    // 静态资源目录
    assetsDir: 'assets',
    // 生产环境是否生成 source map
    sourcemap: false,
    // 代码分割配置
    rollupOptions: {
      output: {
        // 代码分割策略
        manualChunks: {
          // 将第三方库打包为单独的 chunk
          vendor: ['vue', 'vue-router', 'pinia', 'axios'],
          // 将 UI 库打包为单独的 chunk
          ui: ['element-plus'],
          // 将工具库打包为单独的 chunk
          utils: ['lodash-es']
        }
      }
    }
  }
})

2.2 环境配置和部署策略

环境变量配置
typescript 复制代码
// .env
// 基础环境变量
// 设计意图:定义应用的基础配置,适用于所有环境

// 应用标题
VITE_APP_TITLE=My Enterprise App
// API 基础路径
VITE_API_BASE_URL=/api

// .env.development
// 开发环境变量
// 设计意图:定义开发环境特有的配置

// 开发环境 API 地址
VITE_API_BASE_URL=http://localhost:8080/api
// 开发环境是否启用调试
VITE_APP_DEBUG=true

// .env.test
// 测试环境变量
// 设计意图:定义测试环境特有的配置

// 测试环境 API 地址
VITE_API_BASE_URL=http://test-api.example.com/api
// 测试环境是否启用调试
VITE_APP_DEBUG=false

// .env.production
// 生产环境变量
// 设计意图:定义生产环境特有的配置

// 生产环境 API 地址
VITE_API_BASE_URL=https://api.example.com/api
// 生产环境是否启用调试
VITE_APP_DEBUG=false
环境配置加载
typescript 复制代码
// src/utils/env.ts
// 环境配置工具
// 设计意图:统一管理环境变量,提供类型安全的访问方式

// 应用配置接口
interface AppConfig {
  title: string
  apiBaseUrl: string
  debug: boolean
  // 其他配置...
}

// 环境配置
const config: AppConfig = {
  title: import.meta.env.VITE_APP_TITLE || 'My Enterprise App',
  apiBaseUrl: import.meta.env.VITE_API_BASE_URL || '/api',
  debug: import.meta.env.VITE_APP_DEBUG === 'true',
  // 其他配置...
}

export default config

// 使用环境配置
// import config from '@/utils/env'
// console.log(config.title)
// console.log(config.apiBaseUrl)

2.3 开发、测试、生产环境的区分

环境区分策略
typescript 复制代码
// src/utils/environment.ts
// 环境区分工具
// 设计意图:提供环境判断和环境相关的工具函数

// 环境类型
export type EnvironmentType = 'development' | 'test' | 'production'

// 获取当前环境
export function getCurrentEnvironment(): EnvironmentType {
  if (import.meta.env.DEV) {
    return 'development'
  }
  if (import.meta.env.PROD) {
    // 可以根据其他环境变量进一步区分测试和生产环境
    // 例如:import.meta.env.VITE_ENV === 'test'
    return 'production'
  }
  return 'development'
}

// 检查是否为开发环境
export function isDevelopment(): boolean {
  return getCurrentEnvironment() === 'development'
}

// 检查是否为测试环境
export function isTest(): boolean {
  return getCurrentEnvironment() === 'test'
}

// 检查是否为生产环境
export function isProduction(): boolean {
  return getCurrentEnvironment() === 'production'
}

// 根据环境获取配置
export function getConfigByEnvironment<T>(configs: {
  development: T
  test: T
  production: T
}): T {
  const env = getCurrentEnvironment()
  return configs[env]
}

3. 核心功能实现

3.1 用户认证和授权系统

认证系统实现
typescript 复制代码
// src/stores/auth.ts
// 认证状态管理
// 设计意图:管理用户认证状态,提供登录、登出等功能
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { login, logout, refreshToken, getUserInfo } from '@/services/auth'
import type { User, LoginRequest, LoginResponse } from '@/types/auth'

export const useAuthStore = defineStore('auth', () => {
  // 状态
  const user = ref<User | null>(null)
  const token = ref<string | null>(null)
  const refreshTokenValue = ref<string | null>(null)
  const isLoading = ref(false)
  const error = ref<string | null>(null)

  // Getters
  const isAuthenticated = computed(() => !!token.value)
  const userRole = computed(() => user.value?.role || 'guest')

  // Actions
  async function loginAction(credentials: LoginRequest): Promise<void> {
    isLoading.value = true
    error.value = null
    
    try {
      const response = await login(credentials)
      token.value = response.accessToken
      refreshTokenValue.value = response.refreshToken
      
      // 获取用户信息
      const userInfo = await getUserInfo()
      user.value = userInfo
      
      // 存储到本地存储
      localStorage.setItem('token', response.accessToken)
      localStorage.setItem('refreshToken', response.refreshToken)
    } catch (err) {
      error.value = err instanceof Error ? err.message : '登录失败'
      throw err
    } finally {
      isLoading.value = false
    }
  }

  async function logoutAction(): Promise<void> {
    isLoading.value = true
    
    try {
      // 调用登出接口
      await logout()
    } catch (err) {
      console.error('登出失败:', err)
    } finally {
      // 清理状态
      user.value = null
      token.value = null
      refreshTokenValue.value = null
      
      // 清理本地存储
      localStorage.removeItem('token')
      localStorage.removeItem('refreshToken')
      
      isLoading.value = false
    }
  }

  async function refreshTokenAction(): Promise<void> {
    if (!refreshTokenValue.value) return
    
    try {
      const response = await refreshToken(refreshTokenValue.value)
      token.value = response.accessToken
      refreshTokenValue.value = response.refreshToken
      
      // 存储到本地存储
      localStorage.setItem('token', response.accessToken)
      localStorage.setItem('refreshToken', response.refreshToken)
    } catch (err) {
      console.error('刷新令牌失败:', err)
      // 刷新失败,执行登出
      await logoutAction()
    }
  }

  // 初始化:从本地存储恢复状态
  function initialize(): void {
    const storedToken = localStorage.getItem('token')
    const storedRefreshToken = localStorage.getItem('refreshToken')
    
    if (storedToken && storedRefreshToken) {
      token.value = storedToken
      refreshTokenValue.value = storedRefreshToken
      
      // 恢复用户信息
      getUserInfo().then(userInfo => {
        user.value = userInfo
      }).catch(err => {
        console.error('恢复用户信息失败:', err)
        // 恢复失败,执行登出
        logoutAction()
      })
    }
  }

  return {
    // 状态
    user,
    token,
    isLoading,
    error,
    // Getters
    isAuthenticated,
    userRole,
    // Actions
    loginAction,
    logoutAction,
    refreshTokenAction,
    initialize
  }
})
授权系统实现
typescript 复制代码
// src/utils/permission.ts
// 权限管理工具
// 设计意图:管理用户权限,提供权限检查和路由守卫功能

// 角色类型
export type Role = 'admin' | 'user' | 'guest'

// 权限类型
export type Permission = 'read' | 'write' | 'delete' | 'admin'

// 角色权限映射
const rolePermissions: Record<Role, Permission[]> = {
  admin: ['read', 'write', 'delete', 'admin'],
  user: ['read', 'write'],
  guest: ['read']
}

// 检查用户是否有指定权限
export function hasPermission(role: Role, permission: Permission): boolean {
  const permissions = rolePermissions[role]
  return permissions.includes(permission)
}

// 检查用户是否有指定角色
export function hasRole(role: Role, requiredRoles: Role[]): boolean {
  return requiredRoles.includes(role)
}

// 权限检查函数
export function checkPermission(requiredPermission: Permission): boolean {
  // 从认证存储获取用户角色
  const authStore = useAuthStore()
  const userRole = authStore.userRole
  return hasPermission(userRole, requiredPermission)
}

// 角色检查函数
export function checkRole(requiredRoles: Role[]): boolean {
  // 从认证存储获取用户角色
  const authStore = useAuthStore()
  const userRole = authStore.userRole
  return hasRole(userRole, requiredRoles)
}

// 导入认证存储
import { useAuthStore } from '@/stores/auth'

3.2 数据管理和状态同步

Pinia 状态管理
typescript 复制代码
// src/stores/user.ts
// 用户状态管理
// 设计意图:管理用户相关的状态和操作
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { getUserList, getUserById, createUser, updateUser, deleteUser } from '@/services/user'
import type { User, CreateUserRequest, UpdateUserRequest } from '@/types/user'

export const useUserStore = defineStore('user', () => {
  // 状态
  const users = ref<User[]>([])
  const currentUser = ref<User | null>(null)
  const isLoading = ref(false)
  const error = ref<string | null>(null)
  const total = ref(0)
  const page = ref(1)
  const pageSize = ref(10)

  // Getters
  const userList = computed(() => users.value)
  const userCount = computed(() => total.value)
  const hasMore = computed(() => (page.value - 1) * pageSize.value < total.value)

  // Actions
  async function fetchUsers(pageNum: number = 1, pageSizeNum: number = 10): Promise<void> {
    isLoading.value = true
    error.value = null
    
    try {
      page.value = pageNum
      pageSize.value = pageSizeNum
      
      const response = await getUserList(pageNum, pageSizeNum)
      users.value = response.data
      total.value = response.total
    } catch (err) {
      error.value = err instanceof Error ? err.message : '获取用户列表失败'
    } finally {
      isLoading.value = false
    }
  }

  async function fetchUserById(id: number): Promise<void> {
    isLoading.value = true
    error.value = null
    
    try {
      const user = await getUserById(id)
      currentUser.value = user
    } catch (err) {
      error.value = err instanceof Error ? err.message : '获取用户详情失败'
    } finally {
      isLoading.value = false
    }
  }

  async function createUserAction(userData: CreateUserRequest): Promise<void> {
    isLoading.value = true
    error.value = null
    
    try {
      const user = await createUser(userData)
      users.value.push(user)
      total.value++
    } catch (err) {
      error.value = err instanceof Error ? err.message : '创建用户失败'
      throw err
    } finally {
      isLoading.value = false
    }
  }

  async function updateUserAction(id: number, userData: UpdateUserRequest): Promise<void> {
    isLoading.value = true
    error.value = null
    
    try {
      const user = await updateUser(id, userData)
      const index = users.value.findIndex(u => u.id === id)
      if (index !== -1) {
        users.value[index] = user
      }
      if (currentUser.value?.id === id) {
        currentUser.value = user
      }
    } catch (err) {
      error.value = err instanceof Error ? err.message : '更新用户失败'
      throw err
    } finally {
      isLoading.value = false
    }
  }

  async function deleteUserAction(id: number): Promise<void> {
    isLoading.value = true
    error.value = null
    
    try {
      await deleteUser(id)
      users.value = users.value.filter(u => u.id !== id)
      total.value--
      if (currentUser.value?.id === id) {
        currentUser.value = null
      }
    } catch (err) {
      error.value = err instanceof Error ? err.message : '删除用户失败'
      throw err
    } finally {
      isLoading.value = false
    }
  }

  return {
    // 状态
    users,
    currentUser,
    isLoading,
    error,
    total,
    page,
    pageSize,
    // Getters
    userList,
    userCount,
    hasMore,
    // Actions
    fetchUsers,
    fetchUserById,
    createUserAction,
    updateUserAction,
    deleteUserAction
  }
})
API 服务层实现
typescript 复制代码
// src/services/api.ts
// API 服务基础配置
// 设计意图:统一配置 API 请求,处理认证、错误等通用逻辑
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import config from '@/utils/env'
import { useAuthStore } from '@/stores/auth'

// 创建 axios 实例
const apiClient: AxiosInstance = axios.create({
  baseURL: config.apiBaseUrl,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
})

// 请求拦截器
apiClient.interceptors.request.use(
  (config) => {
    // 从认证存储获取 token
    const authStore = useAuthStore()
    const token = authStore.token
    
    // 添加认证头
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
apiClient.interceptors.response.use(
  (response) => {
    return response.data
  },
  async (error) => {
    const authStore = useAuthStore()
    const originalRequest = error.config
    
    // 处理 401 错误(未认证)
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true
      
      try {
        // 尝试刷新 token
        await authStore.refreshTokenAction()
        
        // 重新发送请求
        originalRequest.headers.Authorization = `Bearer ${authStore.token}`
        return apiClient(originalRequest)
      } catch (refreshError) {
        // 刷新 token 失败,执行登出
        await authStore.logoutAction()
        return Promise.reject(refreshError)
      }
    }
    
    // 处理其他错误
    return Promise.reject(error)
  }
)

// API 请求方法
export const api = {
  get: <T = any>(url: string, config?: AxiosRequestConfig) => 
    apiClient.get<T, T>(url, config),
  
  post: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) => 
    apiClient.post<T, T>(url, data, config),
  
  put: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) => 
    apiClient.put<T, T>(url, data, config),
  
  delete: <T = any>(url: string, config?: AxiosRequestConfig) => 
    apiClient.delete<T, T>(url, config),
  
  patch: <T = any>(url: string, data?: any, config?: AxiosRequestConfig) => 
    apiClient.patch<T, T>(url, data, config)
}

export default api

3.3 表单处理和验证

表单验证实现
typescript 复制代码
// src/composables/useForm.ts
// 表单处理组合式函数
// 设计意图:提供表单状态管理、验证和提交功能
import { ref, reactive, computed, type Ref } from 'vue'

// 表单字段接口
interface FormField {
  value: any
  error: string | null
  touched: boolean
  // 其他字段属性...
}

// 表单选项接口
interface FormOptions {
  initialValues: Record<string, any>
  validationSchema?: Record<string, (value: any) => string | null>
  onSubmit: (values: Record<string, any>) => Promise<void>
}

// 表单组合式函数
export function useForm(options: FormOptions) {
  // 表单字段
  const fields = reactive<Record<string, FormField>>(
    Object.fromEntries(
      Object.entries(options.initialValues).map(([key, value]) => [
        key,
        {
          value,
          error: null,
          touched: false
        }
      ])
    )
  )

  // 表单状态
  const isSubmitting = ref(false)
  const submitError = ref<string | null>(null)
  const isDirty = ref(false)

  // 计算属性:表单值
  const values = computed(() => {
    return Object.fromEntries(
      Object.entries(fields).map(([key, field]) => [key, field.value])
    )
  })

  // 计算属性:表单是否有效
  const isValid = computed(() => {
    return Object.values(fields).every(field => !field.error)
  })

  // 计算属性:表单是否被触摸
  const isTouched = computed(() => {
    return Object.values(fields).some(field => field.touched)
  })

  // 验证字段
  function validateField(fieldName: string): boolean {
    const field = fields[fieldName]
    if (!field) return true
    
    const validationSchema = options.validationSchema
    if (validationSchema && validationSchema[fieldName]) {
      field.error = validationSchema[fieldName](field.value)
    } else {
      field.error = null
    }
    
    field.touched = true
    return !field.error
  }

  // 验证整个表单
  function validateForm(): boolean {
    let isValid = true
    
    Object.keys(fields).forEach(fieldName => {
      const fieldIsValid = validateField(fieldName)
      if (!fieldIsValid) {
        isValid = false
      }
    })
    
    return isValid
  }

  // 处理字段变化
  function handleFieldChange(fieldName: string, value: any): void {
    const field = fields[fieldName]
    if (!field) return
    
    field.value = value
    isDirty.value = true
    
    // 实时验证
    validateField(fieldName)
  }

  // 处理字段触摸
  function handleFieldBlur(fieldName: string): void {
    const field = fields[fieldName]
    if (!field) return
    
    field.touched = true
    validateField(fieldName)
  }

  // 提交表单
  async function submitForm(): Promise<void> {
    // 验证表单
    if (!validateForm()) {
      return
    }
    
    isSubmitting.value = true
    submitError.value = null
    
    try {
      await options.onSubmit(values.value)
      isDirty.value = false
    } catch (error) {
      submitError.value = error instanceof Error ? error.message : '提交失败'
      throw error
    } finally {
      isSubmitting.value = false
    }
  }

  // 重置表单
  function resetForm(values?: Record<string, any>): void {
    const resetValues = values || options.initialValues
    
    Object.keys(fields).forEach(fieldName => {
      fields[fieldName].value = resetValues[fieldName]
      fields[fieldName].error = null
      fields[fieldName].touched = false
    })
    
    isDirty.value = false
    submitError.value = null
  }

  return {
    // 表单字段
    fields,
    // 表单状态
    isSubmitting,
    submitError,
    isDirty,
    // 计算属性
    values,
    isValid,
    isTouched,
    // 方法
    validateField,
    validateForm,
    handleFieldChange,
    handleFieldBlur,
    submitForm,
    resetForm
  }
}
表单组件实现
vue 复制代码
<!-- src/components/FormInput.vue -->
<template>
  <div class="form-field">
    <label v-if="label" :for="id">{{ label }}</label>
    <input
      :id="id"
      :type="type"
      :value="modelValue"
      @input="handleInput"
      @blur="handleBlur"
      :class="{ 'is-invalid': error }"
    />
    <div v-if="error" class="error-message">{{ error }}</div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'

// 组件属性
const props = defineProps<{
  modelValue: any
  label?: string
  type?: string
  id?: string
  error?: string | null
}>()

// 事件
const emit = defineEmits<{
  (e: 'update:modelValue', value: any): void
  (e: 'blur'): void
}>()

// 计算属性:输入框类型
const type = computed(() => props.type || 'text')

// 计算属性:输入框 ID
const id = computed(() => props.id || `input-${Math.random().toString(36).substr(2, 9)}`)

// 处理输入
function handleInput(event: Event): void {
  const target = event.target as HTMLInputElement
  emit('update:modelValue', target.value)
}

// 处理失焦
function handleBlur(): void {
  emit('blur')
}
</script>

<style scoped>
.form-field {
  margin-bottom: 1rem;
}

label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
}

input {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
}

input.is-invalid {
  border-color: #dc3545;
}

.error-message {
  color: #dc3545;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}
</style>

4. 团队协作和代码规范

4.1 Git 工作流和分支策略

Git 工作流配置
bash 复制代码
# .gitignore
# Git 忽略文件配置
# 设计意图:忽略不需要版本控制的文件和目录

# 依赖目录
node_modules/

# 构建输出
/dist
/build

# 环境文件
.env
.env.local
.env.*.local

# IDE 和编辑器文件
.vscode/
.idea/
*.swp
*.swo
*~

# 操作系统文件
.DS_Store
Thumbs.db

# 日志文件
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# 临时文件
*.tmp
*.temp
Git 分支策略
typescript 复制代码
// 分支策略说明
// 设计意图:定义 Git 分支的使用规范,确保团队协作的顺畅

/*
  Git 分支策略

  1. 主分支
  - main:生产环境分支,保持稳定
  - develop:开发分支,集成所有功能开发

  2. 功能分支
  - feature/xxx:新功能开发分支,从 develop 分支创建
  - 命名规范:feature/功能名称(小写,用连字符分隔)
  - 示例:feature/user-authentication

  3. 修复分支
  - fix/xxx:bug 修复分支,从 develop 分支创建
  - hotfix/xxx:紧急修复分支,从 main 分支创建
  - 命名规范:fix/修复名称 或 hotfix/修复名称
  - 示例:fix/login-error, hotfix/performance-issue

  4. 发布分支
  - release/xxx:发布准备分支,从 develop 分支创建
  - 命名规范:release/版本号
  - 示例:release/1.0.0

  5. 分支操作流程
  - 从正确的源分支创建新分支
  - 开发完成后提交代码
  - 创建 Pull Request 到目标分支
  - 代码审查通过后合并
  - 合并后删除临时分支
*/

4.2 代码审查和质量保证

ESLint 配置
typescript 复制代码
// .eslintrc.js
// ESLint 配置
// 设计意图:定义代码风格和质量规则,确保代码一致性
module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/vue3-essential',
    '@vue/typescript/recommended',
    '@vue/prettier',
    '@vue/prettier/@typescript-eslint'
  ],
  parserOptions: {
    ecmaVersion: 2020
  },
  rules: {
    // 类型检查规则
    '@typescript-eslint/no-explicit-any': 'error',
    '@typescript-eslint/explicit-module-boundary-types': 'error',
    '@typescript-eslint/no-unused-vars': ['error', {
      argsIgnorePattern: '^_',
      varsIgnorePattern: '^_'
    }],
    
    // Vue 规则
    'vue/multi-word-component-names': 'off',
    'vue/require-default-prop': 'off',
    
    // 代码风格规则
    'prettier/prettier': ['error', {
      singleQuote: true,
      semi: false,
      trailingComma: 'es5',
      tabWidth: 2,
      useTabs: false
    }],
    
    // 其他规则
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn'
  }
}
Prettier 配置
typescript 复制代码
// .prettierrc
// Prettier 配置
// 设计意图:定义代码格式化规则,确保代码风格一致性
{
  "singleQuote": true,
  "semi": false,
  "trailingComma": "es5",
  "tabWidth": 2,
  "useTabs": false,
  "printWidth": 80,
  "arrowParens": "avoid"
}

4.3 团队开发规范和工具链

开发规范
typescript 复制代码
// 开发规范说明
// 设计意图:定义团队开发的规范和最佳实践,确保代码质量和一致性

/*
  团队开发规范

  1. 代码风格
  - 使用 TypeScript 编写所有代码
  - 使用 Prettier 格式化代码
  - 遵循 ESLint 规则
  - 使用单引号,不使用分号
  - 缩进使用 2 个空格

  2. 命名规范
  - 组件名:大驼峰命名,例如 UserProfile
  - 变量名:小驼峰命名,例如 userName
  - 常量名:全大写,使用下划线分隔,例如 API_BASE_URL
  - 类型名:大驼峰命名,例如 UserType
  - 接口名:大驼峰命名,以 I 开头,例如 IUser
  - 函数名:小驼峰命名,例如 getUserInfo

  3. 组件开发
  - 使用组合式 API (setup script)
  - 组件 props 和 events 使用 TypeScript 类型
  - 组件逻辑复杂时,使用组合式函数拆分
  - 组件命名使用语义化名称

  4. 状态管理
  - 使用 Pinia 进行状态管理
  - 按功能模块划分 store
  - 状态管理逻辑清晰,避免过度使用

  5. API 调用
  - 使用 axios 进行 HTTP 请求
  - 统一 API 服务层,处理认证、错误等通用逻辑
  - API 调用使用 async/await

  6. 错误处理
  - 使用 try/catch 捕获异常
  - 统一错误处理逻辑
  - 对用户友好的错误提示

  7. 注释规范
  - 代码文件顶部添加文件说明
  - 复杂逻辑添加注释
  - 函数添加 JSDoc 注释
  - 组件添加 props 和 events 注释
*/
工具链配置
typescript 复制代码
// package.json
// 项目配置和脚本
// 设计意图:定义项目依赖、脚本和配置,统一开发工具链
{
  "name": "my-enterprise-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "build:dev": "vite build --mode development",
    "build:test": "vite build --mode test",
    "build:prod": "vite build --mode production",
    "preview": "vite preview",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
    "format": "prettier --write .",
    "type-check": "vue-tsc --noEmit",
    "test": "vitest",
    "test:coverage": "vitest run --coverage",
    "e2e": "cypress run"
  },
  "dependencies": {
    "vue": "^3.3.4",
    "vue-router": "^4.2.4",
    "pinia": "^2.1.6",
    "axios": "^1.4.0"
  },
  "devDependencies": {
    "@types/node": "^20.3.1",
    "@vitejs/plugin-vue": "^4.2.3",
    "@vue/eslint-config-prettier": "^8.0.0",
    "@vue/eslint-config-typescript": "^12.0.0",
    "@vue/test-utils": "^2.4.1",
    "@vue/tsconfig": "^0.4.0",
    "cypress": "^12.17.4",
    "eslint": "^8.43.0",
    "eslint-plugin-vue": "^9.15.1",
    "prettier": "^3.0.0",
    "typescript": "~5.1.6",
    "vite": "^4.3.9",
    "vite-plugin-checker": "^0.6.1",
    "vitest": "^0.33.0",
    "vue-tsc": "^1.8.8"
  }
}

5. 测试和质量保证

5.1 单元测试和集成测试策略

单元测试配置
typescript 复制代码
// vite.config.ts (添加 Vitest 配置)
// Vitest 配置
// 设计意图:配置单元测试环境,支持 TypeScript 和 Vue 组件测试
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

// 路径别名配置
const pathResolve = (path: string) => {
  return resolve(__dirname, path)
}

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': pathResolve('./src'),
      // 其他别名...
    }
  },
  // Vitest 配置
  test: {
    globals: true,
    environment: 'happy-dom',
    coverage: {
      reporter: ['text', 'json', 'html'],
      include: ['src/**/*.{ts,vue}'],
      exclude: ['src/main.ts', 'src/router/**', 'src/stores/**'],
      // 覆盖率阈值
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 70,
        statements: 80
      }
    }
  }
})
单元测试示例
typescript 复制代码
// src/components/Button.test.ts
// 按钮组件单元测试
// 设计意图:测试按钮组件的功能和行为
import { mount } from '@vue/test-utils'
import Button from './Button.vue'

describe('Button component', () => {
  // 测试按钮渲染
  test('renders button with text', () => {
    const wrapper = mount(Button, {
      props: {
        label: 'Click me'
      }
    })
    
    expect(wrapper.text()).toBe('Click me')
  })

  // 测试按钮类型
  test('applies correct type class', () => {
    const wrapper = mount(Button, {
      props: {
        label: 'Click me',
        type: 'primary'
      }
    })
    
    expect(wrapper.classes()).toContain('btn-primary')
  })

  // 测试按钮点击事件
  test('emits click event when clicked', async () => {
    const wrapper = mount(Button, {
      props: {
        label: 'Click me'
      }
    })
    
    await wrapper.trigger('click')
    expect(wrapper.emitted('click')).toBeTruthy()
  })

  // 测试按钮禁用状态
  test('does not emit click event when disabled', async () => {
    const wrapper = mount(Button, {
      props: {
        label: 'Click me',
        disabled: true
      }
    })
    
    await wrapper.trigger('click')
    expect(wrapper.emitted('click')).toBeFalsy()
    expect(wrapper.classes()).toContain('btn-disabled')
  })
})

5.2 E2E 测试和性能测试

Cypress E2E 测试
typescript 复制代码
// cypress.config.ts
// Cypress 配置
// 设计意图:配置 E2E 测试环境,支持自动化测试
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
    supportFile: 'cypress/support/commands.ts',
    fixturesFolder: 'cypress/fixtures',
    screenshotsFolder: 'cypress/screenshots',
    videosFolder: 'cypress/videos'
  }
})
E2E 测试示例
typescript 复制代码
// cypress/e2e/login.cy.ts
// 登录功能 E2E 测试
// 设计意图:测试登录功能的完整流程

describe('Login functionality', () => {
  beforeEach(() => {
    // 访问登录页面
    cy.visit('/login')
  })

  // 测试登录成功
  it('should login successfully with valid credentials', () => {
    // 输入用户名和密码
    cy.get('input[name="username"]').type('admin')
    cy.get('input[name="password"]').type('password123')
    
    // 点击登录按钮
    cy.get('button[type="submit"]').click()
    
    // 验证登录成功后跳转到首页
    cy.url().should('include', '/')
    cy.contains('Welcome, admin').should('be.visible')
  })

  // 测试登录失败
  it('should show error message with invalid credentials', () => {
    // 输入无效的用户名和密码
    cy.get('input[name="username"]').type('invalid')
    cy.get('input[name="password"]').type('invalid')
    
    // 点击登录按钮
    cy.get('button[type="submit"]').click()
    
    // 验证错误消息
    cy.contains('Invalid username or password').should('be.visible')
  })

  // 测试表单验证
  it('should show validation errors for empty fields', () => {
    // 直接点击登录按钮
    cy.get('button[type="submit"]').click()
    
    // 验证表单验证错误
    cy.contains('Username is required').should('be.visible')
    cy.contains('Password is required').should('be.visible')
  })
})

5.3 测试覆盖率和质量指标

测试覆盖率配置
typescript 复制代码
// vitest.config.ts (单独的 Vitest 配置)
// 测试覆盖率配置
// 设计意图:详细配置测试覆盖率报告,确保代码质量
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

// 路径别名配置
const pathResolve = (path: string) => {
  return resolve(__dirname, path)
}

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': pathResolve('./src'),
      // 其他别名...
    }
  },
  test: {
    globals: true,
    environment: 'happy-dom',
    coverage: {
      reporter: ['text', 'json', 'html', 'lcov'],
      include: ['src/**/*.{ts,vue}'],
      exclude: [
        'src/main.ts',
        'src/router/**',
        'src/stores/**',
        'src/types/**',
        'src/constants/**'
      ],
      // 覆盖率阈值
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 70,
        statements: 80
      },
      // 报告目录
      reportsDirectory: './coverage'
    }
  }
})
质量指标监控
typescript 复制代码
// .github/workflows/quality-check.yml
// GitHub Actions 质量检查工作流
// 设计意图:自动化检查代码质量和测试覆盖率
name: Quality Check

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  quality-check:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Type check
        run: npm run type-check
      
      - name: Lint
        run: npm run lint
      
      - name: Test
        run: npm run test:coverage
      
      - name: Build
        run: npm run build
      
      - name: Upload coverage report
        uses: actions/upload-artifact@v3
        with:
          name: coverage
          path: coverage
          retention-days: 7

6. 部署和监控

6.1 CI/CD 流程搭建

GitHub Actions 配置
typescript 复制代码
// .github/workflows/ci-cd.yml
// CI/CD 工作流配置
// 设计意图:自动化构建、测试和部署流程
name: CI/CD

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]
  release:
    types: [ created ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Type check
        run: npm run type-check
      
      - name: Lint
        run: npm run lint
      
      - name: Test
        run: npm run test:coverage
      
      - name: Build
        run: npm run build
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build
          path: dist
          retention-days: 7
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.event_name == 'release' && github.event.action == 'created'
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: build
          path: dist
      
      - name: Deploy to production
        uses: easingthemes/ssh-deploy@v2
        with:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          ARGS: '-rltgoDzvO --delete'
          SOURCE: 'dist/'
          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
          REMOTE_USER: ${{ secrets.REMOTE_USER }}
          REMOTE_PORT: ${{ secrets.REMOTE_PORT }}
          TARGET: ${{ secrets.REMOTE_TARGET }}

6.2 应用部署策略

部署配置
typescript 复制代码
// nginx.conf
// Nginx 配置
// 设计意图:配置 Web 服务器,支持 Vue 应用的部署

/*
  server {
    listen 80;
    server_name example.com;
    
    # 重定向 HTTP 到 HTTPS
    return 301 https://$host$request_uri;
  }
  
  server {
    listen 443 ssl;
    server_name example.com;
    
    # SSL 配置
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # 根目录
    root /var/www/my-enterprise-app;
    
    # 索引文件
    index index.html;
    
    # 位置配置
    location / {
      try_files $uri $uri/ /index.html;
    }
    
    # API 代理
    location /api {
      proxy_pass http://localhost:8080;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_cache_bypass $http_upgrade;
    }
    
    # 静态资源缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
      expires 30d;
      add_header Cache-Control "public, max-age=2592000, immutable";
    }
    
    # 日志配置
    access_log /var/log/nginx/my-enterprise-app.access.log;
    error_log /var/log/nginx/my-enterprise-app.error.log;
  }
*/
Docker 部署
typescript 复制代码
// Dockerfile
// Docker 配置
// 设计意图:容器化应用,便于部署和扩展
# 基础镜像
FROM node:18-alpine as build

# 工作目录
WORKDIR /app

# 复制 package.json 和 package-lock.json
COPY package*.json ./

# 安装依赖
RUN npm ci

# 复制源代码
COPY . .

# 构建应用
RUN npm run build

# 生产环境镜像
FROM nginx:alpine

# 复制构建产物
COPY --from=build /app/dist /usr/share/nginx/html

# 复制 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 暴露端口
EXPOSE 80

# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

// docker-compose.yml
// Docker Compose 配置
// 设计意图:定义多容器应用,包括前端、后端和数据库
version: '3.8'

services:
  frontend:
    build: .
    ports:
      - "80:80"
    depends_on:
      - backend
    restart: always
  
  backend:
    image: my-enterprise-backend:latest
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=database
      - DB_PORT=5432
      - DB_USER=admin
      - DB_PASSWORD=password
      - DB_NAME=myapp
    depends_on:
      - database
    restart: always
  
  database:
    image: postgres:14-alpine
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=myapp
    volumes:
      - postgres-data:/var/lib/postgresql/data
    restart: always

volumes:
  postgres-data:

6.3 生产环境监控和告警

应用监控配置
typescript 复制代码
// src/utils/monitoring.ts
// 应用监控工具
// 设计意图:监控应用性能和错误,及时发现问题
import * as Sentry from '@sentry/vue'
import { BrowserTracing } from '@sentry/tracing'
import config from '@/utils/env'

// 初始化监控
function initMonitoring(app: any) {
  if (config.debug) {
    return // 开发环境不启用监控
  }
  
  Sentry.init({
    app,
    dsn: 'YOUR_SENTRY_DSN',
    integrations: [
      new BrowserTracing({
        routingInstrumentation: Sentry.vueRouterInstrumentation(router),
        tracingOrigins: ['localhost', 'api.example.com', /^https:\/\/yourdomain\.com\//]
      })
    ],
    // 采样率
    tracesSampleRate: 1.0,
    // 环境
    environment: import.meta.env.MODE,
    // 发布版本
    release: 'my-enterprise-app@' + import.meta.env.VITE_APP_VERSION
  })
}

// 捕获错误
export function captureError(error: any, context?: any) {
  Sentry.captureException(error, {
    contexts: context
  })
}

// 捕获消息
export function captureMessage(message: string, level?: Sentry.SeverityLevel) {
  Sentry.captureMessage(message, level)
}

// 性能监控
export function startTransaction(name: string, operation: string) {
  return Sentry.startTransaction({ name, operation })
}

export default {
  initMonitoring,
  captureError,
  captureMessage,
  startTransaction
}

// 使用监控
// import monitoring from '@/utils/monitoring'
// monitoring.initMonitoring(app)
// monitoring.captureError(error)
// monitoring.captureMessage('User logged in', 'info')
服务器监控配置
typescript 复制代码
// prometheus.yml
// Prometheus 配置
// 设计意图:监控服务器和应用的运行状态

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'nginx'
    static_configs:
      - targets: ['nginx-exporter:9113']

  - job_name: 'app'
    static_configs:
      - targets: ['app:3000']

// grafana-dashboard.json
// Grafana 仪表板配置
// 设计意图:可视化监控数据,提供直观的监控界面
/*
  {
    "dashboard": {
      "id": null,
      "title": "My Enterprise App Dashboard",
      "tags": ["production"],
      "timezone": "browser",
      "schemaVersion": 26,
      "version": 0,
      "refresh": "10s",
      "panels": [
        {
          "title": "Server CPU Usage",
          "type": "graph",
          "datasource": "Prometheus",
          "targets": [
            {
              "expr": "100 - (avg by(instance) (irate(node_cpu_seconds_total{mode='idle'}[5m])) * 100)",
              "legendFormat": "{{instance}}",
              "refId": "A"
            }
          ]
        },
        {
          "title": "Server Memory Usage",
          "type": "graph",
          "datasource": "Prometheus",
          "targets": [
            {
              "expr": "(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100",
              "legendFormat": "{{instance}}",
              "refId": "A"
            }
          ]
        },
        {
          "title": "API Request Rate",
          "type": "graph",
          "datasource": "Prometheus",
          "targets": [
            {
              "expr": "rate(api_requests_total[5m])",
              "legendFormat": "{{endpoint}}",
              "refId": "A"
            }
          ]
        },
        {
          "title": "Error Rate",
          "type": "graph",
          "datasource": "Prometheus",
          "targets": [
            {
              "expr": "rate(api_errors_total[5m])",
              "legendFormat": "{{endpoint}}",
              "refId": "A"
            }
          ]
        }
      ]
    }
  }
*/

7. 总结

Vue 3 + TypeScript 企业级应用开发是一个复杂但有序的过程,需要从多个维度进行考虑和实践。本文从项目初始化和配置、核心功能实现、团队协作和代码规范、测试和质量保证、部署和监控五个方面,详细介绍了企业级应用开发的实战经验和最佳实践。

通过本文的学习,你应该能够:

  1. 搭建完整的项目脚手架:使用 Vite 初始化项目,配置必要的依赖和工具,设置合理的目录结构。

  2. 实现核心功能:开发用户认证和授权系统,管理应用状态,处理表单验证和 API 调用。

  3. 规范团队协作:制定 Git 工作流和分支策略,建立代码审查和质量保证机制,统一团队开发规范。

  4. 确保代码质量:编写单元测试和集成测试,进行 E2E 测试和性能测试,监控测试覆盖率和质量指标。

  5. 部署和监控应用:搭建 CI/CD 流程,配置应用部署策略,实现生产环境监控和告警。

企业级应用开发需要团队成员的密切协作和持续努力,希望本文提供的实战经验和最佳实践能够帮助你构建高质量、可维护的 Vue 3 + TypeScript 企业级应用。

8. 附录

8.1 开发工具推荐

  • 代码编辑器:VS Code
  • 浏览器:Chrome DevTools
  • 版本控制:Git
  • 项目管理:Jira、Trello
  • 团队协作:Slack、Microsoft Teams
  • CI/CD:GitHub Actions、Jenkins
  • 监控工具:Sentry、Prometheus、Grafana

8.2 资源推荐

8.3 常见问题解决方案

  1. TypeScript 类型错误:检查类型定义,使用类型断言或泛型解决。

  2. API 调用失败:检查网络连接,验证 API 地址和认证信息,查看服务器日志。

  3. 性能问题:使用 Chrome DevTools 分析性能瓶颈,优化渲染和计算逻辑,使用虚拟滚动和代码分割。

  4. 部署失败:检查服务器配置,验证 SSH 密钥和权限,查看部署日志。

  5. 监控告警:分析告警原因,查看应用日志,及时修复问题。

通过不断学习和实践,你将能够掌握 Vue 3 + TypeScript 企业级应用开发的精髓,构建出高质量、高性能的应用。

相关推荐
mCell4 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell5 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭5 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清5 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
萧曵 丶5 小时前
Vue 中父子组件之间最常用的业务交互场景
javascript·vue.js·交互
银烛木5 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076605 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声5 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易5 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得06 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化