欢迎光临
我们一直在努力

Vue 3 + TypeScript 项目架构实践

Vue 3 + TypeScript 项目架构实践

1. 引言

Vue 3 + TypeScript + Vite + Pinia 技术栈已经成为现代前端开发的主流选择,其优秀的性能和开发体验备受开发者青睐。然而,要充分发挥这一技术栈的潜力,合理的项目架构设计至关重要。

一个良好的项目架构能够:

  • 提高代码的可维护性和可扩展性
  • 减少团队协作中的冲突和问题
  • 加速开发效率和代码质量
  • 便于测试和部署

本文将详细介绍 Vue 3 + TypeScript 项目的架构实践,包括项目结构设计、状态管理架构、路由架构、API 层设计、工具类和公共组件的组织,以及企业级项目案例分析。

2. 项目结构设计

2.1 目录结构组织

一个合理的目录结构是项目架构的基础,它能够清晰地分离不同功能模块,便于开发者理解和维护代码。

// 推荐的 Vue 3 + TypeScript 项目目录结构
// 该结构清晰分离了不同功能模块,便于开发者理解和维护代码
// 每个目录都有明确的职责和用途,遵循关注点分离原则
// 设计意图:通过模块化组织提高代码可维护性和可扩展性
src/ // 源代码目录
├── assets/ # 静态资源文件
│ ├── images/ # 图片资源(如产品图片、背景图等)
│ ├── styles/ # 全局样式(如重置样式、主题样式等)
│ └── icons/ # 图标资源(如 SVG 图标、字体图标等)
├── components/ # 公共组件
│ ├── common/ # 通用基础组件(如按钮、输入框等)
│ ├── layout/ # 布局相关组件(如头部、侧边栏等)
│ └── business/ # 业务相关组件(如用户卡片、产品列表等)
├── composables/ # 组合式 API 逻辑
│ ├── useAuth.ts # 认证相关逻辑(如登录、注册等)
│ ├── useApi.ts # API 调用逻辑(如请求封装、错误处理等)
│ └── useLocalStorage.ts # 本地存储逻辑(如数据持久化等)
├── constants/ # 常量定义
│ ├── api.ts # API 相关常量(如接口地址、请求超时等)
│ ├── routes.ts # 路由相关常量(如路由名称、路径等)
│ └── storage.ts # 存储相关常量(如存储键名、过期时间等)
├── enums/ # 枚举类型定义
│ ├── user.ts # 用户相关枚举(如用户角色、状态等)
│ └── status.ts # 状态相关枚举(如订单状态、审批状态等)
├── hooks/ # 自定义钩子(与 composables 类似,可根据团队习惯选择其一)
├── layouts/ # 布局组件
│ ├── DefaultLayout.vue # 默认布局(如包含侧边栏和头部的布局)
│ └── AuthLayout.vue # 认证布局(如登录、注册页面的布局)
├── models/ # 数据模型定义
│ ├── user.ts # 用户模型(如用户信息结构、类型定义等)
│ ├── product.ts # 产品模型(如产品信息结构、类型定义等)
│ └── common.ts # 通用模型(如分页结构、响应结构等)
├── router/ # 路由配置
│ ├── index.ts # 路由主配置(如创建路由实例、应用守卫等)
│ ├── routes.ts # 路由定义(如路由路径、组件映射等)
│ └── guards.ts # 路由守卫(如认证守卫、权限守卫等)
├── services/ # 服务层
│ ├── api/ # API 服务
│ │ ├── user.ts # 用户相关 API(如登录、获取用户信息等)
│ │ ├── product.ts # 产品相关 API(如获取产品列表、创建产品等)
│ │ └── index.ts # API 服务主文件(如创建 axios 实例、配置拦截器等)
│ └── utils/ # 工具服务(如第三方服务集成、业务工具等)
├── stores/ # Pinia 状态管理
│ ├── user.ts # 用户状态(如用户信息、认证状态等)
│ ├── product.ts # 产品状态(如产品列表、详情等)
│ └── common.ts # 通用状态(如全局加载状态、错误信息等)
├── types/ # TypeScript 类型定义
│ ├── api.ts # API 相关类型(如请求参数、响应类型等)
│ ├── components.ts # 组件相关类型(如 props 类型、事件类型等)
│ └── common.ts # 通用类型(如通用接口、工具类型等)
├── utils/ # 工具函数
│ ├── format.ts # 格式化工具(如日期格式化、金额格式化等)
│ ├── validation.ts # 验证工具(如邮箱验证、密码强度验证等)
│ └── storage.ts # 存储工具(如本地存储封装、会话存储封装等)
├── views/ # 页面组件
│ ├── auth/ # 认证相关页面
│ │ ├── Login.vue # 登录页面
│ │ └── Register.vue # 注册页面
│ ├── dashboard/ # 仪表盘页面
│ └── products/ # 产品相关页面
├── App.vue # 根组件(应用的入口组件)
├── main.ts # 应用入口(如初始化 Vue 应用、注册插件等)
└── env.d.ts # 环境变量类型定义(如 Vite 环境变量类型声明)

