
简介这是基于 Vite 构建的 Vue3 项目组织模板压缩包共 233 个文件、约 9.9 MB其中 159 个 Markdown 文档覆盖使用说明与知识点记录17 个 JavaScript、11 个 TypeScript、5 个 Vue 与 5 个 CSS 文件构成完整前端工程骨架另有少量 SQL、JSON、SVG、HTML 等辅助资源。模板按根组件、路由、布局与业务组件分层组织采用 Composition API 管理逻辑集成 Vue Router 路由、Vuex 状态管理、ESLint/Prettier 代码规范与基础测试配置同时附带多套主题样式及常用布局、正则、rem 单位等示例页面可直接复用或按需调整。模板目录中还包含构建配置、公共工具函数与静态资源模块方便直接运行和二次开发。面向中高级前端开发者可显著减少项目初始化与架构搭建时间帮助形成清晰、可维护的代码组织方式对理解 Vite 工作流、组件拆分和状态管理也有实际参考价值。目前已有 786 人学习下载适合作为 Vue3 工程化实践或新项目起步的实用模板。1. Vue3 项目模板它组织代码的方式比脚手架本身更值钱拿到这份 vue3 项目模板的压缩包时我先扫了一遍目录树。和常见脚手架直接抛给你一个能跑的 demo 不一样它把 api、stores、views、components、modules 分域铺开路由权限、请求封装、环境变量都给出了统一写法。这套组织方式适合两类人准备从 vue2 或原生 Vite 工程迁移过来的开发者以及想统一团队规范、不想每次起新项目都争论目录怎么摆的人。模板自带 Pinia 状态管理、axios 请求层、自动导入和 mock 数据解压装上依赖就能跑。一句话它解决的不是「怎么把 demo 启动」而是「项目变大之后代码还找得到、改得动」。下面按落地顺序拆开讲。2. 工程结构拆解src 分域目录与构建配置三件套模板的优雅体现在一个细节整个 src 都按职责分域而不是按页面堆文件。我第一次打开时数了一下顶层就十来个文件夹每个名字都指向一类明确的改动。这种组织方式在只有两三个页面的小项目里看不出优势一旦超过十个页面、前后端并行开发时好处会明显放大。2.1 目录树每个文件夹只干一件事# 解压后的顶层结构省略 node_modules 和 dist ├── .env # 所有环境共享的变量 ├── .env.development # 本地开发环境 ├── .env.production # 生产构建环境 ├── vite.config.ts # Vite 构建配置 ├── src/ │ ├── api/ # 接口层按业务域拆文件 │ │ ├── request.ts # axios 实例与拦截器 │ │ └── user.ts # 用户相关接口 │ ├── assets/ # 静态资源 │ ├── components/ # 通用组件自动导入 │ ├── layout/ # 后台布局侧边栏、顶栏 │ ├── modules/ # 业务模块可插拔的小应用 │ ├── mock/ # 开发环境 mock 数据 │ ├── router/ # 路由表 守卫 │ ├── stores/ # Pinia 状态 │ ├── styles/ # 全局样式变量 │ ├── types/ # 全局 TS 类型 │ ├── utils/ # 纯函数工具 │ ├── views/ # 页面组件按业务分文件夹 │ ├── App.vue │ └── main.ts └── package.jsonviews 是唯一放页面组件的地方我会让 views 下的文件夹和后端模块一一对应比如 user、order、goods一个文件夹里放页面、子组件和自己私有的样式。api 层跟 views 同构但只做接口定义和类型导出不掺业务逻辑。layout 负责整个后台的骨架页面路由只需要作为 children 挂进去。modules 目录是这套模板里比较灵活的设计适合放相对独立的业务能力比如扫码登录、批量导出、消息中心。它内部自带 api、views、store像是可以插拔的小应用。如果多人协作每个人认领一个 module 就能明显减少代码冲突。utils 只放纯函数比如日期格式化、防抖节流原则是不允许 import 组件或者 store否则很容易形成循环依赖。types 放全局类型比如后端返回结构 ResultT、分页参数 PageParamsapi 和 views 都引用这些类型集中定义能避免每个文件重复手写。2.2 构建配置别名、自动导入、环境变量三件套// vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite export default defineConfig({ plugins: [ vue(), AutoImport({ imports: [vue, vue-router, pinia] }), Components({ dirs: [src/components] }) ], resolve: { alias: { : /src } }, server: { port: 5173 } })AutoImport 里写了 vue 之后ref、reactive、computed 不用每个文件手动 import组件注册同理放在 src/components 里的组件在模板中直接用不用写 import。resolve.alias 里的 指向 /src主要价值是摆脱 ../../../ 这种相对路径。注意自动导入省的是 import 语句不会帮你做 Tree-shaking 决策该用按需引入的第三方库还是要显式引。环境变量这块模板用 Vite 的标准机制把环境差异收敛到根目录的几个文件里变量名开发环境值生产环境值作用VITE_API_BASE/api/prod-api所有请求的统一前缀VITE_MOCKtruefalse是否注入 mock 数据VITE_APP_TITLE本地开发生产环境浏览器标题、水印等运行时变量# .env.development VITE_API_BASE/api VITE_MOCKtrue VITE_APP_TITLE本地开发Vite 约定以 VITE_ 开头的变量才会暴露到 import.meta.env其余变量业务代码里根本拿不到。生产环境值放在同目录的 .env.production 里同一个 key 会被覆盖。业务代码里判断环境只有一个标准——import.meta.env.MODE不要在 Header 里猜域名那都是玄学。改完 env 文件必须重启 dev serverVite 不会热更新 env 配置。2.3 规范层eslint、prettier、commitlint 怎么协同{ scripts: { dev: vite, build: vue-tsc --noEmit vite build, preview: vite preview, lint: eslint src --ext .ts,.vue, format: prettier --write src } }build 前先跑 vue-tsc 类型检查类型错误直接中断构建能把运行时 bug 提前挡掉一半。lint 只扫 src 不扫 node_modules配合 --max-warnings 0 之后警告也按错误处理团队里不会出现「我代码有 warning 但能跑」的争论。Prettier 只管格式化ESLint 管代码质量两者分工明确模板里再用 editorconfig 统一缩进和换行符。husky 的 pre-commit 钩子只检查暂存区文件避免全量检查浪费时间。commitlint 强制提交信息带 feat/fix 这类类型前缀后面用 git log 回溯改动时会轻松很多。3. 核心模块拆解一个请求从点击到渲染的完整链路这一章是模板里最值得抄的三块代码我放在一条链路上讲用户在页面点一个按钮调 api 发请求路由守卫判断要不要放行store 保存状态回填页面。模块各自独立串起来就是一次完整的数据流。3.1 请求层axios 实例与业务码约定// src/api/request.ts import axios from axios import { useUserStore } from /stores/user import router from /router const service axios.create({ baseURL: import.meta.env.VITE_API_BASE, timeout: 15000 }) // 请求拦截把 token 带进 header service.interceptors.request.use((config) { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.token} } return config }) // 响应拦截统一处理后端返回的 code service.interceptors.response.use( (response) { // 约定返回格式为 { code, message, data } const { code, message, data } response.data if (code 0) return data return Promise.reject(new Error(message)) }, (error) { if (error.response?.status 401) { // token 失效清掉登录态并跳回登录页 const userStore useUserStore() userStore.logout() router.push({ name: login }) } return Promise.reject(error) } ) export function getT(url: string, params?: object) { return service.getT, T(url, { params }) } export function postT(url: string, data?: object) { return service.postT, T(url, data) }baseURL 直接取 env 里的 VITE_API_BASE本地开发值是 /api生产环境由部署时的变量覆盖。timeout 设了 15 秒超过就触发 error 分支弱网项目可以调到 30 秒但要注意超时不代表请求被取消后端可能还在处理写 loading 状态时别依赖超时来复位。请求拦截只干一件事——把 token 从 store 取出来塞进 header不要在拦截器里写死 token 值token 统一由 store 管理。响应拦截把 code0 的 data 直接剥出来业务代码拿到的就是干净的数据不用在每个页面里都写 if (res.code 0)。401 分支放在拦截器里统一跳登录避免每个接口都重复一遍。一个常见的误用是上传或下载接口的 responseType 是 blob拦截器盲目判断 code 会把文件流搞坏模板的做法是下载单独封装一个方法不走这个拦截逻辑。3.2 路由层白名单、登录跳转与动态路由入口// src/router/index.ts import { createRouter, createWebHistory } from vue-router const routes [ { path: /login, name: login, component: () import(/views/login/index.vue), meta: { public: true } }, { path: /, component: () import(/layout/index.vue), children: [ { path: dashboard, name: dashboard, component: () import(/views/dashboard/index.vue) } ] } ] const router createRouter({ history: createWebHistory(), routes }) router.beforeEach((to) { const userStore useUserStore() if (!to.meta.public !userStore.token) { return { name: login, query: { redirect: to.fullPath } } } if (to.name login userStore.token) { return { name: dashboard } } }) export default routercreateWebHistory 是 HTML5 history 模式URL 干干净净如果服务端实在没法配回退可以换成 createWebHashHistory 用 #/ 兜底。meta.public 用来标记不需要登录的页面login 进白名单。用 to.meta 判断而不是全局判断扩展性更好——以后加找回密码、公开活动页只要标 public 即可不用改守卫逻辑。redirect 参数把用户本来想去的地址带上登录完直接回跳省掉在 localStorage 里存上一跳地址的麻烦。模板给 modules 预留了动态路由入口每个 module 导出自己的 routes 数组登录后按角色权限 addRoute 挂载。这里有个血泪经验addRoute 重复添加同一 name 会告警挂载前先用 hasRoute 判断退出登录时要 removeRoute 清理否则换账号能看到上一个账号的菜单。3.3 状态层Pinia setup store 的写法约定// src/stores/user.ts import { defineStore } from pinia import { loginApi } from /api/user export const useUserStore defineStore(user, () { const token ref(localStorage.getItem(token) || ) const userInfo ref(null) const isLoggedIn computed(() !!token.value) async function login(payload: { username: string; password: string }) { const res await loginApi(payload) token.value res.token userInfo.value res.userInfo localStorage.setItem(token, token.value) } function logout() { token.value userInfo.value null localStorage.removeItem(token) } return { token, userInfo, isLoggedIn, login, logout } })defineStore 第二个参数传函数就是 setup 写法ref 就是 statecomputed 是 getterfunction 是 action最后统一 return 出去。组件里 useUserStore() 之后要注意直接解构 state 会丢失响应性要用 storeToRefs 解构属性方法直接取就行。login 里调用的是 api 层导出的 loginApistore 不直接 import axios数据流保持单向页面 - store - api - 后端。提示不要在组件顶层解构整个 store 对象这是 Pinia 最经典的翻车点。为什么选 Pinia 不选 VuexPinia 去掉了 mutations写法更接近组合式类型推导对 TS 友好。vue2 迁移过来的同事可能会不习惯直接改 state但恰恰是这一步减少了大量样板代码。持久化用 localStorage 做最简版就够复杂场景可以上持久化插件但插件的序列化方案要自己验证不推荐把大对象塞进 localStorage。4. 从解压到上线跑通模板与部署回退的完整步骤拿到压缩包后的动作顺序是环境 - 依赖 - 本地调试 - 构建 - 部署。每一步都有对应的失败点我按实际跑通的流程讲。4.1 前置检查Node 版本与依赖安装先看本机 Node 版本常见做法是要求 Node 18 以上16 也能跑但第三方依赖容易报一些奇奇怪怪的告警。打开终端执行 node -v 确认版本没问题就装依赖cd vue3-project-template npm install依赖安装阶段的报错九成来自网络和版本缓存。npm install 中途失败时先删掉 node_modules 和 package-lock.json 再重装比反复重试省时间。如果你发现装完的依赖和模板要求的版本对不上以 lock 文件为准锁定版本才能保证团队环境一致。装完后先跑 npm run dev 而不是急着改代码确认模板能起再动手。提示第一次启动如果端口被占用Vite 会自动换端口浏览器地址以终端输出为准。4.2 本地调试mock 开关与接口约定模板的 mock 由 VITE_MOCK 控制只在开发环境打开。mock 数据按业务域写在 src/mock 下比如登录接口// src/mock/user.ts const userMock [ { url: /api/login, method: post, response: () ({ code: 0, data: { token: mock-token, userInfo: { name: admin } } }) } ] export default userMockmock 的 url 要和 VITE_API_BASE 对得上。request.ts 里 baseURL 是 /apimock 文件的 url 也是 /api/login拼起来一致才能命中。开发时接口 404第一反应就去核对这两个前缀比查半天插件配置快得多。mock 只在开发阶段用生产构建不要带进去通过 .env.production 里 VITE_MOCKfalse 关掉。登录态的处理是mock 返回 tokenstore 把它写进 localStorage刷新页面不会丢登录。4.3 构建与部署路径回退与子路径配置npm run build npm run previewbuild 会先跑 vue-tsc 类型检查再打包dist 产物可以直接扔给静态服务器。preview 是本地模拟线上环境验证 history 路由刷新是否 404 最方便部署前先跑一遍能省一次发布事故。server { listen 80; root /var/www/html; location / { try_files $uri $uri/ /index.html; } }这行回退的含义是访问 /dashboard 时服务器先找物理文件找不到就返回 index.html由前端路由接管。如果部署在子路径下比如 https://站点/admin/vite.config 里要设 base: /admin/服务端也要在 location /admin/ 里做同样的回退两边对不上就会出现「首页能开、一刷新就 404」。assets 里的文件带了内容 hash部署后可以放心开强缓存。5. 避坑记录模板跑起来之后的五个高发问题这些坑我在不同项目里真实撞过按 现象 - 原因 - 解决 的顺序写。前四条跟模板直接相关第五条是接手任何 Vite 工程都会遇到的高发问题。5.1 开发环境接口全部 404mock 却没生效现象页面能正常打开Network 里 /api/login 全是 404mock 一条都没命中。原因模板把 mock 挂在 vite.config 的 plugins 里只有 VITE_MOCK 为 true 才注入。常见两种情况一是 .env.development 里变量名拼错比如写成 MOCKtrue 少了 VITE_ 前缀插件拿到的值是 undefined二是 mock 文件的 url 写的是 /login而请求层 baseURL 是 /api拼接后 /api/login 对不上 /login。解决先看 env 文件变量名是不是 VITE_ 开头再看 mock 的 url 是否包含 /api 前缀两者一致后重启 dev server。env 改动不会热更新重启是必须的。多人协作时 mock 文件合并冲突很常见建议一个业务域一个 mock 文件别都堆在单文件里。5.2 history 路由刷新白屏或 404现象本地 dev 一切正常部署到服务器后刷新 /dashboard 变成 404首页能开。原因浏览器地址栏发的是真实 HTTP 请求服务器在 /dashboard 这个路径下找不到 index.html。开发时 Vite 内置了回退逻辑生产环境没人管这一层。解决用 4.3 的服务端回退配置。如果服务端实在没法改模板层面可以退一步用 createWebHashHistory代价是 URL 里多一个 #/刷新时 hash 部分不会发给服务器也能解决。5.3 自动导入的组件在 TS 里报找不到现象页面渲染正常编辑器里组件名一直标红命令行类型检查也报 Cannot find module。原因unplugin-vue-components 在 dev 启动时生成 components.d.ts记录哪些组件可以自动导入。新增组件后如果没重启 dev server 或重新触发类型检查d.ts 还停留在旧状态tsconfig.json 的 include 里漏掉这个文件TS 也会看不到类型。解决检查 tsconfig include 是否覆盖 auto-imports.d.ts 和 components.d.ts然后重启 TS 服务再跑一次 dev 触发类型文件刷新。这类生成文件不要手写也不要提交后反复手改它本身就是要靠工具链维护的。5.4 路由守卫里用 Pinia 报 getActivePinia现象打开页面直接白屏控制台报 getActivePinia was called with no active Pinia。原因路由文件的创建发生在 main.ts 执行完 app.use(pinia) 之前而某个文件在模块顶层就调用了 useUserStore()。Pinia 实例还没安装store 自然拿不到上下文。解决把 store 的获取挪到 beforeEach 回调里执行或者保证 createPinia 在 import router 之前完成。模板的约定是路由文件顶层绝不写 useXxxStore()只在函数体内取。顶层调用的问题在组件里不明显因为组件挂在 app 之后最容易翻车的就是路由和工具函数模块。5.5 环境变量读出来全是 undefined现象import.meta.env.VITE_API_BASE 在业务代码里打印是 undefined请求发出去了但前缀是空的。原因三类原因按概率排env 文件放错了位置Vite 只读项目根目录的 .env放 src 下不生效变量名没带 VITE_ 前缀Vite 默认不暴露给客户端改完 env 没重启 dev加载的还是旧文件。解决把 .env、.env.development 放到和 vite.config.ts 同级变量统一 VITE_ 开头每次改完 env 强制重启 dev server。生产环境出问题先确认 .env.production 是否存在很多部署事故都来自漏了这个文件。6. 二次开发从模板长成业务系统的三个切入点拿到模板先别急着删文件我通常从三个切入点动手。第一个切入点把 api 层变成与后端模块一一对应的文件组。模板里的 user.ts 只是示例业务里我会按 order.ts、goods.ts、stat.ts 拆分每个文件导出针对单一资源的函数views 里按业务模块建文件夹组件只依赖对应 api 文件跨模块调用统一走 store。后端接口变动时改动范围能控制在两个文件内。第二个切入点权限从静态路由变成动态路由。登录后根据后端返回的角色和路由名用 router.addRoute 把对应模块的路由挂上去beforeEach 里做白名单校验。改的时候注意 addRoute 重复挂载会告警先用 hasRoute 判断退出登录要 removeRoute 清掉否则换账号能看见上一个账号的菜单。第三个切入点把自动导入的粒度调小。模板默认整目录自动导入业务大了容易误伤同名组件我在 vite.config 里给 Components 加 include 匹配只让通用组件走自动导入页面级组件手动 import。这样既保留开发效率又让组件引用关系变得可读。这套模板我留存着每次起新项目都会走一遍同样的约定。从那以后我接手任何工程第一件事不再是问框架版本而是先看目录结构怎么摆、数据流从哪进、出错往哪报哪怕要推倒重写也先把分域规则立住。这份模板我已经整理成可复现的压缩包解压后从第 4 章开始跑十分钟内能看到登录页。希望帮到你。本文还有配套的精品资源点击获取