完整的Vue3项目文件结构

一个完整的中大型 Vue 3 项目通常会有 5-7 层的文件结构。以下是一个完整的架构图。

🏗️ 完整的 Vue 3 项目文件结构

复制代码
src/
├── views/              # 页面视图层
├── components/         # 组件层(可复用 UI)
├── composables/        # 组合式函数层(逻辑复用)
├── stores/            # 状态管理层(Pinia/Vuex)
├── api/               # API 请求层
├── utils/             # 工具函数层
├── router/            # 路由层
├── directives/        # 自定义指令层
├── plugins/           # 插件层
├── constants/         # 常量定义层
├── types/             # TypeScript 类型定义
├── assets/            # 静态资源
└── styles/            # 全局样式

📁 各层详细说明

1. API 层 (api/)

封装所有 HTTP 请求,统一管理接口地址和请求配置。

javascript 复制代码
// api/request.js - axios 实例配置
import axios from 'axios'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000,
  headers: { 'Content-Type': 'application/json' }
})

// 请求拦截器
request.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

// 响应拦截器
request.interceptors.response.use(
  response => response.data,
  error => {
    // 统一错误处理
    if (error.response?.status === 401) {
      // 跳转登录
    }
    return Promise.reject(error)
  }
)

export default request
javascript 复制代码
// api/user.js - 用户相关 API
import request from './request'

export const userApi = {
  // 获取用户列表
  getUsers(params) {
    return request.get('/users', { params })
  },
  
  // 获取用户详情
  getUserById(id) {
    return request.get(`/users/${id}`)
  },
  
  // 创建用户
  createUser(data) {
    return request.post('/users', data)
  },
  
  // 更新用户
  updateUser(id, data) {
    return request.put(`/users/${id}`, data)
  },
  
  // 删除用户
  deleteUser(id) {
    return request.delete(`/users/${id}`)
  }
}
javascript 复制代码
// api/product.js - 商品相关 API
import request from './request'

export const productApi = {
  getProducts(params) {
    return request.get('/products', { params })
  },
  
  getProductById(id) {
    return request.get(`/products/${id}`)
  }
}

2. 状态管理层 (stores/) - Pinia

管理全局共享状态,跨组件的数据。

javascript 复制代码
// stores/userStore.js - 用户全局状态
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null,
    token: localStorage.getItem('token'),
    permissions: []
  }),
  
  getters: {
    isLoggedIn: (state) => !!state.token,
    hasPermission: (state) => (permission) => state.permissions.includes(permission)
  },
  
  actions: {
    async login(credentials) {
      const { data } = await userApi.login(credentials)
      this.token = data.token
      this.userInfo = data.user
      localStorage.setItem('token', data.token)
    },
    
    logout() {
      this.token = null
      this.userInfo = null
      localStorage.removeItem('token')
    }
  }
})
javascript 复制代码
// stores/appStore.js - 应用全局状态
export const useAppStore = defineStore('app', {
  state: () => ({
    theme: 'light',      // 主题
    sidebarCollapsed: false,  // 侧边栏状态
    language: 'zh-CN'    // 语言
  }),
  
  actions: {
    toggleSidebar() {
      this.sidebarCollapsed = !this.sidebarCollapsed
    },
    
    setTheme(theme) {
      this.theme = theme
      localStorage.setItem('theme', theme)
    }
  }
})

3. 工具函数层 (utils/)

纯函数,无副作用的工具方法。

javascript 复制代码
// utils/date.js - 日期处理工具
export const formatDate = (date, format = 'YYYY-MM-DD') => {
  // 日期格式化逻辑
  return dayjs(date).format(format)
}

export const getRelativeTime = (timestamp) => {
  // 获取相对时间(刚刚、几分钟前)
  const diff = Date.now() - new Date(timestamp).getTime()
  const minutes = Math.floor(diff / 60000)
  
  if (minutes < 1) return '刚刚'
  if (minutes < 60) return `${minutes}分钟前`
  // ...
}
javascript 复制代码
// utils/validator.js - 表单验证工具
export const validators = {
  // 手机号验证
  phone: (value) => /^1[3-9]\d{9}$/.test(value),
  
  // 邮箱验证
  email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
  
  // 身份证验证
  idCard: (value) => /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$/.test(value)
}
javascript 复制代码
// utils/storage.js - 本地存储封装
export const storage = {
  set(key, value) {
    localStorage.setItem(key, JSON.stringify(value))
  },
  
  get(key) {
    const value = localStorage.getItem(key)
    return value ? JSON.parse(value) : null
  },
  
  remove(key) {
    localStorage.removeItem(key)
  },
  
  clear() {
    localStorage.clear()
  }
}

4. 常量定义层 (constants/)

避免魔法数字和字符串。

javascript 复制代码
// constants/roles.js - 角色常量
export const USER_ROLES = {
  ADMIN: 'admin',
  USER: 'user',
  GUEST: 'guest'
}