2.2 目录结构设计原则

  • 功能模块化:将相关功能的代码组织在一起,便于理解和维护
  • 关注点分离:将不同职责的代码分离到不同目录,如视图、组件、状态管理等
  • 层次清晰:建立清晰的代码层次结构,如 API 层、服务层、业务逻辑层等
  • 可扩展性:预留合理的扩展空间,便于后续功能的添加和修改
  • 命名规范:采用一致的命名规范,提高代码的可读性
  • 2.3 模块划分策略

    按业务功能划分

    将代码按照业务功能进行划分,每个功能模块包含完整的组件、状态、服务等。

    // 按业务功能划分的目录结构
    // 该结构将代码按照业务功能进行划分,每个功能模块包含完整的组件、状态、服务等
    // 优点:业务逻辑内聚,便于团队协作和代码维护
    // 缺点:可能导致某些通用代码重复

    // 适用场景:大型应用,多个团队负责不同业务模块
    // 设计意图:通过业务模块划分,实现团队协作的隔离和业务逻辑的内聚
    src/
    ├── modules/ # 业务模块目录(存放各个独立的业务模块)
    │ ├── auth/ # 认证模块(处理登录、注册等认证相关功能)
    │ │ ├── components/ # 认证相关组件(如登录表单、注册表单等)
    │ │ ├── views/ # 认证相关页面(如登录页面、注册页面等)
    │ │ ├── services/ # 认证相关服务(如认证 API 调用等)
    │ │ └── stores/ # 认证相关状态(如认证状态管理等)
    │ ├── product/ # 产品模块(处理产品相关功能)
    │ │ ├── components/ # 产品相关组件(如产品卡片、产品列表项等)
    │ │ ├── views/ # 产品相关页面(如产品列表页、产品详情页等)
    │ │ ├── services/ # 产品相关服务(如产品 API 调用等)
    │ │ └── stores/ # 产品相关状态(如产品列表状态、详情状态等)
    │ └── user/ # 用户模块(处理用户相关功能)
    │ ├── components/ # 用户相关组件(如用户信息卡片、用户列表项等)
    │ ├── views/ # 用户相关页面(如用户列表页、用户详情页等)
    │ ├── services/ # 用户相关服务(如用户 API 调用等)
    │ └── stores/ # 用户相关状态(如用户列表状态、详情状态等)
    └── shared/ # 共享资源(存放各个模块共用的代码)
    ├── components/ # 共享组件(如通用按钮、输入框等)
    ├── composables/ # 共享组合式 API(如通用认证逻辑、API 调用逻辑等)
    ├── utils/ # 共享工具函数(如格式化工具、验证工具等)
    └── types/ # 共享类型定义(如通用接口、类型声明等)

    按技术类型划分

    将代码按照技术类型进行划分,如组件、服务、状态管理等。

    // 按技术类型划分的目录结构
    // 该结构将代码按照技术类型进行划分,如组件、服务、状态管理等
    // 优点:技术职责清晰,便于代码复用和维护
    // 缺点:业务逻辑分散在不同目录,可能增加跨模块理解难度

    // 适用场景:中小型应用,团队成员技术栈全面
    // 设计意图:通过技术类型划分,实现代码的分类管理和复用
    src/
    ├── components/ # 所有组件(按技术类型组织的组件集合)
    ├── services/ # 所有服务(按技术类型组织的服务集合)
    ├── stores/ # 所有状态管理(按技术类型组织的状态管理集合)
    ├── views/ # 所有页面(按技术类型组织的页面集合)
    └── utils/ # 所有工具函数(按技术类型组织的工具函数集合)

    混合划分策略

    结合业务功能和技术类型的划分策略,在顶层按技术类型划分,在具体模块内按业务功能划分。

    // 混合划分策略的目录结构
    // 该结构结合业务功能和技术类型的划分策略,在顶层按技术类型划分,在具体模块内按业务功能划分
    // 优点:既保持了技术职责清晰,又保证了业务逻辑的内聚性
    // 缺点:目录结构可能相对复杂

    // 适用场景:中大型应用,需要平衡技术管理和业务逻辑
    // 设计意图:通过混合划分策略,兼顾技术管理的清晰性和业务逻辑的内聚性
    src/
    ├── components/ # 所有组件(顶层按技术类型划分)
    │ ├── common/ # 通用组件(如按钮、输入框等通用基础组件)
    │ ├── auth/ # 认证相关组件(如登录表单、注册表单等)
    │ └── product/ # 产品相关组件(如产品卡片、产品列表等)
    ├── services/ # 所有服务(顶层按技术类型划分)
    │ ├── api/ # API 服务(按业务功能划分的 API 服务)
    │ │ ├── auth.ts # 认证 API(处理认证相关的 API 调用)
    │ │ └── product.ts # 产品 API(处理产品相关的 API 调用)
    │ └── utils/ # 服务工具(如 API 请求封装、错误处理等)
    └── stores/ # 所有状态管理(顶层按技术类型划分)
    ├── auth.ts # 认证状态(处理认证相关的状态管理)
    └── product.ts # 产品状态(处理产品相关的状态管理)

    2.4 推荐的目录结构

    根据项目规模和团队习惯,推荐使用以下目录结构:

    // 推荐的目录结构
    // 该结构根据项目规模和团队习惯,综合考虑了技术管理和业务逻辑的平衡
    // 每个目录都有明确的职责,便于开发者理解和维护代码

    // 适用场景:大多数 Vue 3 + TypeScript 项目
    // 设计意图:提供一个通用的、可扩展的目录结构,适用于各种规模的项目
    src/
    ├── assets/ # 静态资源(如图片、样式、图标等)
    ├── components/ # 公共组件(如通用组件、业务组件等)
    ├── composables/ # 组合式 API(如认证逻辑、API 调用逻辑等)
    ├── constants/ # 常量定义(如 API 地址、路由名称等)
    ├── enums/ # 枚举类型(如用户角色、订单状态等)
    ├── layouts/ # 布局组件(如默认布局、认证布局等)
    ├── models/ # 数据模型(如用户模型、产品模型等)
    ├── router/ # 路由配置(如路由定义、守卫等)
    ├── services/ # 服务层(如 API 服务、工具服务等)
    ├── stores/ # Pinia 状态管理(如用户状态、产品状态等)
    ├── types/ # TypeScript 类型(如 API 类型、组件类型等)
    ├── utils/ # 工具函数(如格式化工具、验证工具等)
    ├── views/ # 页面组件(如认证页面、仪表盘页面等)
    ├── App.vue # 根组件(应用的入口组件)
    ├── main.ts # 入口文件(初始化 Vue 应用、注册插件等)
    └── env.d.ts # 环境变量类型(Vite 环境变量类型声明)

    3. 状态管理架构

    3.1 Pinia 状态管理实践

    Pinia 作为 Vue 3 官方推荐的状态管理库,提供了更简洁、更灵活的状态管理方案。

    状态管理结构设计
  • 按模块划分 Store:将状态按照业务模块进行划分,每个模块对应一个 Store
  • 使用 Composition API:采用 Composition API 风格定义 Store,提高代码的可读性和可维护性
  • 类型安全:充分利用 TypeScript 的类型系统,为 Store 定义明确的类型
  • Store 组织方式

    // stores/user.ts
    // 用户状态管理 Store
    // 该文件使用 Pinia 的 Composition API 风格定义用户相关的状态管理
    // 包含用户信息、认证状态、登录/登出等功能
    // 特点:类型安全,代码结构清晰,逻辑组织合理
    // 设计意图:通过 Pinia 管理用户认证状态和用户信息,实现状态的集中管理和持久化

    import { defineStore } from 'pinia'
    import { ref, computed } from 'vue'
    import { User } from '@/models/user'
    import { userApi } from '@/services/api/user'

    /**
    * 用户状态管理 Store
    * 管理用户认证状态、用户信息等
    * 使用 Composition API 风格定义,提供更好的类型推断和代码组织
    * @returns 用户状态管理 Store 实例
    */

    export const useUserStore = defineStore('user', () => {
    // 状态(State)
    const currentUser = ref<User | null>(null) // 当前用户信息,类型为 User 或 null
    const token = ref<string | null>(null) // 认证令牌,用于 API 调用的身份验证
    const loading = ref(false) // 加载状态,用于控制加载指示器的显示
    const error = ref<string | null>(null) // 错误信息,用于显示错误提示

    // Getters(计算属性)
    const isAuthenticated = computed(() => !!token.value) // 是否已认证,根据 token 是否存在判断
    const userDisplayName = computed(() => {
    return currentUser.value ? `${currentUser.value.firstName} ${currentUser.value.lastName}` : ''
    }) // 用户显示名称,组合 firstName 和 lastName

    // Actions(方法)
    /**
    * 用户登录
    * @param email 邮箱
    * @param password 密码
    * @returns 登录响应,包含用户信息和令牌
    * @throws 登录失败时抛出错误
    */

    async function login(email: string, password: string) {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    const response = await userApi.login({ email, password }) // 调用登录 API
    currentUser.value = response.user // 更新当前用户信息
    token.value = response.token // 更新认证令牌
    localStorage.setItem('token', response.token) // 持久化存储令牌到 localStorage
    return response // 返回登录响应
    } catch (err) {
    error.value = 'Login failed' // 设置错误信息
    throw err // 重新抛出错误,以便调用方处理
    } finally {
    loading.value = false // 结束加载
    }
    }

    /**
    * 用户登出
    * @throws 登出失败时抛出错误
    */

    async function logout() {
    loading.value = true // 开始加载
    try {
    await userApi.logout() // 调用登出 API
    currentUser.value = null // 清空当前用户信息
    token.value = null // 清空认证令牌
    localStorage.removeItem('token') // 清除 localStorage 中的令牌
    } catch (err) {
    error.value = 'Logout failed' // 设置错误信息
    throw err // 重新抛出错误,以便调用方处理
    } finally {
    loading.value = false // 结束加载
    }
    }

    /**
    * 获取当前用户信息
    * @returns 用户信息
    * @throws 获取失败时抛出错误
    */

    async function fetchCurrentUser() {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    const response = await userApi.getCurrentUser() // 调用获取用户信息 API
    currentUser.value = response // 更新当前用户信息
    return response // 返回用户信息
    } catch (err) {
    error.value = 'Failed to fetch user' // 设置错误信息
    throw err // 重新抛出错误,以便调用方处理
    } finally {
    loading.value = false // 结束加载
    }
    }

    /**
    * 初始化用户状态
    * 从本地存储恢复令牌并获取用户信息
    * 在应用启动时调用,用于保持用户登录状态
    */

    function initialize() {
    const storedToken = localStorage.getItem('token') // 从 localStorage 获取存储的令牌
    if (storedToken) {
    token.value = storedToken // 更新认证令牌
    fetchCurrentUser() // 获取用户信息
    }
    }

    return {
    // 状态(State)
    currentUser,
    token,
    loading,
    error,
    // Getters(计算属性)
    isAuthenticated,
    userDisplayName,
    // Actions(方法)
    login,
    logout,
    fetchCurrentUser,
    initialize
    }
    })

    3.2 跨 Store 通信策略

    在复杂的应用中,不同 Store 之间可能需要进行通信。以下是几种跨 Store 通信的策略:

    1. 直接引用其他 Store

    在一个 Store 中直接引用和使用另一个 Store。

    // stores/cart.ts
    // 购物车状态管理 Store
    // 该文件演示了如何通过直接引用其他 Store 来实现跨 Store 通信
    // 优点:代码简洁直接,易于理解
    // 缺点:Store 之间耦合度较高
    // 设计意图:演示跨 Store 通信的直接引用方法,适用于简单场景

    import { defineStore } from 'pinia'
    import { useUserStore } from './user'

    /**
    * 购物车状态管理 Store
    * 管理购物车商品、数量等
    * @returns 购物车状态管理 Store 实例
    */

    export const useCartStore = defineStore('cart', () => {
    // 直接引用用户 Store
    // 优点:代码简洁直接,易于理解
    // 缺点:Store 之间耦合度较高,可能导致循环依赖
    const userStore = useUserStore()

    /**
    * 获取用户购物车
    * 根据用户认证状态获取对应的购物车数据
    * 演示了如何使用其他 Store 的状态(isAuthenticated)
    */

    function getUserCart() {
    if (userStore.isAuthenticated) {
    // 获取登录用户的购物车
    // 当用户已认证时,获取与用户账号关联的购物车
    console.log('Getting cart for authenticated user')
    } else {
    // 获取访客购物车
    // 当用户未认证时,获取基于本地存储的访客购物车
    console.log('Getting cart for guest user')
    }
    }

    return {
    getUserCart
    }
    })

    2. 使用事件总线

    通过事件总线在不同 Store 之间传递消息。

    // utils/eventBus.ts
    // 事件总线工具
    // 用于在不同组件和 Store 之间传递消息
    // 基于 mitt 库实现
    // 设计意图:通过事件总线实现组件和 Store 之间的解耦通信

    import mitt from 'mitt'

    /**
    * 事件总线实例
    * 用于跨组件和跨 Store 通信
    * 优点:组件和 Store 之间解耦,减少直接依赖
    * 缺点:事件流难以追踪,可能导致调试困难
    */

    export const eventBus = mitt()

    // stores/user.ts
    // 用户状态管理 Store
    // 该文件演示了如何通过事件总线发送事件
    // 设计意图:登录成功后通知其他模块,实现跨模块通信

    import { defineStore } from 'pinia'
    import { eventBus } from '@/utils/eventBus'

    /**
    * 用户状态管理 Store
    * @returns 用户状态管理 Store 实例
    */

    export const useUserStore = defineStore('user', () => {
    /**
    * 用户登录
    * 登录成功后通过事件总线通知其他 Store
    * 演示了如何通过事件总线发送事件
    */

    function login() {
    // 登录逻辑
    console.log('User logging in…')
    // 登录成功后发送事件
    // 事件名称:'user:login',事件数据:{ userId: 123 }
    eventBus.emit('user:login', { userId: 123 })
    }

    return {
    login
    }
    })

    // stores/cart.ts
    // 购物车状态管理 Store
    // 该文件演示了如何通过事件总线监听事件
    // 设计意图:监听用户登录事件,同步购物车数据

    import { defineStore } from 'pinia'
    import { eventBus } from '@/utils/eventBus'

    /**
    * 购物车状态管理 Store
    * @returns 购物车状态管理 Store 实例
    */

    export const useCartStore = defineStore('cart', () => {
    /**
    * 初始化购物车
    * 监听用户登录事件,以便同步购物车数据
    * 演示了如何通过事件总线监听事件
    */

    function initialize() {
    // 监听用户登录事件
    // 事件名称:'user:login',回调函数处理事件数据
    eventBus.on('user:login', (data) => {
    // 处理用户登录事件
    console.log('User logged in:', data.userId)
    // 可以在这里同步购物车数据
    // 例如:将访客购物车数据同步到登录用户的购物车
    })
    }

    return {
    initialize
    }
    })

    3. 使用 Composition API 共享逻辑

    将共享逻辑提取到 composables 中,供多个 Store 使用。

    // composables/useApi.ts
    // API 调用逻辑
    // 该文件演示了如何通过 Composition API 封装共享逻辑
    // 优点:逻辑复用性高,Store 之间耦合度低
    // 设计意图:封装 API 请求的通用逻辑,供多个 Store 和组件复用

    import { ref } from 'vue'

    /**
    * API 调用逻辑
    * 封装了 API 请求的通用逻辑,如加载状态、错误处理等
    * 使用 Composition API 风格,提供更好的代码组织和复用性
    * @returns API 调用相关的状态和方法
    */

    export function useApi() {
    const loading = ref(false) // 加载状态,用于控制加载指示器的显示
    const error = ref<string | null>(null) // 错误信息,用于显示错误提示

    /**
    * 发起 API 请求
    * 封装了 API 请求的通用逻辑,包括加载状态管理和错误处理
    * @param apiCall API 调用函数,返回 Promise
    * @returns API 响应数据
    * @throws API 请求失败时抛出错误
    */

    async function request<T>(apiCall: () => Promise<T>): Promise<T> {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    return await apiCall() // 执行 API 调用
    } catch (err) {
    error.value = 'API request failed' // 设置错误信息
    throw err // 重新抛出错误,以便调用方处理
    } finally {
    loading.value = false // 结束加载
    }
    }

    return {
    loading, // 加载状态
    error, // 错误信息
    request // API 请求方法
    }
    }

    // stores/user.ts
    // 用户状态管理 Store
    // 该文件演示了如何通过 Composition API 共享逻辑
    // 设计意图:通过使用共享的 useApi composable,减少代码重复,提高可维护性

    import { defineStore } from 'pinia'
    import { useApi } from '@/composables/useApi'
    import { userApi } from '@/services/api/user' // 假设已导入

    /**
    * 用户状态管理 Store
    * @returns 用户状态管理 Store 实例
    */

    export const useUserStore = defineStore('user', () => {
    // 使用共享的 API 逻辑
    // 优点:代码复用,减少重复逻辑,Store 之间耦合度低
    const { loading, error, request } = useApi()

    /**
    * 用户登录
    * 使用共享的 request 方法发起登录请求
    * 演示了如何使用共享的 composable 逻辑
    * @param email 邮箱
    * @param password 密码
    * @returns 登录响应,包含用户信息和令牌
    * @throws 登录失败时抛出错误
    */

    async function login(email: string, password: string) {
    return request(() => userApi.login({ email, password }))
    }

    return {
    loading, // 加载状态
    error, // 错误信息
    login // 登录方法
    }
    })

    3.3 状态持久化策略

    对于需要持久化的状态,如用户认证信息、用户偏好设置等,可以使用以下策略:

    1. 使用 localStorage/sessionStorage

    // stores/user.ts
    // 用户状态管理 Store
    // 该文件演示了如何通过 localStorage 实现状态持久化
    // 优点:实现简单直接,适用于基本的状态持久化需求
    // 缺点:需要手动管理存储逻辑,代码冗余
    // 设计意图:通过 localStorage 实现状态持久化,保持用户登录状态

    import { defineStore } from 'pinia'
    import { ref } from 'vue'

    /**
    * 用户状态管理 Store
    * @returns 用户状态管理 Store 实例
    */

    export const useUserStore = defineStore('user', () => {
    // 从 localStorage 初始化令牌
    // 在 Store 初始化时,从 localStorage 中读取之前存储的令牌
    const token = ref<string | null>(localStorage.getItem('token'))

    /**
    * 设置令牌
    * 同时更新 localStorage 中的令牌,实现状态持久化
    * @param newToken 新令牌,字符串或 null
    */

    function setToken(newToken: string | null) {
    token.value = newToken // 更新内存中的令牌状态
    if (newToken) {
    localStorage.setItem('token', newToken) // 存储令牌到 localStorage
    } else {
    localStorage.removeItem('token') // 清除 localStorage 中的令牌
    }
    }

    return {
    token, // 令牌状态
    setToken // 设置令牌的方法
    }
    })

    2. 使用 pinia-plugin-persistedstate

    // stores/user.ts
    // 用户状态管理 Store
    // 该文件演示了如何通过 pinia-plugin-persistedstate 实现状态持久化
    // 优点:配置简单,自动管理存储逻辑,代码简洁
    // 缺点:需要安装额外的插件
    // 设计意图:通过 pinia-plugin-persistedstate 插件实现状态持久化,简化存储逻辑

    import { defineStore } from 'pinia'
    import { ref } from 'vue'
    import type { User } from '@/models/user' // 假设已导入

    /**
    * 用户状态管理 Store
    * @returns 用户状态管理 Store 实例
    */

    export const useUserStore = defineStore('user', () => {
    const currentUser = ref<User | null>(null) // 当前用户信息,类型为 User 或 null
    const token = ref<string | null>(null) // 认证令牌,用于 API 调用的身份验证

    /**
    * 用户登录
    * @param user 用户信息,类型为 User
    * @param userToken 认证令牌,字符串
    */

    function login(user: User, userToken: string) {
    currentUser.value = user // 更新当前用户信息
    token.value = userToken // 更新认证令牌
    // 由于配置了 persist,状态会自动持久化到 localStorage
    }

    /**
    * 用户登出
    */

    function logout() {
    currentUser.value = null // 清空当前用户信息
    token.value = null // 清空认证令牌
    // 由于配置了 persist,状态会自动从 localStorage 中移除
    }

    return {
    currentUser, // 当前用户信息
    token, // 认证令牌
    login, // 登录方法
    logout // 登出方法
    }
    }, {
    persist: {
    key: 'user-store', // 存储键名,用于在 localStorage 中标识存储的数据
    storage: localStorage, // 存储方式,使用 localStorage 实现持久化
    paths: ['currentUser', 'token'] // 需要持久化的状态路径,只持久化 currentUser 和 token
    }
    })

    4. 路由架构

    4.1 路由配置组织

    一个合理的路由配置能够清晰地定义应用的导航结构,便于开发者理解和维护。

    路由配置文件结构

    // router/index.ts
    // 路由主配置文件
    // 该文件负责创建路由实例并应用全局守卫
    // 设计意图:配置和初始化 Vue Router,应用全局守卫

    import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
    import { routes } from './routes'
    import { authGuard } from './guards'

    /**
    * 创建路由实例
    * 配置路由的历史模式和路由表
    * @returns 路由实例
    */

    const router = createRouter({
    history: createWebHistory(), // 使用 HTML5 History API,移除 URL 中的 # 符号
    routes // 路由配置,从 ./routes 文件导入
    })

    /**
    * 全局前置守卫
    * 应用认证守卫,处理所有路由的认证和权限检查
    */

    router.beforeEach(authGuard)

    export default router

    // router/routes.ts
    // 路由定义文件
    // 该文件负责定义应用的所有路由
    // 设计意图:集中管理应用的路由配置,包括路由路径、组件映射、元信息等

    import { RouteRecordRaw } from 'vue-router'
    import DefaultLayout from '@/layouts/DefaultLayout.vue'
    import AuthLayout from '@/layouts/AuthLayout.vue'

    /**
    * 路由元信息接口
    * 定义路由的额外信息,如认证要求、页面标题、角色权限等
    */

    export interface RouteMeta {
    /**
    * 是否需要认证
    * @default true
    */

    requiresAuth?: boolean
    /**
    * 页面标题
    */

    title?: string
    /**
    * 角色权限
    * 只有具有指定角色的用户才能访问该路由
    */

    roles?: string[]
    /**
    * 是否在侧边栏显示
    */

    sidebar?: boolean
    /**
    * 侧边栏图标
    * 用于侧边栏导航的图标名称
    */

    icon?: string
    }

    /**
    * 扩展的路由记录类型
    * 集成自定义的元信息接口,提供更好的类型支持
    */

    export type AppRouteRecordRaw = RouteRecordRaw & {
    meta?: RouteMeta
    children?: AppRouteRecordRaw[]
    }

    /**
    * 路由配置数组
    * 定义应用的所有路由,包括布局、页面组件、元信息等
    */

    export const routes: AppRouteRecordRaw[] = [
    {
    path: '/',
    component: DefaultLayout, // 使用默认布局(包含侧边栏和头部)
    meta: {
    requiresAuth: true // 需要认证
    },
    children: [
    {
    path: '',
    name: 'Dashboard',
    component: () => import('@/views/dashboard/index.vue'), // 懒加载仪表盘页面
    meta: {
    title: '仪表盘',
    sidebar: true, // 在侧边栏显示
    icon: 'dashboard' // 侧边栏图标
    }
    },
    {
    path: 'products',
    name: 'Products',
    component: () => import('@/views/products/index.vue'), // 懒加载产品管理页面
    meta: {
    title: '产品管理',
    sidebar: true, // 在侧边栏显示
    icon: 'shopping-cart' // 侧边栏图标
    }
    },
    {
    path: 'users',
    name: 'Users',
    component: () => import('@/views/users/index.vue'), // 懒加载用户管理页面
    meta: {
    title: '用户管理',
    sidebar: true, // 在侧边栏显示
    icon: 'users', // 侧边栏图标
    roles: ['admin'] // 只有 admin 角色可以访问
    }
    }
    ]
    },
    {
    path: '/auth',
    component: AuthLayout, // 使用认证布局(简洁布局,无侧边栏)
    meta: {
    requiresAuth: false // 不需要认证
    },
    children: [
    {
    path: 'login',
    name: 'Login',
    component: () => import('@/views/auth/login.vue'), // 懒加载登录页面
    meta: {
    title: '登录'
    }
    },
    {
    path: 'register',
    name: 'Register',
    component: () => import('@/views/auth/register.vue'), // 懒加载注册页面
    meta: {
    title: '注册'
    }
    }
    ]
    },
    {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/error/404.vue'), // 懒加载 404 页面
    meta: {
    title: '页面不存在'
    }
    }
    ]

    // router/guards.ts
    // 路由守卫文件
    // 该文件负责定义路由守卫逻辑
    // 设计意图:实现路由的认证和权限控制,保护需要认证的路由

    import { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
    import { useUserStore } from '@/stores/user'

    /**
    * 认证守卫
    * 处理路由的认证和权限检查
    * 在每次路由导航前执行,确保用户有足够的权限访问目标路由
    * @param to 目标路由对象
    * @param from 来源路由对象
    * @param next 导航函数,决定导航是否继续
    */

    export function authGuard(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
    const userStore = useUserStore() // 获取用户状态管理 Store

    // 设置页面标题
    if (to.meta.title) {
    document.title = `${to.meta.title} – My App`
    }

    // 权限检查
    const requiresAuth = to.meta.requiresAuth !== false // 默认需要认证
    const isAuthenticated = userStore.isAuthenticated // 用户是否已认证
    const roles = to.meta.roles || [] // 路由所需的角色权限

    if (requiresAuth && !isAuthenticated) {
    // 未登录,重定向到登录页
    next({ name: 'Login' })
    } else if (roles.length > 0 && !roles.some(role => userStore.hasRole(role))) {
    // 无权限,重定向到 403 页面
    next({ name: 'Forbidden' })
    } else {
    // 已登录或不需要认证,继续导航
    next()
    }
    }

    4.2 路由模块化

    对于大型应用,可以将路由配置按照模块进行拆分,提高代码的可维护性。

    // router/modules/auth.ts
    // 认证模块路由配置
    // 设计意图:将认证相关的路由配置拆分到独立的模块,提高代码可维护性

    import { AppRouteRecordRaw } from '../types'

    /**
    * 认证模块路由配置
    * 包含登录、注册等认证相关页面的路由
    */

    export const authRoutes: AppRouteRecordRaw[] = [
    {
    path: '/auth',
    component: () => import('@/layouts/AuthLayout.vue'), // 懒加载认证布局
    meta: {
    requiresAuth: false // 认证页面不需要认证
    },
    children: [
    {
    path: 'login',
    name: 'Login',
    component: () => import('@/views/auth/login.vue'), // 懒加载登录页面
    meta: {
    title: '登录'
    }
    },
    {
    path: 'register',
    name: 'Register',
    component: () => import('@/views/auth/register.vue'), // 懒加载注册页面
    meta: {
    title: '注册'
    }
    }
    ]
    }
    ]

    // router/modules/dashboard.ts
    // 仪表盘模块路由配置
    // 设计意图:将仪表盘相关的路由配置拆分到独立的模块,提高代码可维护性

    import { AppRouteRecordRaw } from '../types'

    /**
    * 仪表盘模块路由配置
    * 包含仪表盘页面的路由
    */

    export const dashboardRoutes: AppRouteRecordRaw[] = [
    {
    path: '/dashboard',
    component: () => import('@/layouts/DefaultLayout.vue'), // 懒加载默认布局
    meta: {
    requiresAuth: true // 需要认证
    },
    children: [
    {
    path: '',
    name: 'Dashboard',
    component: () => import('@/views/dashboard/index.vue'), // 懒加载仪表盘页面
    meta: {
    title: '仪表盘',
    sidebar: true, // 在侧边栏显示
    icon: 'dashboard' // 侧边栏图标
    }
    }
    ]
    }
    ]

    // router/routes.ts
    // 路由配置主文件
    // 设计意图:组合所有路由模块,形成完整的应用路由配置

    import { AppRouteRecordRaw } from './types'
    import { authRoutes } from './modules/auth' // 认证模块路由
    import { dashboardRoutes } from './modules/dashboard' // 仪表盘模块路由
    import { productRoutes } from './modules/product' // 产品模块路由
    import { userRoutes } from './modules/user' // 用户模块路由

    /**
    * 路由配置数组
    * 组合所有路由模块,形成完整的应用路由配置
    */

    export const routes: AppRouteRecordRaw[] = [
    authRoutes, // 认证模块路由
    dashboardRoutes, // 仪表盘模块路由
    productRoutes, // 产品模块路由
    userRoutes, // 用户模块路由
    {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/error/404.vue'), // 懒加载 404 页面
    meta: {
    title: '页面不存在'
    }
    }
    ]

    4.3 路由守卫策略

    路由守卫是控制路由访问权限的重要手段,合理使用路由守卫能够提高应用的安全性和用户体验。

    全局守卫

    全局守卫适用于所有路由,可以用于处理认证、权限检查、页面标题设置等通用逻辑。

    // router/guards.ts
    // 路由守卫文件
    // 设计意图:实现路由的认证和权限控制,保护需要认证的路由

    import { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
    import { useUserStore } from '@/stores/user'

    /**
    * 认证守卫
    * 全局前置守卫,处理所有路由的认证和权限检查
    * @param to 目标路由对象
    * @param from 来源路由对象
    * @param next 导航函数,决定导航是否继续
    */

    export function authGuard(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
    const userStore = useUserStore() // 获取用户状态管理 Store

    // 设置页面标题
    if (to.meta.title) {
    document.title = `${to.meta.title} – My App`
    }

    // 权限检查
    const requiresAuth = to.meta.requiresAuth !== false // 默认需要认证
    const isAuthenticated = userStore.isAuthenticated // 用户是否已认证
    const roles = to.meta.roles || [] // 路由所需的角色权限

    if (requiresAuth && !isAuthenticated) {
    // 未登录,重定向到登录页
    next({ name: 'Login' })
    } else if (roles.length > 0 && !roles.some(role => userStore.hasRole(role))) {
    // 无权限,重定向到 403 页面
    next({ name: 'Forbidden' })
    } else {
    // 已登录或不需要认证,继续导航
    next()
    }
    }

    /**
    * 全局后置守卫
    * 在路由导航完成后执行,用于处理页面加载完成后的逻辑
    * @param to 目标路由对象
    * @param from 来源路由对象
    */

    export function globalAfterGuard(to: RouteLocationNormalized, from: RouteLocationNormalized) {
    // 页面加载完成后的逻辑,如埋点统计、页面性能监控等
    console.log(`Navigated from ${from.path} to ${to.path}`)
    }

    路由独享守卫

    路由独享守卫只适用于特定路由,可以用于处理该路由的特殊逻辑。

    // router/routes.ts
    // 路由定义文件
    // 设计意图:演示如何使用路由独享守卫处理特定路由的特殊逻辑

    import { AppRouteRecordRaw } from './types'

    /**
    * 路由配置数组
    */

    export const routes: AppRouteRecordRaw[] = [
    {
    path: '/admin',
    name: 'Admin',
    component: () => import('@/views/admin/index.vue'), // 懒加载管理员页面
    meta: {
    requiresAuth: true, // 需要认证
    roles: ['admin'] // 只有 admin 角色可以访问
    },
    beforeEnter: (to, from, next) => {
    // 路由独享守卫逻辑
    // 只适用于当前路由,用于处理该路由的特殊逻辑
    console.log('Entering admin route')
    // 可以在这里添加额外的权限检查或其他逻辑
    next() // 继续导航
    }
    }
    ]

    组件内守卫

    组件内守卫适用于组件级别,可以用于处理组件的进入、离开等逻辑。

    <script setup lang="ts">
    // 组件内守卫示例
    // 设计意图:演示如何在组件级别使用路由守卫,处理组件的进入、离开等逻辑

    import { onBeforeRouteEnter, onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

    /**
    * 路由进入组件前
    * 在路由进入组件之前执行,此时组件实例还未创建
    * @param to 目标路由对象
    * @param from 来源路由对象
    * @param next 导航函数,决定导航是否继续
    */
    onBeforeRouteEnter((to, from, next) => {
    console.log('Before route enter')
    // 可以在这里进行数据预加载、权限检查等
    // 例如:获取组件需要的数据,避免组件渲染时出现空白
    next() // 继续导航
    })

    /**
    * 路由离开组件前
    * 在路由离开组件之前执行,此时组件实例仍然存在
    * @param to 目标路由对象
    * @param from 来源路由对象
    * @param next 导航函数,决定导航是否继续
    */
    onBeforeRouteLeave((to, from, next) => {
    console.log('Before route leave')
    // 可以在这里进行表单验证、确认提示等
    // 例如:检查用户是否有未保存的修改,提示用户确认离开
    next() // 继续导航
    })

    /**
    * 路由更新但组件被复用时
    * 在路由更新但组件被复用时执行,例如从 /user/1 导航到 /user/2
    * @param to 目标路由对象
    * @param from 来源路由对象
    * @param next 导航函数,决定导航是否继续
    */
    onBeforeRouteUpdate((to, from, next) => {
    console.log('Before route update')
    // 可以在这里更新组件数据、重新获取数据等
    // 例如:根据新的路由参数重新获取用户信息
    next() // 继续导航
    })
    </script>

    5. API 层设计

    5.1 API 服务组织

    一个合理的 API 层设计能够清晰地管理与后端的通信,提高代码的可维护性和可测试性。

    API 服务文件结构

    // services/api/index.ts
    // API 客户端配置文件
    // 设计意图:创建和配置 axios 实例,设置请求/响应拦截器,统一处理 API 调用

    import axios from 'axios'
    import { useUserStore } from '@/stores/user'

    /**
    * 创建 axios 实例
    * 配置基础 URL、超时时间、默认请求头等
    */

    const apiClient = axios.create({
    baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // API 基础 URL,从环境变量获取或使用默认值
    timeout: 10000, // 请求超时时间,10秒
    headers: {
    'Content-Type': 'application/json' // 默认请求头,使用 JSON 格式
    }
    })

    /**
    * 请求拦截器
    * 在发送请求之前执行,用于添加认证令牌、请求日志等
    */

    apiClient.interceptors.request.use(
    (config) => {
    const userStore = useUserStore() // 获取用户状态管理 Store
    const token = userStore.token // 获取认证令牌

    if (token) {
    // 如果有令牌,添加到请求头的 Authorization 字段
    config.headers.Authorization = `Bearer ${token}`
    }

    return config // 返回修改后的配置
    },
    (error) => {
    // 请求错误处理
    return Promise.reject(error)
    }
    )

    /**
    * 响应拦截器
    * 在收到响应之后执行,用于统一处理响应数据、错误处理等
    */

    apiClient.interceptors.response.use(
    (response) => {
    // 只返回响应数据,忽略其他响应信息
    return response.data
    },
    (error) => {
    // 错误处理
    if (error.response?.status === 401) {
    // 401 未授权错误,清除用户登录状态
    const userStore = useUserStore()
    userStore.logout()
    }
    return Promise.reject(error) // 重新抛出错误,以便调用方处理
    }
    )

    export default apiClient

    // services/api/user.ts
    // 用户 API 服务
    // 设计意图:封装用户相关的 API 调用,提供类型安全的 API 服务

    import apiClient from './index'
    import { User, LoginRequest, RegisterRequest } from '@/models/user'

    /**
    * 用户 API 服务
    * 封装用户相关的 API 调用,包括登录、注册、获取用户信息、更新用户信息等
    */

    export const userApi = {
    /**
    * 用户登录
    * @param data 登录数据,包含邮箱和密码
    * @returns 登录响应,包含用户信息和认证令牌
    */

    login: (data: LoginRequest) => {
    return apiClient.post<{ user: User; token: string }>('/auth/login', data)
    },

    /**
    * 用户注册
    * @param data 注册数据,包含用户基本信息
    * @returns 注册响应,包含用户信息和认证令牌
    */

    register: (data: RegisterRequest) => {
    return apiClient.post<{ user: User; token: string }>('/auth/register', data)
    },

    /**
    * 获取当前用户信息
    * @returns 当前用户信息
    */

    getCurrentUser: () => {
    return apiClient.get<User>('/users/me')
    },

    /**
    * 更新用户信息
    * @param data 用户信息,部分更新
    * @returns 更新后的用户信息
    */

    updateUser: (data: Partial<User>) => {
    return apiClient.put<User>('/users/me', data)
    }
    }

    // services/api/product.ts
    // 产品 API 服务
    // 设计意图:封装产品相关的 API 调用,提供类型安全的 API 服务

    import apiClient from './index'
    import { Product, CreateProductRequest, UpdateProductRequest } from '@/models/product'

    /**
    * 产品 API 服务
    * 封装产品相关的 API 调用,包括获取产品列表、获取产品详情、创建产品、更新产品、删除产品等
    */

    export const productApi = {
    /**
    * 获取产品列表
    * @param params 查询参数,包含分页和筛选条件
    * @returns 产品列表
    */

    getProducts: (params?: { page?: number; limit?: number; category?: string }) => {
    return apiClient.get<Product[]>('/products', { params })
    },

    /**
    * 获取产品详情
    * @param id 产品 ID
    * @returns 产品详情
    */

    getProduct: (id: number) => {
    return apiClient.get<Product>(`/products/${id}`)
    },

    /**
    * 创建产品
    * @param data 产品数据,包含产品基本信息
    * @returns 创建的产品
    */

    createProduct: (data: CreateProductRequest) => {
    return apiClient.post<Product>('/products', data)
    },

    /**
    * 更新产品
    * @param id 产品 ID
    * @param data 产品数据,部分更新
    * @returns 更新后的产品
    */

    updateProduct: (id: number, data: UpdateProductRequest) => {
    return apiClient.put<Product>(`/products/${id}`, data)
    },

    /**
    * 删除产品
    * @param id 产品 ID
    * @returns 删除结果
    */

    deleteProduct: (id: number) => {
    return apiClient.delete(`/products/${id}`)
    }
    }

    5.2 API 错误处理策略

    合理的错误处理策略能够提高应用的稳定性和用户体验,以下是几种常见的错误处理方式:

    全局错误处理

    通过 axios 响应拦截器统一处理 API 错误。

    // services/api/index.ts
    // API 客户端配置文件
    // 设计意图:通过响应拦截器实现全局错误处理,统一处理不同类型的 API 错误

    import axios from 'axios'
    import { useUserStore } from '@/stores/user'
    import { ElMessage } from 'element-plus'

    /**
    * 响应拦截器
    * 统一处理 API 错误,根据错误状态码显示不同的错误提示
    */

    apiClient.interceptors.response.use(
    (response) => {
    return response.data // 只返回响应数据
    },
    (error) => {
    // 处理网络错误(无响应)
    if (!error.response) {
    ElMessage.error('网络连接失败,请检查网络设置')
    return Promise.reject(error)
    }

    // 处理 401 错误(未授权)
    if (error.response.status === 401) {
    const userStore = useUserStore()
    userStore.logout() // 清除用户登录状态
    ElMessage.error('登录已过期,请重新登录')
    return Promise.reject(error)
    }

    // 处理 403 错误(禁止访问)
    if (error.response.status === 403) {
    ElMessage.error('没有权限访问该资源')
    return Promise.reject(error)
    }

    // 处理 404 错误(资源不存在)
    if (error.response.status === 404) {
    ElMessage.error('请求的资源不存在')
    return Promise.reject(error)
    }

    // 处理 500 错误(服务器错误)
    if (error.response.status >= 500) {
    ElMessage.error('服务器错误,请稍后重试')
    return Promise.reject(error)
    }

    // 处理其他错误
    const errorMessage = error.response.data?.message || '请求失败,请稍后重试'
    ElMessage.error(errorMessage)
    return Promise.reject(error)
    }
    )

    局部错误处理

    在具体的 API 调用处进行错误处理,适用于需要特殊处理的场景。

    // composables/useAuth.ts
    // 认证相关逻辑
    // 设计意图:在具体的 API 调用处进行错误处理,适用于需要特殊处理的场景

    import { ref } from 'vue'
    import { userApi } from '@/services/api/user'
    import { LoginRequest, RegisterRequest } from '@/models/user'

    /**
    * 认证相关逻辑
    * 封装登录、注册等认证操作,并在具体的 API 调用处进行错误处理
    */

    export function useAuth() {
    const loading = ref(false) // 加载状态
    const error = ref<string | null>(null) // 错误信息

    /**
    * 用户登录
    * 在具体的 API 调用处进行错误处理,提供更具体的错误提示
    * @param email 邮箱
    * @param password 密码
    * @returns 登录响应
    * @throws 登录失败时抛出错误
    */

    async function login(email: string, password: string) {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    const response = await userApi.login({ email, password }) // 调用登录 API
    return response // 返回登录响应
    } catch (err: any) {
    // 自定义错误信息,提供更具体的错误提示
    error.value = err.response?.data?.message || '登录失败,请检查邮箱和密码'
    throw err // 重新抛出错误,以便调用方处理
    } finally {
    loading.value = false // 结束加载
    }
    }

    /**
    * 用户注册
    * 在具体的 API 调用处进行错误处理,提供更具体的错误提示
    * @param userData 注册数据
    * @returns 注册响应
    * @throws 注册失败时抛出错误
    */

    async function register(userData: RegisterRequest) {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    const response = await userApi.register(userData) // 调用注册 API
    return response // 返回注册响应
    } catch (err: any) {
    // 自定义错误信息,提供更具体的错误提示
    error.value = err.response?.data?.message || '注册失败,请稍后重试'
    throw err // 重新抛出错误,以便调用方处理
    } finally {
    loading.value = false // 结束加载
    }
    }

    return {
    loading, // 加载状态
    error, // 错误信息
    login, // 登录方法
    register // 注册方法
    }
    }

    5.3 API 数据缓存策略

    合理的 API 数据缓存策略能够减少重复请求,提高应用的性能和用户体验。

    内存缓存

    使用内存缓存存储临时数据,适用于单次会话中的数据。

    // services/api/product.ts
    // 产品 API 服务
    // 设计意图:通过内存缓存减少重复请求,提高应用性能和用户体验

    import apiClient from './index'
    import { Product } from '@/models/product'

    // 产品列表缓存
    // 使用内存变量存储产品列表数据和缓存时间戳
    let productsCache: { data: Product[]; timestamp: number } | null = null
    const CACHE_DURATION = 5 * 60 * 1000 // 缓存持续时间,5 分钟

    /**
    * 产品 API 服务
    */

    export const productApi = {
    /**
    * 获取产品列表
    * 使用内存缓存减少重复请求,提高应用性能
    * @param params 查询参数,包含分页和筛选条件
    * @returns 产品列表
    */

    getProducts: async (params?: { page?: number; limit?: number; category?: string }) => {
    // 检查缓存是否有效
    // 如果缓存存在且未过期,直接返回缓存数据
    if (productsCache && Date.now() productsCache.timestamp < CACHE_DURATION) {
    return productsCache.data
    }

    // 缓存无效或不存在,发起 API 请求
    const data = await apiClient.get<Product[]>('/products', { params })

    // 更新缓存
    // 存储响应数据和当前时间戳
    productsCache = {
    data,
    timestamp: Date.now()
    }

    return data
    },

    /**
    * 清除产品列表缓存
    * 在产品数据发生变化时调用,确保下次获取最新数据
    */

    clearProductsCache: () => {
    productsCache = null
    }
    }

    本地存储缓存

    使用 localStorage 或 sessionStorage 存储持久化数据,适用于需要跨会话保持的数据。

    // services/api/user.ts
    // 用户 API 服务
    // 设计意图:通过本地存储缓存实现数据持久化,适用于需要跨会话保持的数据

    import apiClient from './index'
    import { User } from '@/models/user'

    const USER_CACHE_KEY = 'user_cache' // 本地存储的缓存键名
    const CACHE_DURATION = 24 * 60 * 60 * 1000 // 缓存持续时间,24 小时

    /**
    * 用户 API 服务
    */

    export const userApi = {
    /**
    * 获取当前用户信息
    * 使用本地存储缓存实现数据持久化,减少重复请求
    * @returns 用户信息
    */

    getCurrentUser: async () => {
    // 检查缓存
    const cachedUser = localStorage.getItem(USER_CACHE_KEY)
    if (cachedUser) {
    const { data, timestamp } = JSON.parse(cachedUser)
    // 如果缓存存在且未过期,直接返回缓存数据
    if (Date.now() timestamp < CACHE_DURATION) {
    return data
    }
    }

    // 缓存无效或不存在,发起 API 请求
    const data = await apiClient.get<User>('/users/me')

    // 更新缓存
    // 将响应数据和当前时间戳存储到本地存储
    localStorage.setItem(USER_CACHE_KEY, JSON.stringify({
    data,
    timestamp: Date.now()
    }))

    return data
    },

    /**
    * 清除用户缓存
    * 在用户信息发生变化时调用,确保下次获取最新数据
    */

    clearUserCache: () => {
    localStorage.removeItem(USER_CACHE_KEY)
    }
    }

    6. 工具类和公共组件

    6.1 工具函数组织

    工具函数是项目中常用的辅助函数,合理组织工具函数能够提高代码的复用性和可维护性。

    工具函数目录结构

    src/utils/
    ├── format.ts # 格式化工具
    ├── validation.ts # 验证工具
    ├── storage.ts # 存储工具
    ├── http.ts # HTTP 工具
    ├── date.ts # 日期工具
    ├── number.ts # 数字工具
    ├── string.ts # 字符串工具
    ├── array.ts # 数组工具
    └── object.ts # 对象工具

    工具函数示例

    // utils/format.ts
    // 格式化工具函数
    // 设计意图:提供常用的格式化功能,如金额格式化、日期格式化等

    /**
    * 格式化金额
    * 将数字金额格式化为带有货币符号和千分位的字符串
    * @param amount 金额,数字类型
    * @param currency 货币符号,默认为 ¥
    * @returns 格式化后的金额字符串,如 "¥1,234.56"
    */

    export function formatCurrency(amount: number, currency: string = '¥'): string {
    // 使用 toFixed(2) 保留两位小数,然后使用正则表达式添加千分位
    return `${currency}${amount.toFixed(2).replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',')}`
    }

    /**
    * 格式化日期
    * 将日期对象、字符串或时间戳格式化为指定格式的日期字符串
    * @param date 日期,可以是 Date 对象、日期字符串或时间戳
    * @param format 格式化模板,默认为 'YYYY-MM-DD'
    * @returns 格式化后的日期字符串,如 "2023-12-25"
    */

    export function formatDate(date: Date | string | number, format: string = 'YYYY-MM-DD'): string {
    const d = new Date(date) // 创建 Date 对象
    const year = d.getFullYear() // 获取年份
    const month = String(d.getMonth() + 1).padStart(2, '0') // 获取月份(0-11,所以加 1),并补零
    const day = String(d.getDate()).padStart(2, '0') // 获取日期,并补零
    const hours = String(d.getHours()).padStart(2, '0') // 获取小时,并补零
    const minutes = String(d.getMinutes()).padStart(2, '0') // 获取分钟,并补零
    const seconds = String(d.getSeconds()).padStart(2, '0') // 获取秒数,并补零

    // 替换格式化模板中的占位符
    return format
    .replace('YYYY', String(year))
    .replace('MM', month)
    .replace('DD', day)
    .replace('HH', hours)
    .replace('mm', minutes)
    .replace('ss', seconds)
    }

    // utils/validation.ts
    // 验证工具函数
    // 设计意图:提供常用的验证功能,如邮箱验证、密码强度验证等

    /**
    * 验证邮箱
    * 检查邮箱地址是否符合标准格式
    * @param email 邮箱地址,字符串类型
    * @returns 是否为有效邮箱,布尔值
    */

    export function isValidEmail(email: string): boolean {
    const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/ // 简单的邮箱格式正则表达式
    return emailRegex.test(email) // 测试邮箱地址是否匹配正则表达式
    }

    /**
    * 验证密码强度
    * 根据密码长度、包含的字符类型等计算密码强度等级
    * @param password 密码,字符串类型
    * @returns 密码强度等级 (0-4),数字越大表示密码强度越高
    */

    export function getPasswordStrength(password: string): number {
    let strength = 0 // 初始强度为 0
    if (password.length >= 8) strength++ // 长度至少 8 位
    if (/[A-Z]/.test(password)) strength++ // 包含大写字母
    if (/[a-z]/.test(password)) strength++ // 包含小写字母
    if (/[0-9]/.test(password)) strength++ // 包含数字
    if (/[^A-Za-z0-9]/.test(password)) strength++ // 包含特殊字符
    return strength // 返回密码强度等级
    }

    // utils/storage.ts
    // 存储工具函数
    // 设计意图:封装本地存储操作,提供类型安全的存储工具函数

    /**
    * 设置本地存储
    * 将值存储到 localStorage 中,自动进行 JSON 序列化
    * @param key 存储键,字符串类型
    * @param value 存储值,可以是任何可 JSON 序列化的类型
    */

    export function setLocalStorage(key: string, value: any): void {
    try {
    // 将值转换为 JSON 字符串并存储
    localStorage.setItem(key, JSON.stringify(value))
    } catch (error) {
    // 捕获并记录错误,避免存储失败导致应用崩溃
    console.error('Error setting localStorage:', error)
    }
    }

    /**
    * 获取本地存储
    * 从 localStorage 中获取值,自动进行 JSON 反序列化
    * @param key 存储键,字符串类型
    * @param defaultValue 默认值,当存储不存在或解析失败时返回
    * @returns 存储值或默认值,类型为 T
    */

    export function getLocalStorage<T>(key: string, defaultValue: T): T {
    try {
    const value = localStorage.getItem(key) // 获取存储的 JSON 字符串
    return value ? JSON.parse(value) : defaultValue // 解析 JSON 字符串,失败则返回默认值
    } catch (error) {
    // 捕获并记录错误,返回默认值
    console.error('Error getting localStorage:', error)
    return defaultValue
    }
    }

    /**
    * 删除本地存储
    * 从 localStorage 中删除指定的存储项
    * @param key 存储键,字符串类型
    */

    export function removeLocalStorage(key: string): void {
    try {
    localStorage.removeItem(key) // 删除存储项
    } catch (error) {
    // 捕获并记录错误
    console.error('Error removing localStorage:', error)
    }
    }

    6.2 公共组件组织

    公共组件是项目中可复用的 UI 组件,合理组织公共组件能够提高开发效率和 UI 一致性。

    公共组件目录结构

    src/components/
    ├── common/ # 通用基础组件
    │ ├── Button.vue # 按钮组件
    │ ├── Input.vue # 输入框组件
    │ ├── Dialog.vue # 对话框组件
    │ └── Loading.vue # 加载组件
    ├── layout/ # 布局相关组件
    │ ├── Header.vue # 头部组件
    │ ├── Sidebar.vue # 侧边栏组件
    │ └── Footer.vue # 底部组件
    └── business/ # 业务相关组件
    ├── UserCard.vue # 用户卡片组件
    ├── ProductList.vue # 产品列表组件
    └── OrderForm.vue # 订单表单组件

    公共组件设计原则
  • 单一职责:每个组件只负责一个功能,便于理解和维护
  • 可配置性:通过 props 提供足够的配置选项,提高组件的灵活性
  • 可扩展性:设计合理的组件结构,便于后续功能的扩展
  • 类型安全:充分利用 TypeScript 的类型系统,为组件定义明确的类型
  • 文档完备:为组件提供详细的文档和使用示例
  • 公共组件示例

    <!– components/common/Button.vue –>
    <!– 通用按钮组件 –>
    <!– 设计意图:提供一个可配置的通用按钮组件,支持多种类型、尺寸和状态 –>

    <template>
    <button
    :class="[
    'btn',
    `btn-${variant}`, // 按钮类型样式
    `btn-${size}`, // 按钮尺寸样式
    { 'btn-block': block }, // 块级按钮样式
    { 'btn-disabled': disabled } // 禁用状态样式
    ]"
    :disabled="disabled" // 禁用状态
    @click="$emit('click', $event)" // 点击事件
    >
    <slot></slot> <!– 按钮内容插槽 –>
    </button>
    </template>

    <script setup lang="ts">
    // 使用 Composition API 语法
    import { defineProps, defineEmits } from 'vue'

    /**
    * 按钮类型
    * 支持多种预设的按钮样式
    */
    export type ButtonVariant = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info'

    /**
    * 按钮尺寸
    * 支持多种预设的按钮尺寸
    */
    export type ButtonSize = 'sm' | 'md' | 'lg'

    /**
    * 按钮组件属性
    * 使用 TypeScript 类型定义,提供更好的类型提示
    */
    const props = defineProps<{
    /**
    * 按钮类型
    * 决定按钮的颜色样式
    * @default 'primary'
    */
    variant?: ButtonVariant
    /**
    * 按钮尺寸
    * 决定按钮的大小
    * @default 'md'
    */
    size?: ButtonSize
    /**
    * 是否为块级按钮
    * 块级按钮会占满父容器的宽度
    * @default false
    */
    block?: boolean
    /**
    * 是否禁用
    * 禁用状态下按钮不可点击
    * @default false
    */
    disabled?: boolean
    }>()

    /**
    * 按钮组件事件
    * 使用 TypeScript 类型定义,提供更好的类型提示
    */
    const emit = defineEmits<{
    /**
    * 点击事件
    * 当用户点击按钮时触发
    * @param event 点击事件对象
    */
    (e: 'click', event: MouseEvent): void
    }>()
    </script>

    <style scoped>
    /* 按钮基础样式 */
    .btn {
    display: inline-block;
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 0.25rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s ease;
    }

    /* 按钮类型样式 */
    .btn-primary {
    background-color: #3b82f6;
    color: white;
    }

    .btn-secondary {
    background-color: #6b7280;
    color: white;
    }

    .btn-success {
    background-color: #10b981;
    color: white;
    }

    .btn-danger {
    background-color: #ef4444;
    color: white;
    }

    .btn-warning {
    background-color: #f59e0b;
    color: white;
    }

    .btn-info {
    background-color: #3b82f6;
    color: white;
    }

    /* 按钮尺寸样式 */
    .btn-sm {
    padding: 0.25rem 0.5rem;
    font-size: 0.875rem;
    }

    .btn-md {
    padding: 0.5rem 1rem;
    font-size: 1rem;
    }

    .btn-lg {
    padding: 0.75rem 1.5rem;
    font-size: 1.125rem;
    }

    /* 块级按钮样式 */
    .btn-block {
    display: block;
    width: 100%;
    }

    /* 禁用状态样式 */
    .btn-disabled {
    opacity: 0.6;
    cursor: not-allowed;
    }
    </style>

    6.3 组合式 API 逻辑组织

    组合式 API 是 Vue 3 的重要特性,合理组织组合式 API 逻辑能够提高代码的复用性和可维护性。

    组合式 API 目录结构

    src/composables/
    ├── useAuth.ts # 认证相关逻辑
    ├── useApi.ts # API 调用逻辑
    ├── useLocalStorage.ts # 本地存储逻辑
    ├── useValidation.ts # 验证相关逻辑
    ├── useDebounce.ts # 防抖逻辑
    └── useThrottle.ts # 节流逻辑

    组合式 API 示例

    // composables/useAuth.ts
    // 认证相关逻辑
    // 设计意图:封装认证相关的逻辑,供多个组件复用,实现逻辑的模块化和可维护性

    import { ref, computed } from 'vue'
    import { userApi } from '@/services/api/user'
    import { useUserStore } from '@/stores/user'
    import { LoginRequest, RegisterRequest } from '@/models/user'

    /**
    * 认证相关逻辑
    * 封装登录、注册、登出等认证操作,提供统一的认证状态管理
    * 使用 Composition API 风格,便于在组件中使用
    * @returns 认证相关的状态和方法
    */

    export function useAuth() {
    const userStore = useUserStore() // 获取用户状态管理 Store
    const loading = ref(false) // 加载状态
    const error = ref<string | null>(null) // 错误信息

    /**
    * 是否已认证
    * 从用户 Store 中获取认证状态
    */

    const isAuthenticated = computed(() => userStore.isAuthenticated)

    /**
    * 当前用户
    * 从用户 Store 中获取当前用户信息
    */

    const currentUser = computed(() => userStore.currentUser)

    /**
    * 用户登录
    * 调用登录 API,更新用户状态
    * @param email 邮箱
    * @param password 密码
    * @returns 登录响应
    * @throws 登录失败时抛出错误
    */

    async function login(email: string, password: string) {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    const response = await userApi.login({ email, password }) // 调用登录 API
    userStore.login(response.user, response.token) // 更新用户状态
    return response // 返回登录响应
    } catch (err: any) {
    // 自定义错误信息
    error.value = err.response?.data?.message || '登录失败,请检查邮箱和密码'
    throw err // 重新抛出错误
    } finally {
    loading.value = false // 结束加载
    }
    }

    /**
    * 用户注册
    * 调用注册 API,更新用户状态
    * @param userData 用户注册数据
    * @returns 注册响应
    * @throws 注册失败时抛出错误
    */

    async function register(userData: RegisterRequest) {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    const response = await userApi.register(userData) // 调用注册 API
    userStore.login(response.user, response.token) // 更新用户状态
    return response // 返回注册响应
    } catch (err: any) {
    // 自定义错误信息
    error.value = err.response?.data?.message || '注册失败,请稍后重试'
    throw err // 重新抛出错误
    } finally {
    loading.value = false // 结束加载
    }
    }

    /**
    * 用户登出
    * 调用登出 API,清除用户状态
    * @throws 登出失败时抛出错误
    */

    async function logout() {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    await userApi.logout() // 调用登出 API
    userStore.logout() // 清除用户状态
    } catch (err: any) {
    // 自定义错误信息
    error.value = err.response?.data?.message || '登出失败,请稍后重试'
    throw err // 重新抛出错误
    } finally {
    loading.value = false // 结束加载
    }
    }

    /**
    * 获取当前用户信息
    * 调用获取用户信息 API,更新用户状态
    * @returns 用户信息
    * @throws 获取失败时抛出错误
    */

    async function fetchCurrentUser() {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    const user = await userApi.getCurrentUser() // 调用获取用户信息 API
    userStore.setCurrentUser(user) // 更新用户状态
    return user // 返回用户信息
    } catch (err: any) {
    // 自定义错误信息
    error.value = err.response?.data?.message || '获取用户信息失败'
    throw err // 重新抛出错误
    } finally {
    loading.value = false // 结束加载
    }
    }

    return {
    loading, // 加载状态
    error, // 错误信息
    isAuthenticated, // 是否已认证
    currentUser, // 当前用户
    login, // 登录方法
    register, // 注册方法
    logout, // 登出方法
    fetchCurrentUser // 获取当前用户信息方法
    }
    }

    // composables/useApi.ts
    // API 调用逻辑
    // 设计意图:封装 API 请求的通用逻辑,供多个组件和 composables 复用,实现错误处理和加载状态的统一管理

    import { ref } from 'vue'

    /**
    * API 调用逻辑
    * 封装 API 请求的通用逻辑,包括加载状态管理和错误处理
    * 使用 Composition API 风格,便于在组件和其他 composables 中使用
    * @returns API 调用相关的状态和方法
    */

    export function useApi() {
    const loading = ref(false) // 加载状态
    const error = ref<string | null>(null) // 错误信息

    /**
    * 发起 API 请求
    * 封装 API 请求的通用逻辑,处理加载状态和错误
    * @param apiCall API 调用函数,返回 Promise
    * @returns API 响应数据
    * @throws API 请求失败时抛出错误
    */

    async function request<T>(apiCall: () => Promise<T>): Promise<T> {
    loading.value = true // 开始加载
    error.value = null // 清空错误信息
    try {
    return await apiCall() // 执行 API 调用
    } catch (err: any) {
    // 自定义错误信息
    error.value = err.response?.data?.message || '请求失败,请稍后重试'
    throw err // 重新抛出错误
    } finally {
    loading.value = false // 结束加载
    }
    }

    return {
    loading, // 加载状态
    error, // 错误信息
    request // API 请求方法
    }
    }

    7. 企业级项目案例分析

    7.1 项目架构演进

    初始架构

    在项目初期,通常会采用相对简单的架构,以快速实现核心功能。

    // 初始架构
    // 项目初期的简单架构,以快速实现核心功能为目标
    // 设计意图:在项目初期,使用简单的目录结构,快速搭建项目框架

    // 特点:
    // – 目录结构简单,易于理解和快速上手
    // – 功能模块划分明确,便于初期开发
    // – 适用于中小型项目或项目初期阶段

    // 适用场景:项目初期,功能相对简单,团队规模较小
    src/
    ├── assets/ # 静态资源(图片、样式等)
    ├── components/ # 公共组件
    ├── views/ # 页面组件
    ├── router/ # 路由配置
    ├── store/ # 状态管理(如 Vuex)
    ├── services/ # API 服务
    ├── utils/ # 工具函数
    ├── App.vue # 根组件
    └── main.ts # 应用入口文件

    演进架构

    随着项目规模的扩大,架构会逐渐演进,添加更多的功能模块和组织结构。

    // 演进架构
    // 随着项目规模扩大,架构逐渐演进,添加更多功能模块和组织结构
    // 设计意图:通过完善的目录结构,提高代码的可维护性和可扩展性,适应项目的不断发展

    // 特点:
    // – 目录结构更加完善,功能模块划分更加细致
    // – 引入了更多的 TypeScript 相关目录,如 types、enums 等
    // – 使用 Pinia 替代 Vuex,采用 Composition API 风格
    // – 增加了更多的功能模块,如 composables、layouts、models 等

    // 适用场景:中大型项目,功能相对复杂,团队规模较大
    src/
    ├── assets/ # 静态资源(图片、样式、图标等)
    ├── components/ # 公共组件(按功能或类型划分)
    ├── composables/ # 组合式 API 逻辑(可复用的业务逻辑)
    ├── constants/ # 常量定义(API 地址、路由名称等)
    ├── enums/ # 枚举类型(用户角色、订单状态等)
    ├── layouts/ # 布局组件(默认布局、认证布局等)
    ├── models/ # 数据模型(用户模型、产品模型等)
    ├── router/ # 路由配置(路由定义、守卫等)
    ├── services/ # 服务层(API 服务、工具服务等)
    ├── stores/ # Pinia 状态管理(按模块划分的状态管理)
    ├── types/ # TypeScript 类型定义(API 类型、组件类型等)
    ├── utils/ # 工具函数(格式化、验证、存储等)
    ├── views/ # 页面组件(按业务功能划分)
    ├── App.vue # 根组件
    ├── main.ts # 应用入口文件
    └── env.d.ts # 环境变量类型声明

    微前端架构

    对于超大型应用,可以考虑采用微前端架构,将应用拆分为多个独立的微应用。

    // 微前端架构
    // 对于超大型应用,采用微前端架构,将应用拆分为多个独立的微应用
    // 设计意图:通过微前端架构,实现应用的模块化和独立部署,提高开发效率和团队协作

    // 特点:
    // – 将应用拆分为多个独立的微应用,每个微应用可以独立开发、部署和维护
    // – 主应用(shell)负责微应用的加载和管理
    // – 共享资源(packages)供多个微应用复用
    // – 提高了应用的可扩展性和可维护性

    // 适用场景:超大型应用,多个团队负责不同业务模块,需要独立部署和维护
    apps/ # 应用目录,包含所有微应用
    ├── shell/ # 主应用(shell),负责微应用的加载和管理
    ├── auth/ # 认证微应用,处理用户登录、注册等认证功能
    ├── dashboard/ # 仪表盘微应用,展示用户仪表盘和概览数据
    └── products/ # 产品微应用,处理产品相关的功能

    packages/ # 共享资源目录,包含多个微应用共用的代码
    ├── components/ # 共享组件,供多个微应用复用
    ├── utils/ # 共享工具函数,供多个微应用复用
    └── types/ # 共享类型定义,供多个微应用复用

    7.2 架构优化策略

  • 性能优化:

    • 代码分割和懒加载
    • 资源压缩和缓存
    • 减少不必要的重渲染
  • 可维护性优化:

    • 模块化和组件化
    • 代码规范和命名约定
    • 文档和注释
  • 可扩展性优化:

    • 插件化架构
    • 配置驱动开发
    • 依赖注入
  • 安全性优化:

    • 认证和授权
    • 数据验证和 sanitization
    • 防止 XSS 和 CSRF 攻击
  • 7.3 实际项目案例

    电商平台

    架构特点:

    • 模块化设计,按业务功能划分模块
    • 微前端架构,将不同业务域拆分为独立的微应用
    • 服务端渲染,提高首屏加载速度和 SEO
    • 实时数据更新,使用 WebSocket 实现商品价格和库存的实时更新

    核心模块:

    • 用户认证模块:处理用户登录、注册、密码重置等
    • 商品模块:处理商品列表、详情、搜索等
    • 购物车模块:处理购物车添加、修改、结算等
    • 订单模块:处理订单创建、支付、物流等
    • 支付模块:集成多种支付方式
    企业管理系统

    架构特点:

    • 权限管理系统,基于角色的访问控制
    • 工作流引擎,支持自定义业务流程
    • 数据可视化,提供丰富的报表和图表
    • 多语言支持,适应国际化需求

    核心模块:

    • 用户管理模块:处理用户信息、角色、权限等
    • 组织管理模块:处理部门、职位、员工等
    • 资产管理模块:处理资产登记、折旧、盘点等
    • 财务管理模块:处理预算、报销、审批等
    • 项目管理模块:处理项目计划、任务、进度等

    8. 总结与实践

    8.1 项目架构实践

  • 合理的目录结构:

    • 按照功能模块组织代码
    • 建立清晰的层次结构
    • 采用一致的命名规范
  • 状态管理策略:

    • 使用 Pinia 进行状态管理
    • 按模块划分 Store
    • 合理使用持久化存储
  • 路由架构设计:

    • 模块化路由配置
    • 合理使用路由守卫
    • 支持动态路由和权限控制
  • API 层设计:

    • 统一的 API 服务封装
    • 合理的错误处理策略
    • 有效的数据缓存机制
  • 工具类和公共组件:

    • 可复用的工具函数
    • 可配置的公共组件
    • 类型安全的组合式 API
  • 代码质量保证:

    • 严格的 TypeScript 类型检查
    • 统一的代码规范和格式化
    • 完善的测试用例
  • 8.2 团队协作实践

  • 代码规范:

    • 制定统一的代码规范
    • 使用 ESLint 和 Prettier 进行代码检查和格式化
    • 定期进行代码审查
  • 版本控制:

    • 采用 Git 工作流
    • 合理的分支管理策略
    • 规范的提交信息
  • 文档管理:

    • 项目架构文档
    • 组件和 API 文档
    • 开发和部署指南
  • 自动化工具:

    • CI/CD 流程
    • 自动化测试
    • 代码质量检测
  • 8.3 未来发展趋势

  • 微前端架构:

    • 将大型应用拆分为多个独立的微应用
    • 提高团队协作效率和应用可维护性
  • Serverless 架构:

    • 前端和后端都采用 Serverless 架构
    • 降低运维成本和提高扩展性
  • AI 辅助开发:

    • 使用 AI 工具辅助代码生成和优化
    • 提高开发效率和代码质量
  • WebAssembly:

    • 部分计算密集型任务使用 WebAssembly
    • 提高应用性能
  • 边缘计算:

    • 将部分计算和存储迁移到边缘节点
    • 减少延迟和提高用户体验
  • 9. 附录

    9.1 技术栈推荐

    | 类别 | 技术 | 版本 | 用途 |

    总结

    Vue 3 + TypeScript 项目架构设计是一个持续迭代和优化的过程,需要根据项目的具体需求和团队的实际情况进行灵活调整和优化。通过合理的架构设计和实践,我们可以构建出高质量、可维护、可扩展的 Vue 3 + TypeScript 项目,为前端开发团队带来更好的开发体验和更高的生产效率。

    赞(0)
    未经允许不得转载:171主机测评 » Vue 3 + TypeScript 项目架构实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址