描述
基于Vue3 + Vite + Vant + Sass + TS + rem适配方案 + Axios封装,构建手机端模板脚手架
项目地址:github
项目预览:查看 demo 建议手机端查看,如pc端浏览器打开需切换到手机调试模式
Node 版本要求
Vite
需要 Node.js 12.0.0 或更高版本 (推荐 14.0.0+)。你可以使用 nvm 或 nvm-windows 在同一台电脑中管理多个 Node 版本。
本示例 Node.js 14.18.1
启动项目
git clone https://github.com/talktao/Vue3-Vite-Vant-TS-H5.git
cd Vue3-Vite-Vant-TS-H5
yarn
yarn dev
复制代码
复制代码
rem适配方案
Vant 中的样式默认使用px
作为单位,如果需要使用rem
单位,推荐使用以下两个工具:
- postcss-pxtorem 是一款
postcss
插件,用于将单位转化为rem
- lib-flexible 用于设置
rem
基准值
更多详细信息: vant
VantUI组件按需加载
项目采 用Vant 自动按需引入组件 (推荐)下 面安装插件介绍:
安装插件
yarn add vite-plugin-style-import -D
复制代码
复制代码
在 vite.config.ts
设置
import vue from '@vitejs/plugin-vue';
import styleImport, { VantResolve } from 'vite-plugin-style-import';
export default {
plugins: [
vue(),
styleImport({
resolves: [VantResolve()],
}),
],
};
复制代码
但是每次页面使用的时候还是要引入,很麻烦,项目在 src/plugins/vant.ts
下统一管理组件,无需在main.ts文件中多次use()
Sass 全局样式
首先 你可能会遇到 node-sass
安装不成功,别放弃多试几次!!!
每个页面自己对应的样式都写在自己的 .vue 文件之中 scoped
它顾名思义给 css 加了一个域的概念。
<style lang="scss">
/* global styles */
</style>
<style lang="scss" scoped>
/* local styles */
</style>
复制代码
复制代码
目录结构
vue-h5-template 所有全局样式都在 @/src/assets/css
目录下设置
├── assets
│ ├── scss
│ │ ├── index.scss # 全局通用样式
│ │ ├── mixin.scss # 全局mixin
│ │ └── reset.scss # 清除标签默认样式
│ │ └── variables.scss # 全局变量
复制代码
复制代码
父组件改变子组件样式 深度选择器
当你子组件使用了 scoped
但在父组件又想修改子组件的样式可以 通过 >>>
来实现:
<style scoped>
.a >>> .b { /* ... */ }
</style>
复制代码
复制代码
全局变量
// 引入全局样式
import '@/assets/css/index.scss'
复制代码
Vuex 状态管理
目录结构
├── store
│ ├── index.ts
复制代码
复制代码
main.ts
引入
使用
Pinia 状态管理
1.安装
node版本需>=14.0.0
yarn add pinia
# or with npm
npm install pinia
复制代码
2. 创建Pinia的Store
在src/store/index.ts
文件中,导出 piniaStore
// src/store/index.ts
import { createPinia } from 'pinia'
export const piniaStore = createPinia()
复制代码
3.在main.ts文件中引用
3. 定义State
在src/store
目录下新建有个testPinia.ts
文件
i. 传统的options API
方式
import { defineStore } from "pinia"
export const usePiniaState = defineStore({
id: 'textPinia',
state: () => {
return {
userName: ''
}
},
getters: {
},
actions: {
getUserNmae(data) {
this.userName = data
}
}
})
复制代码
ii.Vue3 setup
的编程模式
import { ref } from 'vue'
import { defineStore } from "pinia"
export const usePiniaState = defineStore('pinia', ()=>{
const userName = ref('')
// 修改userName的方法
const getUserNmae = (data) => {
userName.value = data
}
return { userName, getUserNmae}
})
复制代码
4.获取/修改 state
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { usePiniaState } from '@/store/testPinia'
// pinia
const piniaStore = usePiniaState()
// 通过storeToRefs方法将存储在pinia里的数据解构出来,保持state响应性
const { userName } = storeToRefs(piniaStore)
const { getUserNmae } = piniaStore
const handleBtn = () =>{
// pinia
getUserNmae('真乖,如果对您有帮助请在github上点个星星哦~')
}
</script>
复制代码
Vue-router
本案例采用 hash
模式,开发者根据需求修改 mode
base
自动化导入路由
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
// 通过Vite的import.meta.glob()方法实现自动化导入路由
const mainRouterModules = import.meta.glob('../layout/*.vue')
const viewRouterModules = import.meta.glob('../views/**/*.vue')
// 子路由
const childRoutes = Object.keys(viewRouterModules).map((path)=>{
const childName = path.match(/\.\.\/views\/(.*)\.vue$/)[1].split('/')[1];
return {
path: `/${childName.toLowerCase()}`,
name: childName,
component: viewRouterModules[path]
}
})
console.log(childRoutes,'childRouter');
// 根路由
const rootRoutes = Object.keys(mainRouterModules).map((path) => {
const name = path.match(/\.\.\/layout\/(.*)\.vue$/)[1].toLowerCase();
const routePath = `/${name}`;
if (routePath === '/index') {
return {
path: '/',
name,
redirect: '/home',
component: mainRouterModules[path],
children: childRoutes
};
}
})
const routes: Array<RouteRecordRaw> = rootRoutes
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router
复制代码
普通设置
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Index',
component: () => import ('@/layout/index.vue'),
redirect: '/home',
meta: {
title: '首页',
keepAlive:false
},
children: [
{
path: '/home',
name: 'Home',
component: () => import('@/views/home/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/about/About.vue')
},
]
},
]
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router
复制代码
更多:Vue Router
Axios 封装及接口管理
utils/request.js
封装 axios ,开发者需要根据后台接口做修改。
接口管理
在src/api
文件夹下统一管理接口
通过引入axios库的ts版本即可配置
import axiosInstance, { AxiosResponseProps } from '@/uitls/request'
export const getList = (params: any) => {
return axiosInstance.get("/common/code/logisticsInfo/getOrderByPhone", { params: params || {} });
}
复制代码
控制全局请求loading
// 定义存放请求接口数组
let requestList = [];
const setLoadingToFalse = response => {
requestList
.filter(item => item.url == response.config.url && item.method == response.config.method)
.forEach(item => (item.isLoading = false));
//所有请求都加载完才让加载提示消失
if (requestList.every(item => !item.isLoading)) vuexStore.commit("changeIsLoading", false);
};
复制代码
上述setLoadingToFalse
函数主要接收响应报文
参数,从而判断是否所有请求都加载完成,加载完成则取消全局loading
instance.interceptors.request.use(
(config: any) => {
// 不用判断请求loading的路由
let ignoreLoadingUrls = ["/login"];
if (!ignoreLoadingUrls.includes(config.url)) {
if (requestList.length < 10) {
requestList.unshift({ ...config, isLoading: true });
} else {
requestList = [...requestList.slice(0, 9), { ...config, isLoading: true }];
}
vuexStore.commit("changeIsLoading", true);
}
return config;
},
error => Promise.reject(error + '请求错误')
);
复制代码
上述请求拦截
主要用于给当前请求配置isLoading:true
,以便于给setLoadingToFalse
函数最终来判断请求是否结束
instance.interceptors.response.use(
response => {
setLoadingToFalse(response);
return response.data;
},
error => {
if (error.response.status == 301) {}
setLoadingToFalse(error);
return Promise.reject(error.response?.data);
}
);
复制代码
上述响应拦截
主要用于将当前的响应报文 参数
传递给setLoadingToFalse
,并返回响应结果
效果
如何调用
// 请求接口
import { getUserInfo } from '@/api/home'
const params = {user: 'talktao'}
getUserInfo(params)
.then(() => {})
.catch(() => {})
复制代码
复制代码
vite.config.ts 基础配置
检查文件中的env路径
配置 alias 别名
resolve: {
alias:{
// 配置src目录
"@": path.resolve(__dirname,"src"),
// 导入其他目录
"components": path.resolve(__dirname, "components")
}
},
复制代码
配置 proxy 跨域
如果你的项目需要跨域设置,你需要打开 vite.config.ts
proxy
注释 并且配置相应参数
module.exports = {
// 跨域代理
server: {
host: '0.0.0.0',
proxy: {
'/api': {
target: 'https://api.inews.qq.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') // 将匹配到的api替换成''
}
}
}
}
复制代码
Eslint+Pettier 统一开发规范
VScode安装 eslint
prettier
vetur
插件 .vue
文件使用 vetur 进行格式化,其他使用prettier
批量全局注册公共组件
文件地址在 src/plugins/components
const modules = import.meta.globEager('../components/*.vue')
export default {
install(app) {
Object.keys(modules).forEach(componentPath => {
let splitPart1 = componentPath.split("/")
let componentName = splitPart1[splitPart1.length - 1].split(".vue")[0]
// 获取所有组件的实例对象,它是个数组
let modulesData = Object.values(modules).map((v) => v.default)
// 过滤出当前组件的实例对象与注册组件匹配一致
let curComponent = modulesData.filter(
item=>item.__file.split("/")[item.__file.split("/").length-1].split(".vue")[0] === componentName
)[0]
app.component(componentName, curComponent);
})
}
}
复制代码
上面的批量全局注册公共组件在本地启动中正常,但是上生产打包后,会有问题,具体是__file
该组件路径找不到,可以修改成如下代码:
const modules = import.meta.globEager('../components/*.vue')
export default {
install(app) {
Object.keys(modules).forEach(componentPath => {
// 获取遍历的当前组件实例对象
let curComponent = modules[componentPath]?.default
app.component(curComponent.name, curComponent);
})
}
}
复制代码
注意:
由于sfc语法糖没有携带组件的name属性,上面的curComponent.name会报curComponent下没有name属性
,此时需要在注册的公共组件中加上如下代码,比如在src/components/CustomHeader.vue
中加上如下代码,这样组件的实例对象中就会有name
属性
总结
关于我
如果对你有帮助送我一颗小星星❤
转载请联系作者!