赞
踩
github:https://github.com/HSg666/vue3.3-Mobile-template
gitee:https://gitee.com/HaushoLin/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
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的配置文件
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
路径:src/router/index.ts
// 需要Tabbar的组件在layoutRoutes中添加路由,Tabbar就是页面底部的 精选、分类、购物车、我的 export const layoutRoutes: Array<RouteRecordRaw> = [ { 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'), }, ]
// 不需要Tabbar的组件在routes中添加路由,即页面底部空空如也的组件。 export const routes: Array<RouteRecordRaw> = [ // 这个是布局,不用改 { path: '/', component: () => import('@/layout/index.vue'), redirect: '/index', // 需要layout的页面 children: layoutRoutes, }, // 注册的路由类似登录页 { path: '/login', name: 'login', component: () => import('@/views/login/index.vue'), }, ]
项目已经配置好按需导入和组件自动注册了,页面直接使用即可,无需手动注册。
除了Toast轻提示使用时需要手动引入,其他都无需手动引入。示例如下:
在vant4 Toast的函数名都改了,大家看官方文档就知道。
// 示例:
<script lang="ts" setup>
import { showToast } from 'vant'
const handleClick = () => {
showToast('轻提示')
}
</script>
<template>
<van-button type="primary" @click="handleClick">按钮</van-button>
</template>
自动注册的组件都保存在项目根目录的 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
封装的axios配合api接口使用模板
(1)、先把接口添加进接口列表
export const APIs = {
GET_SHOPLIST: '/h5/getShopList', // 获取商品列表
}
(2)、页面使用
// 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中添加,类似于已经存在的上传图片接口
1、先到service/apiList.ts中添加接口
export const APIs = {
GET_PRDDETAIL: '/m/CcbLifeGoods/getyById', // 获取商品详情
}
2、在service/requestList.ts中添加,用模板字符串自行拼接
注:由于get如果传多个参数需要自己拼接的,不像post直接传整个json对象给后端,所以你打算用get传多个参数,请看一下案例(采用模板字符串和&拼接方式),见getOnlyId和getShopListFN;想用post单独传参也是ok的,见postOnlyId。
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上的,所以直接在它身上取就可以
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) }
开发和正式环境地址在 global/env.ts 中配置
// 静态图片前缀 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) }
示例:@/store 只要在src下的都能这样简写
总共分为4步:
1、vite.config.ts
import path from 'path'
export default defineConfig({
//新增
resolve: {
alias: {
'@/assets': path.resolve(__dirname, './src/assets'),
},
},
})
2、tsconfig.json
"paths": {
"@/assets/*": ["src/assets/*"],
}
3、配置好页面使用
例如main.ts引入
// 引入全局样式
import '@/assets/scss/index.scss'
4、更改完vite.config.ts和tsconfig.json记得重启项目。
使用方式:
1、在store/modules下创建user.ts
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
export * from './user'
3、store在组件中的使用方式
// 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('张三') } // 页面 <template> <p>{{ name }}</p> <p>{{ useUserStore.name }}</p> </template>
完整代码
import { userStore } from '@/store' // 1、引入
const useUserStore = userStore() // 2、实例化
const { name } = useUserStore // 3、解构变量
// 4、使用
const handleLogin = () => {
useUserStore.changeName('张三')
}
<template>
<p>{{ name }}</p>
</template>
4、引入的store存储的数据默认是没有响应式的,可以用 storeToRefs 将其变为响应式。
// 引入
import { storeToRefs } from "pinia";
// 将我们实例化的useAppstore放进去然后解构,解构出的state数据即为响应式
const { name } = storeToRefs(useAppstore);
需要storeToRefs的完整代码
import { userStore } from '@/store' // 引入userStore
import { storeToRefs } from "pinia"; // 取出响应式方法
const useUserStore = userStore() // 实例化
const { name } = storeToRefs(useUserStore); // 将实例化对象的数据更改为响应式并解构出来
// 使用userStore中的函数
const handleLogin = () => {
useUserStore.changeName('张三')
}
// 页面
<template>
<p>{{ name }}</p>
</template>
如何证明数据是否为响应式,请看这篇文章 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
详细配置说明看这篇文章:https://blog.csdn.net/Steven_Son/article/details/135554296?spm=1001.2014.3001.5501
使用components下的组件时自动注册的插件 unplugin-vue-components
作用:哪个页面要用到components下的组件无需import手动导入,直接使用即可。
所用的组件都自动保存在项目根目录的 components.d.ts 中。
1、路径:src/layout/index.vue
2、作用:页面整体的布局结构,如需增加/减少tabbar数量,增加时记得给新tabbar配置正确的路由,才能正常跳转。
库已经配置好了,你直接使用即可。
<p class="text-orange-500">橙色</p>
官方文档:https://windicss.org/
src/assets/scss/reset.scss 和 src/assets/scss/index.scss
项目使用的字体和字体图标是阿里巴巴免费可商用的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
1、路由设置keepAlive属性
src/router/index.ts
{
path: '/category',
name: 'category',
meta: {
title: 'category',
keepAlive: true, // 加这一行
},
component: () => import('@/views/category/index.vue'),
},
2、到布局结构页面手动添加要keep-alive的组件名称
src/layout/index.vue
const routerStrArr = ['home']
浏览器可以搭配插件vue.js Devtools 查看以及控制台网络降速测试
注意:最多缓存10个,缓存太多影响性能。
vite.config.ts timeStamp
具体代码逻辑在 src/App.vue onMounted中
core-js 和 @vitejs/plugin-legacy
防抖和节流的使用方法,节流用到时再去查
import { debounce,throttle } from 'lodash-es'
// 它返回一个带防抖的新函数
const debounceLogin = debounce(toLogin, 500)
function toLogin() {
console.log(111)
}
详细文章看这篇:https://blog.csdn.net/Steven_Son/article/details/135555570?spm=1001.2014.3001.5501
https://blog.csdn.net/Steven_Son/article/details/135414494?spm=1001.2014.3001.5501
1、src/service/webRequest.ts 设置token的地方
2、src/service/error.ts 错误报错页
3、src/login/index.vue 登录页,登录后可能就要保存token了
1、修改package.json配置,更改为你电脑的IP地址,同时电脑和手机要在同个网络。
说明:连的同个WIFI、同个网线。
"scripts": {
"testMobile": "vite --host 192.168.1.193"
}
2、电脑(windows)关闭防火墙,这三个都要关闭:域网络、专用网络、公用网络。
位置:安全中心 —— 防火墙和网络保护
3、pnpm testMobild 启动项目,手机访问启动后的项目链接。
1、如果引入路径正确,但是提示找不到文件,则删除’XX’,重新引入
2、检查vite.config.ts的路径别名配置是否正确,正确代码如下
//新增
resolve: {
alias: {
'@': path.resolve(__dirname, './src'), //把 src 的别名设置为 @
},
extensions: ['.js', '.json', '.ts'], // 这些类型的文件后缀的不需要写
},
3、检查tsconfig.json的部分属性配置
"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的部分属性配置
"compilerOptions": {
"module": "ES2020",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts", "src/**/*.ts", "global/*.ts"]
5、在src下新建vite-env.d.ts ,解决ts无法识别引入.vue后缀的文件夹
/// <reference types="vite/client" />
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、重启项目。
直接使用,但由于它会盖住外部包裹层,所以你使用van-nav-bar时需要给外层container添加padding-top:92px; 也就是vant-nav-bar的2倍高度(1倍是46),因为我们设计稿是750的。
这样在van-nav-bar下的内容就不会被它盖住了。
不需要van-nav-bar的无需加此样式。
<template>
<container>
<van-nav-bar title="首页" />
</container>
</template>
<style scoped lang="scss">
.container{
padding-top: 92px;
}
</style>
import { ref } from 'vue' // 正确引了,但提示报未找到文件
node -v 如果不是则切换为16
nvm use 16 切换后souceTree重新提交代码
::v-deep(.van-search) {
background-color: #f5f5f5;
}
1、在src/assets/scss/variables.scss中定义
// 主题色
$theme-color: #af1d36;
2、记得在vite.config.ts配置一下这个文件路径,否则页面用了找不着
vite.config.ts
export default defineConfig({
css:{
// css预处理器
preprocessorOptions: {
scss: {
// 引入 variables.scss 这样就可以在全局中使用 variables.scss中预定义的变量了
// 给导入的路径最后加上 ;
additionalData: '@import "@/assets/scss/variables.scss";',
},
},
}
})
3、页面如何使用
views/home/index.vue
<template>
<p>测试</p>
</template>
<style lang="scss" scoped>
p{
color: $theme-color
}
</style>
1、以home.vue为例
项目中已为assets配置路径别名,所以无需…/
import prodImg from '@/assets/images/icons/商品1.png'
<template>
<img :src="prodImg" />
</teplate>
1、如果页面顶部有nav-bar导航栏,则用这种布局
.container {
width: 100%;
min-height: 100vh;
padding-top: 92px;
overflow-x: hidden;
overflow-y: scroll;
}
2、不需要导航栏的页面用这种,少了一个padding-top,因为有导航栏时页面数据会被它盖住,所以需要下移。
.container {
width: 100%;
min-height: 100vh;
overflow-x: hidden;
overflow-y: scroll;
}
<script setup lang="ts"> import { ref } from 'vue' // 1、引入静态图 import HOME_ONE from '@/assets/images/icons/1首页.png' import HOME_TWO from '@/assets/images/icons/2首页.png' // 2、做点数据结构,用于页面v-for遍历用 const tabBar = [ { title: '首页', to: { name: 'home', }, icon: HOME_ONE, // 默认 icon_acitve: HOME_TWO, // 选中 }, { title: '商品', to: { name: 'productList', }, icon: PRODUCT_ONE, icon_acitve: PRODUCT_TWO, }, ... ] const active = ref(0) // 默认选中第一个 </script> <template> <van-tabbar v-model="active" fixed route active-color="#af1d36" inactive-color="#707070" safe-area-inset-bottom> <van-tabbar-item v-for="(item, index) in tabBar" :key="index" :to="item.to"> <span>{{ item.title }}</span> <template #icon="props"> <img :src="props.active ? item.icon_acitve : item.icon" /> </template> </van-tabbar-item> </van-tabbar> </template>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。