export const ROLE_PERMISSIONS = {
  [USER_ROLES.ADMIN]: ['*'],
  [USER_ROLES.USER]: ['read', 'write'],
  [USER_ROLES.GUEST]: ['read']
}
javascript 复制代码
// constants/api.js - API 端点常量
export const API_ENDPOINTS = {
  AUTH: {
    LOGIN: '/auth/login',
    LOGOUT: '/auth/logout',
    REGISTER: '/auth/register'
  },
  USER: {
    LIST: '/users',
    DETAIL: (id) => `/users/${id}`,
    UPDATE: (id) => `/users/${id}`
  }
}
javascript 复制代码
// constants/options.js - 下拉选项等常量
export const GENDER_OPTIONS = [
  { label: '男', value: 1 },
  { label: '女', value: 2 }
]

export const ORDER_STATUS = {
  PENDING: { value: 0, label: '待支付', color: 'warning' },
  PAID: { value: 1, label: '已支付', color: 'success' },
  CANCELLED: { value: 2, label: '已取消', color: 'danger' }
}

5. 类型定义层 (types/) - TypeScript

定义 TypeScript 接口和类型。

typescript 复制代码
// types/user.ts
export interface User {
  id: number
  name: string
  email: string
  age: number
  role: 'admin' | 'user' | 'guest'
  createdAt: Date
}

export interface LoginRequest {
  username: string
  password: string
}

export interface LoginResponse {
  token: string
  user: User
}
typescript 复制代码
// types/api.ts
export interface ApiResponse<T = any> {
  code: number
  message: string
  data: T
}

export interface PaginationParams {
  page: number
  pageSize: number
}

export interface PaginationResponse<T> {
  list: T[]
  total: number
  page: number
  pageSize: number
}

6. 自定义指令层 (directives/)

封装 DOM 操作逻辑。

javascript 复制代码
// directives/permission.js - 权限指令
export const permission = {
  mounted(el, binding) {
    const { value } = binding
    const permissions = useUserStore().permissions
    
    if (value && !permissions.includes(value)) {
      el.parentNode?.removeChild(el)
    }
  }
}
javascript 复制代码
// directives/loading.js - 加载指令
export const loading = {
  mounted(el, binding) {
    const div = document.createElement('div')
    div.className = 'loading-mask'
    div.innerHTML = '<div class="loading-spinner"></div>'
    el.loadingMask = div
    
    if (binding.value) {
      el.appendChild(div)
    }
  },
  
  updated(el, binding) {
    if (binding.value !== binding.oldValue) {
      if (binding.value) {
        el.appendChild(el.loadingMask)
      } else {
        el.loadingMask?.remove()
      }
    }
  }
}

7. 路由层 (router/)

页面路由配置。

javascript 复制代码
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    component: () => import('@/layouts/MainLayout.vue'),
    children: [
      {
        path: '',
        name: 'Home',
        component: () => import('@/views/Home.vue'),
        meta: { title: '首页', requiresAuth: true }
      },
      {
        path: 'users',
        name: 'Users',
        component: () => import('@/views/Users.vue'),
        meta: { title: '用户管理', permission: 'user:list' }
      }
    ]
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue'),
    meta: { title: '登录', requiresGuest: true }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 路由守卫
router.beforeEach((to, from, next) => {
  const userStore = useUserStore()
  
  if (to.meta.requiresAuth && !userStore.isLoggedIn) {
    next('/login')
  } else {
    next()
  }
})

export default router

📊 完整的数据流关系图

复制代码
用户操作
   ↓
Vue 组件 (views/components)
   ↓ 调用
Composables (组合式函数)
   ↓ 调用              ↓ 读取/写入
API 层 ←────────────── Stores (状态管理)
   ↓                    ↓
后端接口              组件响应式更新
   ↓
返回数据

🎯 实战示例:完整的用户列表功能

文件结构

复制代码
src/
├── views/
│   └── Users.vue                    # 用户列表页面
├── components/
│   └── UserTable.vue                # 用户表格组件
├── composables/
│   └── useUserList.js               # 用户列表逻辑
├── stores/
│   └── userStore.js                 # 用户全局状态
├── api/
│   ├── request.js                   # axios 配置
│   └── user.js                      # 用户 API
├── utils/
│   ├── date.js                      # 日期格式化
│   └── validator.js                 # 验证工具
├── constants/
│   └── user.js                      # 用户常量
└── types/
    └── user.ts                      # 用户类型定义

各层代码实现

typescript 复制代码
// types/user.ts
export interface User {
  id: number
  name: string
  email: string
  status: 'active' | 'inactive'
  createdAt: string
}

export interface UserQueryParams {
  page: number
  pageSize: number
  keyword?: string
  status?: string
}
javascript 复制代码
// constants/user.js
export const USER_STATUS = {
  ACTIVE: { value: 'active', label: '启用', color: 'success' },
  INACTIVE: { value: 'inactive', label: '禁用', color: 'danger' }
}

export const DEFAULT_PAGE_SIZE = 10
javascript 复制代码
// api/user.js
import request from './request'

