ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Vue 3 项目目录结构实战:设计思路与工程化落地指南

2026/10/4 13:39:44 拓冰建站 浏览量
Vue 3 项目目录结构实战:设计思路与工程化落地指南 刚开始切换 Vue 3 的时候我真正纠结的其实不是 setup 语法也不是 ref 和 reactive 到底该用哪个而是“项目目录到底该怎么摆”。你搜“vue3项目目录结构”能翻到一大堆模板但它们往往只在默认脚手架层面展开真正到了后台管理系统、商城、带权限的复杂项目里那点默认结构根本不够用。这篇文章是根据我这几年带 Vue 3 项目组的实际经验整理的不打算教你怎么炫技而是把目录结构这件事掰开揉碎从设计思路到具体落地再到踩坑记录一次说清楚。无论你是刚用 create-vue 搭完第一个工程还是正在把老项目从 Vue 2 迁到 Vue 3下面这些内容应该都能帮上忙。1. 为什么 Vue 3 项目的目录结构不能照抄 Vue 21.1 Composition API 改变了代码的“组织单元”先说一个很实际的问题Vue 2 时代团队写代码其实是按“类型”归位的。组件里固定是 data、methods、computed、watch 这些选项目录就顺势铺成 components、views、router、store、utils 这种大分类。那时候一个功能模块的代码会被平均分摊到这些目录里比如订单功能订单表格组件放在 components订单页面放在 views订单请求放在 api 或 utils订单状态放在 store。这种目录对小型项目还能忍受但项目一过 20 个页面就会产生一种典型症状改一个订单状态流转的需求你需要在五六个目录之间来回跳稍不注意就漏改。Vue 3 的 Composition API 把代码组织的最小单位从“选项”变成了“逻辑片段”。同一个功能里状态、计算属性、方法可以通过 setup 组合在一起还能抽成独立的 useXxx 函数。这就让目录设计多了一条路按业务域组织。比如你的订单模块可以直接在 src/features/order 下放 order.ts 或 order-composable内含订单状态、下单逻辑、价格计算而不是把这段逻辑强行拆进 utils 和 store。这个变化听起来不大但团队一旦习惯按业务域组织改需求的时候会很舒服——一个功能相关的文件基本上在同一个目录里就能找齐。1.2 好目录结构的验收标准可定位、可约束、可扩展我见过太多团队文件夹名字起得花里胡哨最后连自己都找不到文件。判断一套 Vue 3 目录结构好不好其实不用看多少理论就看三条标准第一可定位。新同事问“商品列表接口在哪个文件”答案应该是唯一的而不是“可能在 api 里也可能在 service 里你看哪个顺眼”。一个目录如果出现了两个名字含义重叠的目录比如同时有 services 和 api那就是隐患。第二可约束。目录结构本质上是对团队的约束它得能拦住坏味道。比如组件里永远不能直接写 axios 请求这个约束怎么落实靠 review 和自觉效果不稳定更靠谱的做法是在目录上体现明确划分 api 层目录所有网络请求只能留在 api 目录里的文件这样 code review 时一眼就能看出谁越界了。第三可扩展。新增一个功能模块时你应该只需要“新建一个文件夹里面放视图、composable、局部组件、接口文件”而不是在十几个平级目录里各加一个文件。这条标准直接决定了你在设计目录时要不要引入业务域的概念。1.3 基于类型与基于业务的两种组织方式很多初学者纠结的问题是我到底应该按“类型”建目录还是按“业务”建目录我的回答是绝大多数项目应该用“混合式”顶层按类型模块内按业务。顶层按类型指的是 src 下保留 views、components、composables、api、stores、router、utils、styles、types 这些约定俗成的类型目录。这样做的好处是心智负担低Vue 生态的工具链、脚手架和大部分开源项目都默认这种结构团队招人进来上手快。模块内按业务指的是在这些类型目录内部不要把所有文件平铺而是按业务模块再分一层。比如 views 下是 views/admin、views/order、views/userapi 下同样对应 api/admin、api/order、api/user而不是 views 下堆 80 个 index.vue。这两种方式没有绝对的对错真正不合适的是全按类型把文件堆成一个大平层或者全按业务把所有东西都塞进一个模块目录里导致全局共享的组件和逻辑没法复用。后面我会详细说说具体怎么落地。2. 搭建 Vue 3 TypeScript 项目的标准目录骨架2.1 用 create-vue 初始化后默认结构到底给了我们什么现在创建 Vue 3 项目我推荐直接用官方脚手架新版本默认已经包含了 Vite、TypeScript、ESLint、Prettier 这些基础设施的选择项。初始化命令很简单npm create vuelatest my-vue3-app按提示勾选 TypeScript、Router、Pinia生成出来的基础结构大致是这样my-vue3-app/ ├── public/ │ └── favicon.ico ├── src/ │ ├── assets/ │ │ └── base.css │ ├── components/ │ │ └── TheWelcome.vue │ ├── router/ │ │ └── index.ts │ ├── stores/ │ │ └── counter.ts │ ├── views/ │ │ ├── HomeView.vue │ │ └── AboutView.vue │ ├── App.vue │ └── main.ts ├── .env.development ├── .env.production ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc.json ├── index.html ├── package.json ├── tsconfig.app.json ├── tsconfig.json ├── vite.config.ts └── README.md官方脚手架给的是一个“最小可用”的结构它的意义不是让你原封不动拿来开发而是让你看清 Vue 3 项目需要管理哪些层面的东西。src 之外有配置文件src 之内分好了静态资源、组件、路由、状态、视图这几块。你在这套骨架上继续发育而不是推翻重来。2.2 根目录配置文件各自扮演什么角色很多人建完项目就直奔 src根目录那一堆配置看都不看这是不对的。先理清根目录几个关键文件package.json 不用多说它是项目的身份证明。要留意其中 type 字段是否为 moduleVue 3 Vite 的现代项目基本都是 ESM这决定了导入导出语法和配置文件本身要用 import/export 而不是 require。vite.config.ts 是构建与开发服务器的总控。目录结构相关的核心配置就在这里比如路径别名 alias、开发环境代理 proxy、CSS 预处理器配置。后面几节我会专门讲别名配置这里先记住凡是会影响“文件从哪里来、资源怎么被加载”的规则几乎都在这个文件里。tsconfig.json 和它的拆分文件负责 TypeScript 编译选项。Vue 3 的 create-vue 模板把 tsconfig 拆成 tsconfig.app.json、tsconfig.node.json 等几份核心原因是 IDE、构建脚本和 Node 侧代码需要的编译目标不同。你在这个文件里配置 paths 路径别名时要特别小心别只改 vite.config.ts两边不一致就会出现“Vite 构建没问题编辑器里却一路红线”的经典事故。.env.development 和 .env.production 是环境变量文件。值得注意的坑是Vite 默认只会把以 VITE_ 开头的变量暴露给前端代码比如 VITE_API_BASE_URL。不要指望在这里定义一堆自定义变量能被业务代码读到命名规则从一开始就要统一。2.3 public 与 src/assets 的分工别再放错静态资源我接手过的项目里最常见的一个目录设计错误就是所有静态资源都往 public 里扔。public 目录的产物是“原样拷贝”里面的文件不会被 Vite 处理和改名路径是写死的绝对路径。而 src/assets 里的资源会被打包器读取经过压缩、指纹命名、按需引入等处理引用方式也应该是 import 或 new URL 的形式。实际使用中我的规则很简单只有那些不需要参与构建逻辑的资源才放 public比如 favicon、roubust 第三方SDK的完整离线包、某些不能改路径的静态文件而图片、图标、样式图片这些会被组件引用的资源全部放进 src/assets。尤其是离线地图打点这类场景如果你要把离线瓦片资源放到 public 下注意目录级别要深一点别和页面文件混在一起否则构建后文件数量一多线上加载路径非常容易错乱。这里再插一个经验assets 目录也不要单靠 css 和 img 两级平铺按模块分一层会更好比如 assets/img/order、assets/img/user。否则几十个页面共用一套图片资源时你根本不知道某张图是被谁在引用想清理都不敢动。3. src 内部的深度拆解每个核心目录怎么设计3.1 views 与 components 的边界到底怎么划src 内部最重要的边界是 views 和 components 这两块。我的划分标准很简单views 里的文件对应路由一个路由至少对应一个页面级组件components 里的文件是被页面复用的功能单元本身不直接出现在路由表里。但在实际项目里这里有个容易被忽视的细节。Views 目录里不只是放一个 index.vue因为一个路由页面的内部复杂度往往很高。比如后台管理系统的用户列表页有搜索表单、工具栏、表格、分页器、新增编辑弹窗如果全写进一个 index.vue文件会膨胀到几百行。我的做法是在 views/user 下这样组织src/views/user/ ├── index.vue # 页面主组件负责组装 ├── UserSearch.vue # 搜索表单 ├── UserTable.vue # 表格 ├── UserDialog.vue # 新增/编辑弹窗 ├── useUserList.ts # 本页面业务逻辑的 composable └── user.api.ts # 本页面专属的接口请求注意 search、table、dialog 这些局部组件没有丢到全局 components因为它们很可能只在当前页面使用。全局 components 目录只放真正会被两个以上页面复用的东西比如上传组件、富文本组件、通用弹窗。这个边界能帮你避免 components 变成一个装满“孤儿组件”的仓库。3.2 composables/把逻辑从组件里“搬出来”的正确姿势Composition API 带来最大的目录变化就是 composables也有团队叫 hooks目录。在 Vue 2 里复用逻辑靠 mixin但 mixin 最大的问题是来源不明你根本不知道一个计算属性是从哪个 mixin 混进来的。Vue 3 的 composable 把逻辑封装成普通函数谁用了它它就存在在谁的作用域里直观且好排查。composables 目录的组织我建议先按功能域拆分再按领域拆分。比如src/composables/ ├── usePagination.ts # 分页通用逻辑 ├── useTableState.ts # 表格加载、刷新、选中的通用逻辑 ├── useFormDialog.ts # 弹窗打开关闭和表单初始化的通用逻辑 ├── order/ │ ├── useOrderCalc.ts # 订单价格计算 │ └── useOrderStatus.ts # 订单状态流转 └── user/ └── useUserInfo.ts # 用户信息获取与缓存放在 composables 里的函数一定要遵守 useXxx 命名规范还要有明确的输入输出不要偷偷修改外部全局状态。我常用的一个心法是如果一个 function 里读写了很多组件外部的变量又没有通过参数传入那它就还不算合格的 composable应该改造成纯函数再提升到 utils。这里要专门提醒一个点composables 目录不要藏“单例状态”。全局状态的管理应该归 Pinia store 管composable 更适合处理与视图逻辑绑定的临时状态和副作用。两条线混了以后会出现“同一个用户信息一会儿从 store 读一会儿从 useUserInfo 初始化”的混乱局面。3.3 stores/Pinia 来了目录怎么跟上前端需求变化Pinia 已经成为 Vue 3 官方推荐的状态管理方案它对应的目录也从 Vue 2 的 store/modules 变成了语义更清晰的 stores。我建目录时习惯按业务模块拆分一个模块一个文件命名上使用组合式风格而不是选项式风格// src/stores/order.ts import { defineStore } from pinia import { ref, computed } from vue import { fetchOrderList } from /api/order export const useOrderStore defineStore(order, () { const orderList ref([]) const loading ref(false) const total computed(() orderList.value.length) async function loadOrderList() { loading.value true try { orderList.value await fetchOrderList() } finally { loading.value false } } return { orderList, loading, total, loadOrderList } })组合式写法看起来像普通 composable但它依然是一个 store在 Pinia 里通过 id 区分。store 文件内部也要克制不要塞所有业务字段只放真正需要跨组件共享的“全局状态”组件内部自己管得住的临时状态留在组件里就好。一个额外的组织技巧是如果 store 文件比较大可以在 stores 目录下建子目录并按模块拆分比如 stores/user、stores/cart、stores/order。但对应关系要跟命名跟紧不要让 stores 里出现一个谁都找不到归属的文件。3.4 api/ 层把网络请求集中起来别让 fetch 满天飞如果你去翻那些目录混乱的 Vue 3 项目大概率能看到 axios/fetch 调用散落在 views 和 composables 里。短时间看是省事但接口一变你要搜索所有字符串去改路径很容易漏。更严重的是你没有办法统一处理错误、取消重复请求、注入 token。所以我在项目里一定会让 api 作为一个独立的层目录存在。基本结构是这样的src/api/ ├── http.ts # axios 实例、拦截器、基础请求封装 ├── types.ts # 与后端交互的通用类型如分页结构、响应结构 ├── user.ts # 用户模块接口 ├── product.ts # 商品模块接口 └── order.ts # 订单模块接口http.ts 是唯一的 axios 实例出口所有请求都通过这个实例发出这样 token 注入、错误码提示、接口路径拼接都只在一处处理。api 目录下的业务模块文件里只导出函数不写组件逻辑// src/api/order.ts import http from ./http import type { OrderItem, OrderQuery } from ./types export function fetchOrderList(params: OrderQuery) { return http.getOrderItem[](/order/list, { params }) }api 目录的设计等于给团队立了一条硬性规矩组件里不允许出现裸 axios 调用。规矩不需要挂在墙上它天然存在于目录结构里code review 时只要看到某组件 import 了 axios就已经算违规了。3.5 router/路由文件怎么拆懒加载怎么配路由目录虽然一般不起眼但它和目录结构是强相关的——因为路由是页面目录的入口。Vue 3 项目里路由文件不应该是一个只增不删的 index.ts尤其当项目页面超过 50 个时一个 flat 的路由表会变成“怎么都找不到要改哪一段”的灾难。我常用的路由组织方式有两种。项目模块边界清晰时按模块拆分路由定义文件src/router/ ├── index.ts # 创建 router 实例、配置全局守卫 ├── modules/ │ ├── dashboard.ts │ ├── order.ts │ └── user.ts └── routes.ts # 汇总所有模块路由每个模块的路由文件内部都应该使用动态导入实现懒加载// src/router/modules/order.ts const orderRoutes [ { path: /order, component: () import(/views/order/index.vue), children: [ { path: detail/:id, component: () import(/views/order/detail.vue), meta: { title: 订单详情 } } ] } ] export default orderRoutes懒加载最大的意义不是“省代码”而是按页面拆分包首屏只加载当前路由需要的 JS。这对中大型项目体验提升很明显。还有一点很多人会忽略meta 字段要提前规划好比如 title、requiresAuth、permission。后台管理系统往往会围绕路由 meta 来做菜单生成和权限控制如果一开始没设计后面加权限就跟在没打地基的楼上盖两层一样难受。3.6 utils、styles 与 types 的“三不管地带”怎么处理views、components、composables、stores、api、router 这些目录都很明确但总有文件不知道该放哪。我的处理方式是把它们压进三个兜底目录并且给每个目录定好准入标准。utils 目录只收纯函数或者说不依赖 Vue 运行时的工具函数。格式格式化、日期处理、树结构转换、浏览器 cookie 操作这类。utils 文件要保证是可以被单独测试的如果你在一个 utils 文件里 import 了某个 store 或某个组件那它就不是 utils而是业务逻辑。styles 目录要区分全局样式和模块样式。全局变量、混入、基础样式放 styles页面和组件的局部样式用 scoped 写在对应组件里。如果你用的是 SCSSstyles 下建议放 variables.scss、mixins.scss、reset.scss 这些。注意全局样式文件不要到处 import更推荐在 vite.config.ts 里配置 css.preprocessorOptions 实现自动注入。types 目录是 TypeScript 项目特有的一层用来放全局共享的类型定义。但我要提醒一点types 目录不要变成一个“所有类型都往里丢”的垃圾桶。常见做法是跟 api 模块强相关的类型放在 api 模块文件内部比如 OrderItem、OrderQuery跨多条业务线共享的类型才提升到 types 目录比如分页结构 PageResult、统一响应结构 ApiResponse。两者边界清晰才不会出现“为了找某个接口的返回类型得去 types 目录翻半小时”的尴尬。4. 高频实战场景下的目录落地后台管理、商城、前后端联调4.1 后台管理系统布局、权限、动态路由的目录配套后台管理系统应该是 Vue 3 中需求量最大的项目类型。它的目录设计比普通网站多出两个维度布局和权限。我常用的后台目录框架大概是src/ ├── api/ │ ├── system/ # 用户、角色、菜单等系统接口 │ └── business/ # 具体业务模块接口 ├── layouts/ │ ├── index.vue # 整体布局骨架 │ ├── Sidebar/ │ ├── Navbar/ │ └── TabsView/ # 多页签 ├── router/ │ └── modules/ # 按权限模块拆路由 ├── stores/ │ ├── user.ts # 用户信息与权限点 │ ├── app.ts # 侧边栏折叠、页签状态等 UI 状态 │ └── permission.ts # 可访问路由列表 ├── views/ │ ├── dashboard/ │ ├── system/user/ │ ├── system/role/ │ └── business/... ├── components/ │ ├── TablePro/ │ ├── SearchForm/ │ ├── DialogForm/ │ └── SvgIcon/ └── composables/ ├── useTablePro.ts └── usePagination.tslayouts 目录在后台管理里非常关键它负责把导航侧栏、顶栏、多页签和路由出口组合在一起。视图组件不直接写整体框架而是嵌套在布局里。动态路由是另一个搭配点后端根据用户权限返回菜单路由前端在路由守卫里用 router.addRoute 动态注册。目录要注意的是动态加载的视图路径不要写死字符串去拼容易导致构建时找不到组件最好在目录层级上与路由模块一一对应。4.2 商城类项目业务域边界怎么划分商城类项目和后台管理比页面复杂度不一定更高但业务域特别清楚商品、购物车、订单、支付、用户中心。这种项目我在顶层类型目录的基础上会把业务域区分得更明显。以订单模块为例src/ ├── api/ │ ├── product.ts │ ├── cart.ts │ ├── order.ts │ └── payment.ts ├── stores/ │ ├── cart.ts │ ├── product.ts │ └── order.ts ├── views/ │ ├── product/ │ ├── cart/ │ ├── checkout/ │ └── order/ ├── components/ │ ├── product/ │ ├── cart/ │ └── common/ ├── composables/ │ ├── product/ │ ├── cart/ │ └── order/这样划分之后每个业务的改动都只涉及它对应的一组目录。比如支付成功后更新订单状态涉及的就是 stores/order.ts、api/order.ts、views/order/ 和几个 composable不会误伤其他模块。商城还有一个特点是要适配多端或多种场景。比如离线地图可打点这种功能会涉及地图 SDK、打点数据源和业务组件我建议在 components 下单独建一个 map 子目录里面放 MapContainer、MapMarker、MapPopup 等然后通过 composables 把地图初始化的生命周期逻辑抽出来。不要把地图相关代码散落到订单、配送各页面里否则地图升级 SDK 时你会为“每个页面都有 initMap”付出代价。4.3 与 FastAPI 或 Java 后端配合时的目录约定目录结构不只和前端自己有关前后端的配合方式也会反推目录设计。比如后端用的是 FastAPI它的路由结构常常按模块组织为 user、order、product前端 api 目录也按同样的模块命名联调时两边对文件的成本就低很多。更进一步前端 api 层和类型文件要与后端的接口 schema 对齐。FastAPI 通常有 Pydantic 模型前端可以维护一份与之对应的 TypeScript 类型放在 api 或 types 目录里。后端返回体如果有一个约定的统一结构比如// src/api/types.ts export interface ApiResponseT { code: number message: string data: T }前端所有请求函数都基于这个结构解包后端换字段名时前端只改对应的一个类型定义即可。这里还有个实操经验联调阶段接口地址频繁变更建议把 baseURL 放到环境变量里同时在 vite.config.ts 配置 proxy 指向本地后端端口。目录上不要把后端地址写进代码写进配置不然切环境时要全项目搜“localhost”。5. 我在 Vue 3 目录结构上踩过的坑和调整记录5.1 到处都是绝对路径和深层相对路径早期做项目时大家图省事import 路径直接写 ../../../../utils/format。一开始觉得没什么后来重构目录把 utils 文件夹挪了一个层级结果全局搜索替换花了一个小时还漏了好几个没改到构建直接报错。这个坑的本质是目录结构没有在 import 层形成“稳定接口”。我现在的做法是在 vite.config.ts 和 tsconfig 里统一配置路径别名// vite.config.ts import { fileURLToPath, URL } from node:url import { defineConfig } from vite export default defineConfig({ resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)) } } })// tsconfig.app.json { compilerOptions: { baseUrl: ., paths: { /*: [src/*] } } }之后所有组件 import 都用 / 开头比如 import { fetchOrderList } from /api/order。这样一来即使整个 src 内部再调整层级只要别名不变业务代码就不用改。目录结构有了喘息空间。5.2 循环依赖的典型场景与解决办法目录分得再清也架不住互相引用。我遇到过两个特别典型的循环依赖场景。第一个场景是 api 文件和 store 文件互相 import。比如 order.ts 这个 api 模块里导入 useOrderStore 来获取用户 token而 stores/order.ts 里又导入 api/order.ts 来拉列表。运行时可能表现为 store 初始化时 undefined或者请求被拦截器卡住。解决办法是把需要 token 的逻辑从 api 层拿走token 应该在 http 拦截器里统一读取并注入而 http.ts 只依赖一个简单的 token 存储模块不依赖任何业务 store。第二个场景是两个组件互相引用最常见是父子弹窗组件互相传引用。目录上要避免它们互相 import做法是把被共享的逻辑抽到 composable 或 store 中让组件之间的依赖变浅。排查循环依赖有一个很实用的命令运行vite build时如果出现“Circular dependency”提示立刻先检查这几个文件的 import 链不要等到线上出问题再回头找。5.3 目录过扁与过深两种极端都要避免有些项目特别“平”components 下直接放 50 个组件文件api 下直接放 20 个 ts 文件。这种结构表面简单但一旦文件数量变大你打开目录要滚动很久而且命名冲突概率很高。反过来的极端是目录嵌套过深有人能建出 src/views/business/order/list/detail/components/ 这种四层往上的路径单看路径脑袋都大。我的经验法则有三条。第一单目录内文件数量超过 15 个到 20 个就要考虑按子模块拆分。第二从 src 开始到最深层文件目录层级建议控制在四层以内超过就说明嵌套过度。第三每个目录尽量有单一职责目录名和内容要一致宁可多建一个 index.ts 做中转也不要让目录像个杂物箱。5.4 TS 项目中的类型目录处理和路径别名报错TypeScript 引入后目录结构会多出很多和类型相关的报错。常见的一种是“找不到模块或其相应的类型声明”这往往不是因为类型不存在而是 tsconfig 里的 paths 和 vite 的 alias 没有保持同步。我在几个若依 Vue3 老项目接新需求时也遇到过类似 ts 报错最后排查下来基本都是 tsconfig 没有正确配置 baseUrl 或 paths。另一个容易忽略的点是 import type 的使用。在开启了 verbatimModuleSyntax 或严格模式的项目里纯类型导入必须用 import type否则构建阶段会报错。这也算目录结构配套的一部分类型定义放在 types 目录里导入时明确区分类型和值编译期才能干净。5.5 一次渐进式目录重构的操作记录最后分享一次真实的重构操作。一个 Vue 3 TS 的后台项目初期图快把所有东西按类型堆在一起views 下有 60 个页面文件components 下堆了 30 个。我带着组员做了一次渐进式重构步骤可以给正在发愁的人参考第一步先列模块清单按业务划成 dashboard、system、order、user、report 五个一级模块。第二步把 api 目录按模块拆开逐个移动文件并修正 import 路径这个过程里只做移动不改业务代码风险很小。第三步把 views 下同模块的页面归到一个目录同时把页面专属的局部组件从全局 components 移进 views 对应模块下。第四步把 composables 里已经积累的几个 useXxx 归类到对应模块。第五步删除没有引用方的孤儿文件跑一遍全量构建和回归测试。整个重构拆成了三轮提交每轮都能构建通过团队没有因为“大重构”停工。这套思路最重要的是目录结构的变化可以像功能迭代一样小步推进不要指望一次 git commit 全改完。6. 让目录结构真正提升效率的工具与工作流6.1 Vite 别名与 tsconfig 的同步配置配置路径别名是目录结构最基础的建设值得花几分钟把配置写对。除了前面说的 指向 src还可以针对某几个大目录单独起别名比如resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)), api: fileURLToPath(new URL(./src/api, import.meta.url)), views: fileURLToPath(new URL(./src/views, import.meta.url)) } }注意每个别名都要在 tsconfig 的 paths 里同步一份。如果觉得维护麻烦只用 一个别名也完全够用关键是一致性。6.2 目录聚合导出 index.ts小心使用很多项目喜欢在每个目录下放 index.ts 做聚合导出比如 components/index.ts 统一 export 所有组件api/index.ts 统一 export 所有接口。好处是 import 路径精简坏处是文件一多会形成巨大的引用链构建时可能连带加载用不到的文件还更容易触发循环依赖。我的建议是不要盲目做全目录聚合。对于 api 和 composables 这种“按需导入更清晰”的目录让每个文件独立导出对于组件库类的目录可以用 index.ts 做受控导出但宁可显式列出导出列表也不要写成动态遍历后全部 export 的模式。6.3 按目录约定自动生成路由或菜单当目录结构稳定以后可以进一步利用“约定优于配置”来做自动化。比如你规定 views 下的每个一级目录对应一个路由模块那么就能写一个脚本扫描目录自动生成路由表。或者用 Vite 插件支持文件路由模式路径直接映射为 URL——像 pages 目录下 product/list.vue 自动对应 /product/list。这类方案能大幅减少手工维护路由表的成本但我的建议是不要把魔法做得太深。目录约定和路由映射之间存在隐式规则新人不熟悉时会踩坑。小到中项目手工维护路由表完全够用项目大到几十个页面时自动化才凸显价值。6.4 页面到代码的快速定位靠的首先是好目录热词里有人问“vue3 可以根据页面快速定位到代码的插件”我理解这种需求很多开发者在浏览器里看到一个页面想知道它对应哪个组件文件。Vue DevTools 在 Vue 3 里能很好地支持 component tree 定位编辑器的插件也可以做 Open in Editor。但对大项目来说最稳定的做法反而简单目录结构和路由命名保持一致页面路径就是文件路径/views 下搜一下就定位到了。比如商品详情页是 /product/detail/1那它的视图文件大概率就是 src/views/product/detail.vue接口在 src/api/product.ts状态在 src/stores/product.ts。这种“不看插件也能猜到文件位置”的流畅感才是目录结构带来的真正效率。最后再分享一个经验目录结构不是一次定型的东西它会随着项目规模、团队构成和业务演进不断调整。但只要守住每个目录的职责边界、维护好路径别名、让文件位置能够被稳定预测你在 Vue 3 项目里的大多数目录烦恼都会消失。