Mobile + Vue = MobVue
简介
MobVue 是一个精心制作的移动端 H5 模板,基于 Vue3、Vite、TypeScript、Vant 等主流技术
使用
点击展开
推荐环境
- 新版
Visual Studio Code或者 AI IDECursor与Trae - 安装
.vscode/extensions.json文件中推荐的插件 node20.19+ 或 22.12+pnpm10+bun最新的
本地开发
使用 Node + pnpm
# 安装依赖
pnpm i
# 启动服务
pnpm dev
使用 Bun
bun i
bun run dev
打包构建
使用 Node + pnpm
# 打包构建预发布环境
pnpm build:staging
# 打包构建生产环境
pnpm build
使用 Bun
bun run build:staging
bun run build
本地预览
使用 Node + pnpm
# 先执行打包构建命令生成 dist 目录后再执行以下预览命令
pnpm preview
使用 Bun
bun run preview
代码检查
使用 Node + pnpm
# 代码校验与格式化
pnpm lint
# 单元测试
pnpm test
使用 Bun
bun run lint
bun run test
代码提交规范
feat 新功能
fix 修复错误
perf 性能优化
refactor 重构代码
docs 文档和注释
types 类型相关
test 单测相关
ci 持续集成、工作流
revert 撤销更改
chore 琐事(更新依赖、修改配置等)
特性
📍 纯一级路由设计 - 清晰且缓存友好
📱 移动端适配 px2vw - 并且宽屏友好
🌐 浏览器适配 @vitejs/plugin-legacy + autoprefixer + browserslist - 兼容多种浏览器和低版本浏览器
🧩 布局系统 - 配置化的
🌗 主题模式 Dark Mode
📲 PWA - 渐进式 Web 应用
🔎 Husky + lint-staged + ESLint - 规范代码
💪🏻 依然 TypeScript - 严格模式且无 any
👀 更多功能 - 路由缓存、带防御的水印、灰色和色弱模式、SVG Loader、VConsole、白屏加载动画、单元测试、国际化
技术栈
Vue3:采用 Vue3 + script setup 最新的 Vue3 组合式 API
Vant:轻量、可定制的移动端 Vue 组件库
Pinia: 传说中的 Vuex5
Vite:真的很快
Vue Router:路由路由
TypeScript:JavaScript 语言的超集
pnpm:更快速的,节省磁盘空间的包管理工具
ESLint:代码校验与格式化
Axios:发送网络请求(已封装好)
UnoCSS:具有高性能且极具灵活性的即时原子化 CSS 引擎
Bun:一款快速的 JavaScript 运行时
目录结构
# mobvue
├─ .husky # commit 时进行代码校验和格式化
├─ .vscode # vscode 配置和插件
├─ public
│ ├─ favicon.png # 网站头像
│ └─ app-loading.css # 首屏 loading 动画
├─ src
│ ├─ common # 通用目录
│ │ ├─ apis # 通用目录 - 接口
│ │ ├─ assets # 通用目录 - 静态资源
│ │ ├─ components # 通用目录 - 组件
│ │ ├─ composables # 通用目录 - 组合式函数
│ │ ├─ constants # 通用目录 - 常量
│ │ └─ utils # 通用目录 - 工具函数
│ ├─ http # 网络请求
│ ├─ layout # 布局
│ ├─ pages # 页面
│ │ └─ login # 登录模块
│ │ ├─ apis # 登录模块 - 私有接口
│ │ ├─ components # 登录模块 - 私有组件
│ │ ├─ composables # 登录模块 - 私有组合式函数
│ │ ├─ images # 登录模块 - 私有图片
│ │ └─ index.vue # 登录模块 - 页面
│ ├─ pinia # 状态管理
│ ├─ plugins # 插件(全局组件、自定义指令等)
│ ├─ router # 路由
│ ├─ App.vue # 入口页面
│ └─ main.ts # 入口文件
├─ tests # 单元测试
├─ types # 类型声明
├─ .editorconfig # 编辑器配置
├─ .env # 所有环境
├─ .env.development # 开发环境
├─ .env.production # 生产环境
├─ .env.staging # 预发布环境
├─ eslint.config.js # eslint 配置
├─ tsconfig.json # ts 配置
├─ uno.config.ts # unocss 配置
└─ vite.config.ts # vite 配置
基础
移动端适配
rem or vw? Of course, vw!
基于 postcss + postcss-mobile-forever,实现 px 到 vw 的自动转化
具体内置的配置如下:
{
// UI 设计稿宽度
viewportWidth: (file: string) => file.includes("vant") ? 375 : 375,
// 限制视图的最大宽度
maxDisplayWidth: 750,
// 页面最外层选择器
appSelector: "#app",
// 是否对「页面最外层选择器」对应的元素进行描边
border: true,
// 转换单位后保留的小数点位数
unitPrecision: 3,
// 转换后的单位
mobileUnit: "vw",
// 需要转换的属性
propList: ["*"],
// 忽略的选择器
selectorBlackList: [".ignore", "keep-px"],
// 忽略的属性
propertyBlackList: {
".van-icon": "font"
},
// 忽略的属性值
valueBlackList: ["1px"],
// 忽略的目录或文件
exclude: [],
// 包含块是根元素的选择器列表
rootContainingBlockSelectorList: ["van-tabbar", "van-popup"]
}
浏览器适配
基于 @vitejs/plugin-legacy + autoprefixer + browserslist 兼容多种浏览器和低版本浏览器
你可以无感的使用这套内置的配置,唯一可能需要你改动的地方,可能是 package.json 文件里面的 browserslist 配置,它默认是这样的:
"browserslist": [
"defaults"
]
你可以根据自己想兼容的浏览器来修改这个地方
路由设计
纯一级路由
📍 无需设置嵌套路由来实现 Layout 布局,路由配置更加清晰简洁,并且对路由缓存更加友好,告别某些场景下缓存失效的问题!
如果不需要显示 Layout 布局,以登录页面为例,你可以这样配置:
{
path: "/login",
component: () => import("@/pages/login/index.vue"),
name: "Login",
meta: {
title: "登录"
}
}
如果需要 Layout 布局,以我的页面为例,你可以这样配置:
{
path: "/me",
component: () => import("@/pages/me/index.vue"),
name: "Me",
meta: {
title: "我的",
layout: {
navBar: {
showNavBar: true,
showLeftArrow: false
},
tabbar: {
showTabbar: true,
icon: "user-o"
},
footer: true
}
}
}
meta 配置项
{
/**
* @description 页面标题
*/
title?: string
/**
* @description 设置能进入该路由的角色,支持多个角色叠加,默认不限制角色
* @default undefined
*/
roles?: string[]
/**
* @description 是否缓存该路由页面,注意路由和页面都需要设置一致的 Name
* @default false
*/
keepAlive?: boolean
/**
* @description 布局配置
*/
layout?: {
/**
* @description NavBar 配置
*/
navBar?: {
/**
* @description 是否显示 NavBar
* @default false
*/
showNavBar?: boolean
/**
* @description 是否显示左侧箭头
* @default false
*/
showLeftArrow?: boolean
}
tabbar?: {
/**
* @description 是否显示 Tabbar
* @default false
*/
showTabbar?: boolean
/**
* @description 图标
*/
icon?: string
}
/**
* @description 是否显示 Footer
* @default false
*/
footer?: boolean
}
}
权限控制
页面级
控制代码都在路由守卫 @/router/guard.ts 中,这里可根据具体的业务做相应的修改:
NProgress.configure({ showSpinner: false })
const { setTitle } = useTitle()
const LOGIN_PATH = "/login"
export function registerNavigationGuard(router: Router) {
// 全局前置守卫
router.beforeEach((to, _from) => {
NProgress.start()
const userStore = useUserStore()
// 如果没有登录
if (!getToken()) {
// 如果在免登录的白名单中,则直接进入
if (isWhiteList(to)) return true
// 其他没有访问权限的页面将被重定向到登录页面
return LOGIN_PATH
}
// 如果已经登录,并准备进入 Login 页面,则重定向到主页
if (to.path === LOGIN_PATH) return "/"
// 判断有无该页面权限
if (to.meta.roles ? userStore.roles.some(role => to.meta.roles!.includes(role)) : true) return true
// 无权限则进入 403 页面
return "/403"
})
// 全局后置钩子
router.afterEach((to) => {
const keepAliveStore = useKeepAliveStore()
// 清除所有路由缓存
if (to.path === LOGIN_PATH) keepAliveStore.delAllCachedRoutes()
// 添加路由缓存
keepAliveStore.addCachedRoute(to)
// 设置标题
setTitle(to.meta.title)
NProgress.done()
})
}
按钮级
基于权限指令和权限函数两种方式实现按钮级权限控制:
<van-cell-group title="权限指令" inset>
<van-cell v-permission="['admin']" title="Admin 可见" value="Role admin" />
<van-cell v-permission="['admin', 'editor']" title="Admin 或 Editor 可见" value="Role admin or editor" />
</van-cell-group>
<van-cell-group title="权限函数" inset>
<van-cell v-if="checkPermission(['admin'])" title="Admin 可见" value="Role admin" />
<van-cell v-if="checkPermission(['admin', 'editor'])" title="Admin 或 Editor 可见" value="Role admin or editor" />
</van-cell-group>
其中的关键代码只有两行:
// 权限指令
v-permission="['admin']"
// 权限函数
v-if="checkPermission(['admin'])"
发送 HTTP 请求
大致的流程如下:
graph LR
页面/交互 --> api --> axios --> 服务器
通用 API 模块
src/common/apis 目录存放通用的接口,而非某个页面固定使用的接口
私有 API 模块
某个页面固定使用的接口,应该在当前页面目录下建立一个 apis 文件夹,用来存放私有接口
参考登录页 src/pages/login/apis
封装的 Axios
src/http/axios.ts 是基于 axios 的封装,封装了全局 request 拦截器、response 拦截器、统一的错误处理、统一的超时处理、baseURL 设置等
多环境
打包构建
项目开发完成,打包构建代码时,内置两种环境:
# 打包构建预发布环境
pnpm build:staging
# 打包构建生产环境
pnpm build
环境变量
在 .env.production 等形如 .env.xxx 文件中,配置了该环境对应的一些环境变量,例如:
## 后端接口地址(如果解决跨域问题采用反向代理就只需写相对路径)
VITE_BASE_URL = /api/v1
## 开发环境域名和静态资源公共路径(一般 / 或 ./ 都可以)
VITE_PUBLIC_PATH = /
使用方式:
console.log(import.meta.env.VITE_BASE_API)
进阶
ESLint
规范代码很重要!
- 配置项在
eslint.config.js文件中 - 推荐安装 VSCode 的
ESLint插件,它可在写代码时,将不符合规范的代码标红,并且在你保存代码时自动修复一些简单的标红的代码 - 手动校验和格式化命令
pnpm lint(提交代码前可以执行该命令)
代码提交校验
项目采用 husky + lint-staged 的方式,在提交代码的时候,进行全局 ts 类型检查和 eslint 校验
husky 会自动初始化,如果发现没有正常初始化,也可以通过命令 pnpm prepare 初始化 husky
跨域
反向代理
vite.config 里有 proxy 进行反向代理,与之对应的生产环境,则可以使用 nginx 来做反向代理
proxy: {
"/api/v1": {
target: "https://xxxxxx",
// 是否为 WebSocket
ws: false,
// 是否允许跨域
changeOrigin: true
}
}
CORS
这种方案对于前端来说没有什么工作量,和正常发送请求写法上没有任何区别,工作量基本都在后端这里
实现 CORS 之后,不管是开发环境还是生产环境,都能方便的调用接口
SVG
vite-svg-loader 插件提供的的能力,可以将 SVG 文件导入为 Vue 组件!
比如 404 页面:
<script lang="ts" setup>
import Layout from "./components/Layout.vue"
import Svg404 from "./images/404.svg?component" // vite-svg-loader 插件的功能
</script>
<template>
<Layout>
<Svg404 />
</Layout>
</template>
自动按需导入
基于 unplugin-vue-components 和 unplugin-auto-import 实现的组件和 API 自动按需导入
如果我们要使用 Vant 的按钮组件,例如 van-button,不用再手动通过 import 语句导入组件和样式,而是可以直接使用:
<van-button type="primary">主要按钮</van-button>
如果我们要使用 Vue 的 ref API,也不用再手动导入,直接使用即可:
const loading = ref(false)
所有已经内置的自动按需导入:vue、vue-router、pinia、vant 相关的 API 和组件,它们四个会自动按需导入,切记不要手动 import
自动生成类型
types/auto 目录下为自动生成的类型,无需手动管理,切记不要手动修改
常见问题
报错
- AI 一下可以解决
99%的报错 - 尝试删除
node_modules和.lock文件后再次依赖 - 检查环境是否和作者推荐的一致
- 重启一下?
依赖超时
- 国内用户可以设置最新的淘宝源加快依赖速度
https://registry.npmmirror.com
热更新失效
- 检查配置路由时填写的路径是否正确(特别是字母大小写问题)
本地开发时切换路由导致页面重载
只会在开发环境出现,不会影响生成环境
- 由
unplugin-vue-components导致的问题,可等待插件修复 - 也可以取消自动按需导入样式,改为自己手动全量导入
Vant样式来解决该问题
预览图
相关链接
在线预览:github-pages
GitHub 仓库:github
Gitee 仓库:gitee
交流群:查看进群方式
发行版 & 更新日志:releases