export const userApi = {
  getUsers(params) {
    return request.get('/users', { params })
  },
  
  updateUserStatus(id, status) {
    return request.patch(`/users/${id}/status`, { status })
  },
  
  deleteUser(id) {
    return request.delete(`/users/${id}`)
  }
}
javascript 复制代码
// stores/userStore.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    selectedUsers: [],  // 选中的用户
    filters: {}         // 筛选条件
  }),
  
  actions: {
    toggleSelectUser(userId) {
      const index = this.selectedUsers.indexOf(userId)
      if (index > -1) {
        this.selectedUsers.splice(index, 1)
      } else {
        this.selectedUsers.push(userId)
      }
    },
    
    clearSelected() {
      this.selectedUsers = []
    }
  }
})
javascript 复制代码
// composables/useUserList.js
import { ref, computed } from 'vue'
import { userApi } from '@/api/user'
import { DEFAULT_PAGE_SIZE } from '@/constants/user'
import { useUserStore } from '@/stores/userStore'
import { formatDate } from '@/utils/date'

export function useUserList() {
  const users = ref([])
  const loading = ref(false)
  const pagination = ref({
    page: 1,
    pageSize: DEFAULT_PAGE_SIZE,
    total: 0
  })
  
  const userStore = useUserStore()
  
  // 获取用户列表
  const fetchUsers = async () => {
    loading.value = true
    try {
      const { data } = await userApi.getUsers({
        page: pagination.value.page,
        pageSize: pagination.value.pageSize,
        ...userStore.filters
      })
      
      users.value = data.list
      pagination.value.total = data.total
    } finally {
      loading.value = false
    }
  }
  
  // 格式化显示(使用工具函数)
  const formattedUsers = computed(() => {
    return users.value.map(user => ({
      ...user,
      createdAtFormatted: formatDate(user.createdAt),
      statusLabel: USER_STATUS[user.status]?.label
    }))
  })
  
  // 删除用户
  const deleteUser = async (id) => {
    await userApi.deleteUser(id)
    await fetchUsers() // 刷新列表
    userStore.clearSelected() // 清空选中状态
  }
  
  return {
    users: formattedUsers,
    loading,
    pagination,
    fetchUsers,
    deleteUser
  }
}
vue 复制代码
<!-- views/Users.vue -->
<template>
  <div class="users-page">
    <!-- 使用 composable 获取数据 -->
    <UserTable 
      :users="users"
      :loading="loading"
      @delete="handleDelete"
    />
    
    <!-- 使用 store 管理选中状态 -->
    <div v-if="selectedUsers.length > 0">
      已选中 {{ selectedUsers.length }} 条数据
    </div>
  </div>
</template>

<script setup>
import { useUserList } from '@/composables/useUserList'
import { useUserStore } from '@/stores/userStore'
import { onMounted } from 'vue'

const { users, loading, fetchUsers, deleteUser } = useUserList()
const userStore = useUserStore()

const handleDelete = (id) => {
  deleteUser(id)
}

onMounted(() => {
  fetchUsers()
})
</script>

📝 总结:什么时候用什么文件

文件/目录 用途 典型内容
views/ 页面级别组件 路由对应的页面
components/ 可复用 UI 组件 按钮、表格、弹窗
composables/ 可复用逻辑(带状态) 数据获取、业务逻辑
stores/ 全局共享状态 用户信息、主题、权限
api/ 接口请求封装 axios 配置、API 方法
utils/ 纯函数工具 日期格式化、验证函数
constants/ 常量定义 枚举值、配置项
types/ TypeScript 类型 接口定义、类型声明
directives/ 自定义指令 权限、加载、水印
router/ 路由配置 路由表、路由守卫
plugins/ 插件 第三方库配置
assets/ 静态资源 图片、字体、图标
styles/ 全局样式 CSS 变量、重置样式

🎯 核心原则

  1. UI 放 components/views
  2. 逻辑放 composables/stores
  3. 数据放 api/
  4. 工具放 utils/
  5. 配置放 constants/
  6. 类型放 types/
相关推荐
ct9783 小时前
Axios 请求取消
前端·javascript·vue.js
吹个口哨写代码4 小时前
IIS 部署 Vue/React 单页应用 (SPA) 刷新页面 404/403.18 报错原因及终极解决方案
前端·vue.js·react.js
老毛肚4 小时前
jeecgboot vue Pinia 拆分01
前端·javascript·vue.js
小新11017 小时前
从零开始 Vue.js
前端·javascript·vue.js
naildingding18 小时前
Vue基础核心
前端·vue.js
搬砖的阿wei18 小时前
Pinia 与 Vuex 区别
前端·vue.js
用户154471843963120 小时前
从零实现一个vue2项目
vue.js
OpenTiny社区20 小时前
一行命令添加 AI 对话入口!TinyRobot 也太省事了~
前端·vue.js·ai编程
sagima_sdu20 小时前
Vue 前端径向渐变背景制作
前端·javascript·vue.js