目录结构看似只是文件夹摆放问题,实际上它决定了团队协作的边界、代码复用的成本以及后续重构的难度。Vue 3 项目如果一开始没有一个清晰的工程化结构,随着业务膨胀,很快就会出现组件随意放置、接口调用散落各处、公共逻辑复制粘贴等问题。本文给出一套经过实践验证的企业级 Vue 3 项目目录结构模板,并解释每个分层背后的设计考量。

一、整体目录结构模板
先看整体结构,基于 Vite 创建的 Vue 3 + TypeScript 项目,目录大致如下:
vue3-admin/ ├── public/ # 静态资源,不参与构建 ├── src/ │ ├── api/ # 接口请求层,按业务模块拆分 │ ├── assets/ # 样式、图片等资源 │ ├── components/ # 全局通用组件 │ ├── composables/ # 组合式函数 │ ├── directives/ # 自定义指令 │ ├── layouts/ # 布局组件 │ ├── router/ # 路由配置 │ ├── stores/ # Pinia 状态仓库 │ ├── styles/ # 全局样式与变量 │ ├── utils/ # 纯函数工具库 │ ├── views/ # 页面级组件,按业务域分组 │ ├── App.vue │ └── main.ts ├── .env.development # 开发环境变量 ├── .env.production # 生产环境变量 ├── vite.config.ts └── package.json
这个结构的核心思想是职责单一:每个目录只承担一类职责,api 目录不写 UI 逻辑,views 目录不放可复用组件,utils 目录保持纯函数不依赖 Vue 运行时。这样即使新人加入团队,也能通过文件路径快速判断某段代码应该放在哪里。
需要注意的是,src 下的目录数量要控制。一些小项目会把 axios 封装、权限判断、字典缓存全部塞进 utils,导致 utils 变成垃圾抽屉。建议凡是依赖 Vue 响应式系统的逻辑,优先放到 composables;凡是与后端交互的,放到 api;utils 只保留 formatDate、deepClone 这类无副作用的纯函数。
二、按文件类型分层还是按业务领域分模块
这是目录设计中最常见的争论。按文件类型分层(上面模板的方式)把所有页面放 views、所有组件放 components;按业务领域分模块则是每个业务域内部自带自己的 views、components、api,形成一个个垂直切片。
两种方式的对比可以看下表:
| 维度 | 按类型分层 | 按业务领域分模块 |
|---|---|---|
| 适合团队规模 | 中小型团队 | 大型团队、多小组并行 |
| 跨业务复用 | 直观,公共组件集中 | 需要额外的共享层 |
| 模块内聚性 | 较弱,改动散落多处 | 强,可整体迁移删除 |
| 上手成本 | 低 | 较高,需要约定边界 |
实践中推荐的做法是分层为主、域内分组为辅:顶层保持按类型分层的结构,但在 views 和 api 内部按业务域建子目录。例如 views/system/user、views/system/role、views/finance/order,对应的接口文件放在 api/system/user.ts、api/finance/order.ts。这样既保留了公共组件的集中管理,又让业务边界在目录上一目了然。
当项目规模进一步扩大,比如超过五十个页面、多个小组各自负责不同业务线时,可以再演进为按领域分模块,在 src/modules 下按业务域组织完整切片,顶层目录只保留真正全局的东西。
三、关键目录的详细设计与示例
api 层:统一请求入口
api 层的价值在于把接口的 URL、参数、返回类型集中管理,页面不直接拼接请求地址。配合 TypeScript 可以给每个接口定义请求和响应类型:
// src/api/system/user.ts
import request from '@/utils/request'
import type { UserInfo, UserQuery } from './types'
// 获取用户分页列表
export function getUserList(params: UserQuery) {
return request.get<UserInfo[]>('/api/system/users', { params })
}
// 新增用户
export function createUser(data: Omit<UserInfo, 'id'>) {
return request.post<UserInfo>('/api/system/users', data)
}
页面组件中只导入函数调用,不感知 URL 细节。接口变更时只需改这一个文件,全项目受益。request 的实例封装(拦截器、token 注入、错误提示)放在 utils/request.ts 中,与具体业务解耦。
composables:组合式函数抽取
Vue 3 的组合式 API 让逻辑复用从 mixin 时代的主观约定变成了显式函数。命名上建议统一以 use 开头,一个文件只导出一个函数:
// src/composables/usePagination.ts
import { reactive } from 'vue'
export function usePagination(fetchFn: (params: any) => Promise<any>) {
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
async function loadData() {
const res = await fetchFn({ page: pagination.page, pageSize: pagination.pageSize })
pagination.total = res.total
return res.list
}
function handlePageChange(page: number) {
pagination.page = page
return loadData()
}
return { pagination, loadData, handlePageChange }
}
任何列表页都可以复用这套分页逻辑,页面只需传入自己的请求函数。判断一段逻辑是否值得抽成 composable 的标准很简单:它是否同时涉及状态和方法,且会被两个以上页面使用。
router 与 stores 的组织
路由建议拆分为 index.ts(路由实例与守卫)加 modules 子目录(各业务模块的路由片段),并开启懒加载:
// src/router/modules/system.ts
import type { RouteRecordRaw } from 'vue-router'
export default [
{
path: '/system',
component: () => import('@/layouts/BasicLayout.vue'),
meta: { requiresAuth: true, title: '系统管理' },
children: [
{
path: 'user',
name: 'SystemUser',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '用户管理' },
},
],
},
] as RouteRecordRaw[]
Pinia 仓库同样按业务域拆分文件,用户信息、权限、字典缓存各自独立 store,避免出现一个巨大的全局 store。meta 字段中还可以携带权限标识,供路由守卫做按钮级和页面级权限控制。
四、工程化配套:别名、环境变量与规范
目录结构只是骨架,还需要工程化配置来保证它被严格执行。首先在 vite.config.ts 中配置 @ 别名指向 src,并让 TypeScript 的 paths 配置保持一致:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitevite/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
环境变量通过 .env.development 和 .env.production 区分接口地址等配置,代码中用 import.meta.env.VITE_API_BASE_URL 读取。凡是自定义变量必须以 VITE_ 开头,否则不会暴露给客户端代码,这是新手常踩的坑。
最后是规范工具:ESLint 负责代码质量规则,Prettier 统一格式化风格,再配合 eslint-plugin-vue 处理模板写法,husky 与 lint-staged 在提交前自动校验。还可以约定文件命名规则,比如组件目录用 kebab-case、composables 文件用 camel-case,写进团队文档并在 CI 中用脚本检查。
总的来说,目录结构没有唯一正确答案,但边界清晰、约定先行、随规模演进这三条原则是通用的。项目初期用分层结构快速起步,业务膨胀后向模块化演进,始终保持重构的空间,这才是企业级工程化模板的真正含义。