安装
# Vue 3 项目,安装最新版 Vant
npm i vant
# Vue 2 项目,安装 Vant 2
npm i vant@latest-v2
引入组件
按需引入组件(推荐)
# 通过 npm 安装
npm i unplugin-vue-components -D
如果是基于 vite 的项目,在 vite.config.js
文件中配置插件:
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
export default {
plugins: [
vue(),
Components({
resolvers: [VantResolver()],
}),
],
};
如果是基于 vue-cli 的项目,在 vue.config.js
文件中配置插件:
const { VantResolver } = require('unplugin-vue-components/resolvers');
const ComponentsPlugin = require('unplugin-vue-components/webpack');
module.exports = {
configureWebpack: {
plugins: [
ComponentsPlugin({
resolvers: [VantResolver()],
}),
],
},
};
如果是基于 webpack 的项目,在 webpack.config.js
文件中配置插件
const { VantResolver } = require('unplugin-vue-components/resolvers');
const ComponentsPlugin = require('unplugin-vue-components/webpack');
module.exports = {
plugins: [
ComponentsPlugin({
resolvers: [VantResolver()],
}),
],
};
缺少样式处理方案:
npm i -D babel-plugin-import
处理文件.babelrc
{
...,
"plugins": [
...,
[
"import",
{
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}
]
]
}
导入所有组件(不推荐)
Vant 支持一次性导入所有组件,引入所有组件会增加代码包体积,因此不推荐这种做法。
import { createApp } from 'vue';
import Vant from 'vant';
import 'vant/lib/index.css';
const app = createApp();
app.use(Vant);
手动按需引入组件(不推荐)
在不使用任何构建插件的情况下,可以手动引入需要使用的组件和样式。
// 引入组件脚本
import Button from 'vant/es/button/index';
// 引入组件样式
// 若组件没有样式文件,则无须引入
import 'vant/es/button/style/index';
修改Vant组件样式
reset.less
/* 添加这段样式后,Primary Button 会变成红色 */
html:root {
--van-button-primary-background-color: red;
--van-button-primary-border-color: red;
...
}
/* 其他重置羊水 */
html, body { margin: 0; padding: 0; }
body {
overflow: hidden;
background: #f6f7f8;
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
#app { overflow: auto; height: 100vh; }
main.ts
import './assets/reset.less'
局部修改 app.vue
<div :theme-vars="themeVars"></div>
<script>
import { ref } from 'vue';
export default {
setup() {
const rate = ref(4);
const slider = ref(50);
// themeVars 内的值会被转换成对应 CSS 变量
// 比如 sliderBarHeight 会转换成 `--van-slider-bar-height`
const themeVars = {
rateIconFullColor: '#07c160',
sliderBarHeight: '4px',
sliderButtonWidth: '20px',
sliderButtonHeight: '20px',
sliderActiveBackgroundColor: '#07c160',
buttonPrimaryBorderColor: '#07c160',
buttonPrimaryBackgroundColor: '#07c160',
};
return {
rate,
slider,
themeVars,
};
},
};
</script>
封装底部导航
footer.vue:底部全局组件
<template>
<div class="footer">
<div class="f-box" ref="FIXED" :style="{ backgroundColor: bgColor }">
<slot />
</div>
<safe-inset-bottom />
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, getCurrentInstance, } from "vue";
export default defineComponent({
name: "MyFooter",
props: {
bgColor: { type: String, default: "#fff" },
},
setup() {
const { proxy }: any = getCurrentInstance();
const onStiatcHeight = () => {
const isVisible = proxy.$route.meta.isVisibleFooterTab;
const dom = proxy.$refs.FIXED;
const H = dom && isVisible ? dom.offsetHeight : 0;
proxy.$store.commit("SET_FOOTER_HEIGHT", `${H}px`);
};
onMounted(() => {
onStiatcHeight();
window.addEventListener("size", onStiatcHeight, false);
});
onUnmounted(() => {
onStiatcHeight();
window.removeEventListener("size", onStiatcHeight, false);
});
return {};
},
});
</script>
footer-nav.vue:部分底部全局组件使用 这个是导航
<template>
<transition name="fade">
<MyFooter v-if="$route.meta.isVisibleFooterTab">
<div class="ft-ul">
<div class="ft-li">首页</div>
<div class="ft-li">动态</div>
<div class="ft-li">我的</div>
</div>
</MyFooter>
</transition>
</template>
二次封装layout带下拉刷新
App.vue:整体的layout
<template>
<my-layout>
<router-view />
</my-layout>
<my-footer-nav />
</template>
新建文件refresh.ts
const content: any = [];
/*
下拉刷新 触发 所有注册的函数
@parmas callback // 关闭函数
num // 计算执行是否最后一个才执行callback
*/
export const trigger = (callback: any) => {
let num = content.length;
content.forEach((item: any) => item(() => {
num--;
num <= 0 && callback()
}))
}
// 收集所需的更新依赖
export const collect = (callback: any) => {
content.push(callback)
}
// 切换路由beforeEach 触发清空操作
export const clear = () => {
content.length = 0
}
layout.vue:封装下拉操作
<template>
<van-pull-refresh
class="refresh-layout"
:disabled="$route.meta.isDisabledPull"
v-model="loading"
@refresh="onRefresh"
>
<slot />
<div text="footer占位高度">
<div :style="{ height: $store.state.footerHeight }" />
<safe-inset-bottom />
</div>
</van-pull-refresh>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { PullRefresh } from "vant";
import { trigger } from "@/tool/refresh";
export default defineComponent({
name: "MyLayout",
components: { [PullRefresh.name]: PullRefresh },
setup() {
const loading = ref(false);
const onRefresh = () => {
trigger(() => {
setTimeout(() => {
loading.value = false;
}, 500);
})
};
return { loading, onRefresh };
},
});
</script>
<style>
.van-pull-refresh__track {
min-height: 100vh;
}
</style>
A.vue:内部组件控制下拉刷新 完成操作
<script setup lang="ts">
import { collect } from "@/tool/refresh";
collect((close: any) => {
const time = Math.random() * 3000
setTimeout(() => { // 接口异步操作后 关闭
close();
console.log("child close", time);
}, time);
});
</script>
二次封装向上滑动 加载更多
api.ts:模拟异步接口数据
// 模拟加载数据
export default function loadAPI(bodyparams: any) {
const { pageSize: SS, pageNo: NN } = bodyparams;
const total: number = 53;
return new Promise((resovle) => {
setTimeout(() => {
const result: any = [];
Array(SS)
.fill(1)
.forEach((u, x) => {
const id = SS * (NN - 1) + (x + 1);
id <= total &&
result.push({
id,
code: Math.random().toString(32).substr(2),
time: new Date().toString(),
});
});
resovle({
code: "000000",
message: "ok",
data: {
pageNo: NN,
list: result,
total,
},
});
}, Math.random() * 3000);
});
}
MyList.vue
<template>
<!-- 若 List 的内容使用了 float 布局,可以在容器上添加 van-clearfix 类名来清除浮动,使得 List 能正确判断元素位置。 -->
<!-- 如果在 html 和 body 标签上设置了 overflow-x: hidden 样式,会导致 List 一直触发加载。 -->
<van-list
v-model:loading="isloading"
:finished="isfinished"
:offset="100"
finished-text="没有更多了"
@load="onLoad"
>
<div class="van-clearfix">
<slot v-for="(u, x) in array" :key="rowKey ? u[rowKey] : x" :row="u" />
</div>
</van-list>
</template>
<script lang="ts">
import { computed, defineComponent, getCurrentInstance, ref } from "vue";
import { List } from "vant";
import loadAPI from "./api";
export default defineComponent({
name: "MyList",
components: { [List.name]: List },
props: {
action: String, // 接口api
rowKey: String, // 指定遍历key的字段
pageSize: { type: Number, default: 10 }, // 请求数量
modelValue: { type: Array, default: () => [] }, // v-model值
params: { type: Object, default: () => ({}) }, // 其它参数
},
emits: ["update:modelValue"], // v-model值update方法
setup(props, cxt) {
const array: any = computed({
set(value) {
cxt.emit("update:modelValue", value);
},
get() {
return props.modelValue;
},
});
let pageNo: number = 1;
const isloading = ref(false);
const isfinished = ref(false);
const onLoad = () => {
const data = {
pageNo,
pageSize: props.pageSize,
...props.params,
};
// this.$store.disatch(props.action, data).then((res: any) => {})
return loadAPI(data).then((res: any) => {
const { list = [], total } = res.data || {};
array.value = [...array.value, ...list];
pageNo++;
isloading.value = false;
isfinished.value = array.value.length >= total;
return res;
});
};
const onReload = () => {
pageNo = 1;
array.value = [];
isloading.value = true;
isfinished.value = false;
return onLoad();
};
return {
array,
isloading,
isfinished,
onLoad,
onReload,
};
},
});
</script>
A.vue:使用页面
<template>
<my-list ref="T" v-model="info" rowKey="code" action="xxxxx">
<template #default="{ row }">
<div>{{ row }}</div>
</template>
</my-list>
</template>
<script setup lang="ts">
import { ref, getCurrentInstance } from "vue";
import { collect } from "@/tool/refresh";
const { proxy }: any = getCurrentInstance();
const info = ref([{}]);
collect((close: any) => {
proxy.$refs.T.onReload().then((res: any) => {
console.log('rrrr', res)
close()
});
})
</script>