# **[vue3.3-Mobile-template](https://github.com/HSg666/vue3.3-Mobile-template)** 基于Vue3.3 + TS + Vant4 + Vite5 + Pinia + ViewPort适配 + Sass + Axios封装 + vconsole调试工具,搭建的H5移动端开发模板,开箱即用的。 ### 环境要求: Node:16.20.1 pnpm:8.14.0 必须装上安装pnpm,没装的看这篇文章 https://blog.csdn.net/Steven_Son/article/details/135151622 代码管理工具推荐用:sourceTree ### 项目预览 ![](https://img-blog.csdnimg.cn/direct/5d6fdf21951c40efa2ddd1b5abe89b1d.png) ![](https://img-blog.csdnimg.cn/direct/58786e2f1f1c4ff3b57adfa64ece87d1.png) ![](https://img-blog.csdnimg.cn/direct/2462980ba4fa44c1ac591aede2895971.png) ### 项目结构 ```js learn-vite -- UI 主目录 ├── dist 打包后自动生成的文件夹 ├── public -- 静态资源 ├ ├── favicon.ico -- 图标 ├── src -- 源码目录 ├ ├── assets -- 全局静态资源 ├ ├ ├── iconfont -- 字体和字体图标 ├ ├ ├── images -- 图片存放路径 ├ ├ ├── json -- 静态json ├ ├ └── scss -- index.scss 全局样式,reset.scss初始化样式 ├ ├── components -- 封装的组件 ├ ├── global 配置全局URL环境变量 ├ ├── hooks -- vue3 Hooks ├ ├── layout -- 全局Tabbar配置、keep-alive可配置需长缓存的路由 ├ ├── polyfill 解决浏览器兼容性的文件 ├ ├── router -- VUE 路由 ├ ├ ├── index -- 路由入口 ├ ├── service ├ ├ ├── apiList.ts -- 接口列表 ├ ├ ├── error.ts -- 封装的接口错误提示 ├ ├ ├── handleError.ts -- 处理接口请求错误 ├ ├ ├── requestList.ts -- 请求函数列表 ├ ├ └── webRequest.ts -- 封装Axios请求函数 ├ ├── store -- Pinia ├ ├ ├── index -- 统一导出整个pinia和store ├ ├ └── modules.ts store模块化 ├ ├── typings -- 存储TS类型别名 ├ ├── utils -- 工具包 ├ ├── views -- 业务上的 vue 页面 ├ ├── App.vue -- 根组件 ├ └── main.ts -- 入口 ts ├── components.d.ts -- 自动注册组件文件 ├── .eslintrc.js -- ESLint 配置 ├── .gitignore -- git 忽略 ├── tsconfig.json -- vscode 路径引入配置 ├── index.html -- 首页 ├── package.json -- 依赖管理 ├── vite.config.ts -- vite5的相关配置 └── windi.config.ts -- WindiCSS的配置文件 ``` ## 命令 ```js git clone https://github.com/HSg666/vue3.3-Mobile-template // 或 git clone git@github.com:HSg666/vue3.3-Mobile-template cd learn-vite // 切换 pnpm i // 装依赖 pnpm start // 启动 pnpm run build // 打包 rm -rf node_modules // 强行删除依赖包 ``` 准备打包上线时请看 配置全局URL环境变量,检查完配置后再执行pnpm run build 打包 部署上线后如果出现页面刷新报Nginx404,请看这篇文章并对照检查你的router/index.ts中的mode模式,更改配置后再试试就OK了。 https://blog.csdn.net/Steven_Son/article/details/135414494 ## 目录 - [1、封装Router](#router) - [2、Vant4自动按需导入](#vant4) - [3、封装Axios请求函数、接口列表、请求错误处理](#axios) - [4、配置全局URL环境变量](#globalUrl) - [5、配置alias路径别名](#alias) - [6、封装Pinia、模块化、长缓存](#pinia) - [7、postcss-px-to-viewport移动端适配](#postcss-px-to-viewport) - [8、自动导入组件](#unplugin-vue-components) - [9、封装TabBar布局容器](#tabbar) - [10、WindiCSS样式库](#windicss) - [11、初始化全局CSS和防止页面文本被用户选中](#resetcss) - [12、字体与字体图标](#iconfont) - [13、性能优化](#xnyh) - [14、代码规范](#pretter) - [15、配置兼容性](#jrx) - [16、已配置第三方工具库](#threeTool) - [17、拓展](#tuozhan) ## 1、封装Router 路径:src/router/index.ts ```js // 需要Tabbar的组件在layoutRoutes中添加路由,Tabbar就是页面底部的 精选、分类、购物车、我的 export const layoutRoutes: Array = [ { path: '/', name: 'home', meta: { title: 'home', keepAlive: true, }, component: () => import('@/views/home/index.vue'), }, { path: '/category', name: 'category', meta: { title: 'category', // keepAlive: true, }, component: () => import('@/views/category/index.vue'), }, ] ``` ```js // 不需要Tabbar的组件在routes中添加路由,即页面底部空空如也的组件。 export const routes: Array = [ // 这个是布局,不用改 { path: '/', component: () => import('@/layout/index.vue'), redirect: '/index', // 需要layout的页面 children: layoutRoutes, }, // 注册的路由类似登录页 { path: '/login', name: 'login', component: () => import('@/views/login/index.vue'), }, ] ``` ## 2、移动端UI库采用Vant4 项目已经配置好按需导入和组件自动注册了,页面直接使用即可,无需手动注册。 除了Toast轻提示使用时需要手动引入,其他都无需手动引入。示例如下: 在vant4 Toast的函数名都改了,大家看官方文档就知道。 ```js // 示例: ``` 自动注册的组件都保存在项目根目录的 components.d.ts中,可自行查看。 配置详情:https://blog.csdn.net/Steven_Son/article/details/135544198?spm=1001.2014.3001.5501 UI库官网地址:https://vant-ui.github.io/vant/#/zh-CN/button ## 3、封装Axios ### 1、新增axios并封装,还新增了自定义请求错误处理函数,请求类 ### 2、封装api列表 apiList 封装的axios配合api接口使用模板 (1)、先把接口添加进接口列表 ```js export const APIs = { GET_SHOPLIST: '/h5/getShopList', // 获取商品列表 } ``` (2)、页面使用 ```js // account.vue import AxiosRequestError from '@/service/error' // 引入自定义错误处理函数 import $api from '@/service/webRequest' // 封装好的axios请求函数 import { APIs } from '@/service/apiList' // 接口列表 // 二选一即可 // async await 写法 const getShop = async () => { try { const res = await $api.getShopList() console.dir(res, 'res') } catch (error: AxiosRequestError) { console.dir(error, 'error') } } // 原生Primise .then .catch const params = { user:'', password:'' } // 传参将需要传的值放入即可,跟vue2一样 $api.get(APIs.GET_SHOPLIST, params) .then(() => {}) .catch((err: AxiosRequestError) => { console.dir(err, 'err') }) ``` (3)、用console.dir可以捕获到详细的错误信息,还能看到我们封装的错误处理函数 data: undefined, // 接口返回值为undefined isServerError: false, // 是否为服务器出错 isUnAuthorized: false, // 是否已通过鉴权,也就是常见的登录状态 (4)、如果要添加或使用自定义请求函数,请在src/service/requestList.ts中添加,类似于已经存在的上传图片接口 ### 3、自定义封装请求函数 1、先到service/apiList.ts中添加接口 ```js export const APIs = { GET_PRDDETAIL: '/m/CcbLifeGoods/getyById', // 获取商品详情 } ``` 2、在service/requestList.ts中添加,用模板字符串自行拼接 注:由于get如果传多个参数需要自己拼接的,不像post直接传整个json对象给后端,所以你打算用get传多个参数,请看一下案例(采用模板字符串和&拼接方式),见getOnlyId和getShopListFN;想用post单独传参也是ok的,见postOnlyId。 ```js import { APIs } from './apiList' class API { // 获取单个id数据 async getOnlyId(key, id) { return this.get(`${key}?id=${id}`) } // 获取商品列表 以及搜索 // key:接口名称 data: 所有参数组成的对象 async getShopListFN(key, data) { // 写法1 传统 // return this.get(`${key}?id=${data.id}&name=${data.name}&price=${data.price}&sale=${data.sale}&xp=${data.xp}`) // 写法2 解构 const { id,name,price,sale,xp } = data // return this.get(`${key}?id=${id}&name=${name}&price=${price}&sale=${sale}&xp=${xp}`) } // 传递单个id数据 async postOnlyId(key, data) { return this.post(`${key}?goodsId=${data.goodsId}`) } } ``` 3、页面使用 注:自定义的方法都是绑定在$api this上的,所以直接在它身上取就可以 ```js import $api from '@/service/webRequest' // 封装好的axios请求函数 import { APIs } from '@/service/apiList' // 接口列表 const getData = async () => { // 单一传 let id = route.query.id const { data: res } = await $api.getOnlyId(APIs.GET_PRDDETAIL, id) // 多个参数组装为一个对象传入 let params = { id:1, name:'HSg', price:99, sale:3, xp:11 } const { data: res } = await $api.getShopListFN(APIs.GET_PRDDETAIL, params) } ``` ## 4、配置全局URL环境变量 开发和正式环境地址在 global/env.ts 中配置 ```js // 静态图片前缀 export const fileServerAddress = 'http://192.168.1.179:8081/' // 客户端地址(某后端接口地址) // const fileServerAddress = 'http://192.168.1.179:8081/' ; // 客户端地址(线上) // 正式环境 export const PROD_ENV = { SERVER_URL: 'http://192.168.1.193:8090/', // 服务器地址 IS_DEV: 'false', // 是否为开发环境 } // 开发环境 export const DEV_ENV = { SERVER_URL: 'http://192.168.1.193:8099/', IS_DEV: 'true', } /* isDEV:true为生产环境,false为开发环境 假设开发环境的域名是 http://127.0.0.1:8099/api 或 https://xxx-test.com 提示: 本地如果要将请求地址切换为生产服务器,则将isDEV设置为false,注释掉判断开发环境的代码。代码如下 const isDEV = false // if (typeof window !== 'undefined') { // isDEV = process.env.NODE_ENV === 'development' || ['http://192.168.1.193:8099'].includes(window.location.host) // } 准备打包上线,将代码改回来。(开发环境也是这个代码)代码如下 let isDEV = true // 默认为开发环境 if (typeof window !== 'undefined') { isDEV = process.env.NODE_ENV === 'development' || ['http://192.168.1.193:8099'].includes(window.location.host) } */ let isDEV = true // 默认为开发环境,但会根据当前环境动态更换开发或生产 if (typeof window !== 'undefined') { isDEV = process.env.NODE_ENV === 'development' || [fileServerAddress].includes(window.location.host) } ``` 静态图片前缀页面使用案例 ```js home.vue // 图片前缀 import { fileServerAddress } from '@/global/env' // 1、标签中 // 2、函数中 直接使用fileServerAddress ``` ## 5、配置路径别名 alias 示例:@/store 只要在src下的都能这样简写 总共分为4步: 1、vite.config.ts ```js import path from 'path' export default defineConfig({ //新增 resolve: { alias: { '@/assets': path.resolve(__dirname, './src/assets'), }, }, }) ``` 2、tsconfig.json ```js "paths": { "@/assets/*": ["src/assets/*"], } ``` 3、配置好页面使用 例如main.ts引入 ```js // 引入全局样式 import '@/assets/scss/index.scss' ``` 4、更改完vite.config.ts和tsconfig.json记得重启项目。 ## 6、封装Pinia、模块化、长缓存 使用方式: 1、在store/modules下创建user.ts ````js import { defineStore, acceptHMRUpdate } from 'pinia' // 1、声明导出store名称 export const userStore = defineStore({ id: 'user', // 2、声明store名称 state: () => ({ name: '很老很老的值', }), getters: { myName: state => { return `getters ${state.name}` }, }, actions: { changeName(name: string) { this.name = name }, }, }) // 这行代码是用于支持热模块替换(HMR)的。在Pinia中,它允许接受热更新并应用到使用了userStore的地方。 // 3、为了让当前store接收热更新为它配置一下 if (import.meta.hot) { import.meta.hot.accept(acceptHMRUpdate(userStore, import.meta.hot)) } ```` 2、导出user.ts中整个userStore给其他组件使用 store/modules/index.ts ```js export * from './user' ``` 3、store在组件中的使用方式 ```js // 1、引入 import { userStore } from '@/store' // 由于项目已配置路径别名,所以就用@/,它代表的是src // 2、实例化 const useUserStore = userStore() // 3、如何使用userStore中的变量和函数 看下面template中的p标签就知道,解不解构2选1 // 3.1.1 变量可用解构 例如取出name后直接使用即可 const { name } = useUserStore // 3.1.2 变量不解构 需要加上useUserStore.name console.log(useUserStore.name) // 3.2 使用userStore中的函数 const handleLogin = () => { useUserStore.changeName('张三') } // 页面 ``` 完整代码 ```js import { userStore } from '@/store' // 1、引入 const useUserStore = userStore() // 2、实例化 const { name } = useUserStore // 3、解构变量 // 4、使用 const handleLogin = () => { useUserStore.changeName('张三') } ``` 4、引入的store存储的数据默认是没有响应式的,可以用 storeToRefs 将其变为响应式。 ```js // 引入 import { storeToRefs } from "pinia"; // 将我们实例化的useAppstore放进去然后解构,解构出的state数据即为响应式 const { name } = storeToRefs(useAppstore); ``` 需要storeToRefs的完整代码 ```js import { userStore } from '@/store' // 引入userStore import { storeToRefs } from "pinia"; // 取出响应式方法 const useUserStore = userStore() // 实例化 const { name } = storeToRefs(useUserStore); // 将实例化对象的数据更改为响应式并解构出来 // 使用userStore中的函数 const handleLogin = () => { useUserStore.changeName('张三') } // 页面 ``` 如何证明数据是否为响应式,请看这篇文章 https://blog.csdn.net/Steven_Son/article/details/128440811 封装+模块化:https://blog.csdn.net/Steven_Son/article/details/135553816?spm=1001.2014.3001.5501 长缓存:https://blog.csdn.net/Steven_Son/article/details/135551314?spm=1001.2014.3001.5501 Pinia官网文章:https://pinia.web3doc.top/introduction.html ## 7、自适应采用的是postcss-px-to-viewport 详细配置说明看这篇文章:https://blog.csdn.net/Steven_Son/article/details/135554296?spm=1001.2014.3001.5501 ## 8、自动导入组件 使用components下的组件时自动注册的插件 unplugin-vue-components 作用:哪个页面要用到components下的组件无需import手动导入,直接使用即可。 所用的组件都自动保存在项目根目录的 components.d.ts 中。 ## 9、封装TabBar布局容器 1、路径:src/layout/index.vue 2、作用:页面整体的布局结构,如需增加/减少tabbar数量,增加时记得给新tabbar配置正确的路由,才能正常跳转。 ## 10、Windicss库的用法 库已经配置好了,你直接使用即可。 ```html

