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 企业级应用开发是一个复杂但有序的过程,需要从多个维度进行考虑和实践。本文从项目初始化和配置、核心功能实现、团队协作和代码规范、测试和质量保证、部署和监控五个方面,详细介绍了企业级应用开发的实战经验和最佳实践。
通过本文的学习,你应该能够:
-
搭建完整的项目脚手架:使用 Vite 初始化项目,配置必要的依赖和工具,设置合理的目录结构。
-
实现核心功能:开发用户认证和授权系统,管理应用状态,处理表单验证和 API 调用。
-
规范团队协作:制定 Git 工作流和分支策略,建立代码审查和质量保证机制,统一团队开发规范。
-
确保代码质量:编写单元测试和集成测试,进行 E2E 测试和性能测试,监控测试覆盖率和质量指标。
-
部署和监控应用:搭建 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 资源推荐
- Vue 3 官方文档
- TypeScript 官方文档
- Vite 官方文档
- Pinia 官方文档
- Vue Router 官方文档
- Axios 官方文档
- ESLint 官方文档
- Prettier 官方文档
- Vitest 官方文档
- Cypress 官方文档
8.3 常见问题解决方案
-
TypeScript 类型错误:检查类型定义,使用类型断言或泛型解决。
-
API 调用失败:检查网络连接,验证 API 地址和认证信息,查看服务器日志。
-
性能问题:使用 Chrome DevTools 分析性能瓶颈,优化渲染和计算逻辑,使用虚拟滚动和代码分割。
-
部署失败:检查服务器配置,验证 SSH 密钥和权限,查看部署日志。
-
监控告警:分析告警原因,查看应用日志,及时修复问题。
通过不断学习和实践,你将能够掌握 Vue 3 + TypeScript 企业级应用开发的精髓,构建出高质量、高性能的应用。