橙色

``` 官方文档:https://windicss.org/ ## 11、初始化全局CSS和防止页面文本被用户选中 src/assets/scss/reset.scss 和 src/assets/scss/index.scss ## 12、字体和字体图标 项目使用的字体和字体图标是阿里巴巴免费可商用的iconfont,无需担心是否侵权的问题。 路径:src/assets/iconfont 1、iconfont 阿里巴巴字体图标 配置文章链接: https://blog.csdn.net/Steven_Son/article/details/128149868?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22128149868%22%2C%22source%22%3A%22Steven_Son%22%7D 2、引入免费的阿里巴巴思源黑体字体 配置文章链接:https://www.iconfont.cn/fonts/detail?spm=a313x.fonts_index.i1.d9df05512.7ccd3a81uTg3IB&cnid=nsKKStjV4gdI ## 13、性能优化 ### 1、需要keep-alive长缓存的组件在此配置 1、路由设置keepAlive属性 src/router/index.ts ```js { path: '/category', name: 'category', meta: { title: 'category', keepAlive: true, // 加这一行 }, component: () => import('@/views/category/index.vue'), }, ``` 2、到布局结构页面手动添加要keep-alive的组件名称 src/layout/index.vue ```js const routerStrArr = ['home'] ``` 浏览器可以搭配插件vue.js Devtools 查看以及控制台网络降速测试 注意:最多缓存10个,缓存太多影响性能。 ### 2、为每次打包的文件后缀添加打包时的时间戳,防止打包上线页面缓存的问题 vite.config.ts timeStamp ### 3、为index.html增加防盗链,解决图片403 ### 4、PC端时自动生成iframe框架嵌套项目并网页自动居中
具体代码逻辑在 src/App.vue onMounted中 ### 5、vite.config.ts已配置诸多优化,具体请自行查看。 ## 14、代码规范 ### 1、prettier + eslint 配置了代码规范插件 ### 2、husky + lint-staged git提交规范 - feat:新功能(feature) - fix/to:修复 bug,可以是 QA 发现的 BUG,也可以是研发自己发现的 - fix:产生 diff 并自动修复此问题。适合于一次提交直接修复问题 - to:只产生 diff 不自动修复此问题。适合于多次提交。最终修复问题提交时使用 fix - docs:文档(documentation)。 - style:格式(不影响代码运行的变动)【比如说加注释就是这个?】 - refactor:重构(即不是新增功能,也不是修改 bug 的代码变动)。 - perf:优化相关,比如提升性能、体验。 - test:增加测试。 - chore:构建过程或辅助工具的变动。 - revert:回滚到上一个版本。 - merge:代码合并。 - sync:同步主线或分支的 Bug。 ## 15、配置兼容性 ### 1、browserslist 配置了浏览器兼容性 ### 2、polyfill web项目兼容低版本浏览器插件 core-js 和 @vitejs/plugin-legacy ## 16、已配置第三方工具库 ### 1、lodash 防抖和节流的使用方法,节流用到时再去查 ```js import { debounce,throttle } from 'lodash-es' // 它返回一个带防抖的新函数 const debounceLogin = debounce(toLogin, 500) function toLogin() { console.log(111) } ``` ### 2、vConsole移动端调试工具 详细文章看这篇:https://blog.csdn.net/Steven_Son/article/details/135555570?spm=1001.2014.3001.5501 ## 17、拓展: ### 1、如果不知道怎么用Nginx部署前端打包后的dist,可以看这篇文章 https://blog.csdn.net/Steven_Son/article/details/135414494?spm=1001.2014.3001.5501 ### 2、如果要做JWT免登,请根据你的需求对以下几个文件进行更改 1、src/service/webRequest.ts 设置token的地方 2、src/service/error.ts 错误报错页 3、src/login/index.vue 登录页,登录后可能就要保存token了 ### 3、本地开发的项目到手机端演示 1、修改package.json配置,更改为你电脑的IP地址,同时电脑和手机要在同个网络。 说明:连的同个WIFI、同个网线。 ```js "scripts": { "testMobile": "vite --host 192.168.1.193" } ``` 2、电脑(windows)关闭防火墙,这三个都要关闭:域网络、专用网络、公用网络。 位置:安全中心 —— 防火墙和网络保护 3、pnpm testMobild 启动项目,手机访问启动后的项目链接。 ### 4、解决main.ts 文件引入路径的问题 1、如果引入路径正确,但是提示找不到文件,则删除'XX',重新引入 2、检查vite.config.ts的路径别名配置是否正确,正确代码如下 ```js //新增 resolve: { alias: { '@': path.resolve(__dirname, './src'), //把 src 的别名设置为 @ }, extensions: ['.js', '.json', '.ts'], // 这些类型的文件后缀的不需要写 }, ``` 3、检查tsconfig.json的部分属性配置 ```js "baseUrl": ".", "paths": {"@/*": ["src/*"]}, "target": "ES2020", "module": "ES2020", "lib": [ "es2020", "es5", "es6", "DOM", "DOM.Iterable" ], "moduleResolution": "node", "include": [ "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", ], ``` 4、检查tsconfig.node.json的部分属性配置 ```js "compilerOptions": { "module": "ES2020", "moduleResolution": "node", "allowSyntheticDefaultImports": true }, "include": ["vite.config.ts", "src/**/*.ts", "global/*.ts"] ``` 5、在src下新建vite-env.d.ts ,解决ts无法识别引入.vue后缀的文件夹 ```js /// declare module '*.vue' { import type { DefineComponent } from 'vue' // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types const component: DefineComponent<{}, {}, any> export default component } ``` 每次修改完都要重启项目,或者关闭项目重启VSCode、重启项目。 ### 5、使用van-nav-bar时看这里 直接使用,但由于它会盖住外部包裹层,所以你使用van-nav-bar时需要给外层container添加padding-top:92px; 也就是vant-nav-bar的2倍高度(1倍是46),因为我们设计稿是750的。 这样在van-nav-bar下的内容就不会被它盖住了。 不需要van-nav-bar的无需加此样式。 ```html ``` ### 6、如果打开某些组件正确引入vue的api了,但还是报未找到vue文件,此时项目有缓存,关闭整个VScode重启项目即可解决。 ```js import { ref } from 'vue' // 正确引了,但提示报未找到文件 ``` ### 7、souceTree 如果提交代码失败请检查当前node 版本是否为16 ```js node -v 如果不是则切换为16 nvm use 16 切换后souceTree重新提交代码 ``` ### 8、如何深层次修改vant-ui组件样式 ```js ::v-deep(.van-search) { background-color: #f5f5f5; } ``` ### 9、如何定义SCSS全局公共样式并使用 1、在src/assets/scss/variables.scss中定义 ```js // 主题色 $theme-color: #af1d36; ``` 2、记得在vite.config.ts配置一下这个文件路径,否则页面用了找不着 vite.config.ts ```js export default defineConfig({ css:{ // css预处理器 preprocessorOptions: { scss: { // 引入 variables.scss 这样就可以在全局中使用 variables.scss中预定义的变量了 // 给导入的路径最后加上 ; additionalData: '@import "@/assets/scss/variables.scss";', }, }, } }) ``` 3、页面如何使用 views/home/index.vue ```html ``` ### 10、assets/images的图片可以用变量形式引入并使用 1、以home.vue为例 项目中已为assets配置路径别名,所以无需../ ```js import prodImg from '@/assets/images/icons/商品1.png'