官网地址: https://vant-contrib.gitee.io/vant/#/zh-CN/
项目目录下安装vant:
npm i vant@2
在views/Home.vue的script标签中:
import Vue from 'vue';
import { Button } from 'vant';
import 'vant/lib/button/style';
Vue.use(Button);
在views/Home.vue的template标签中:
<div class="home">
<van-button type="primary">主要按钮van-button>
div>
将来在页面中会用到很多vant组件,所以把引入工作单独抽离在src/vantui中的index.js中:
import Vue from 'vue';
import { Button } from 'vant';
import 'vant/lib/button/style';
Vue.use(Button);
在src/main.js中:
import "@/vantui"
项目目录下安装插件:
npm i babel-plugin-import
安装完成后,打开 babel.config.js 写入:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
}
删除vantui.index.js中的:
import 'vant/lib/button/style';
重启服务器,查看页面样式效果
search组件: https://vant-contrib.gitee.io/vant/#/zh-CN/search
vantui/index.js中
import { Button,Search } from 'vant';
Vue.use(Search);
views/Home.vue中
<template>
<div class="home">
<van-search v-model="searchVal" disable placeholder="请输入搜索关键词" />
div>
template>
<script>
export default {
name: 'Home',
data() {
return {
searchVal: ''
};
},
components: {
}
}
script>
页面背景颜色为:#efefef
App.vue中:
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100%;
background-color: #efefef;
}
html,body{
height: 100%;
}
style>
App.vue的样式中:
html{
font-size: 100px;
}
问:如果此时要设置字体为16px, 需要写成多少rem?
此时1个rem为100px,?rem/1rem = 16px/100px 需要写成 .16rem
app中添加字体大小样式:
#app {
...
font-size: .14rem;
}
安装模块 reset-css
npm i reset-css
main.js中添加引入
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'reset-css'
import "@/vantui"
安装完reset-css后,html字体大小样式会被覆盖
p{
width: 1rem;
height: 1rem;
background-color: red;
}
设置完上面样式后达不到想要的效果,原因是样式被覆盖reset-css源代码覆盖了
可通过!important提升html标签字体大小属性的权重至最高,达到不被任何代码覆盖的效果
html{
font-size: 100px!important;
}
安装axios
npm i axios
在Home.vue中书写axios代码:
created(){
axios.get("http://kumanxuan1.f3322.net:8001/index/index")
.then(res=>{
console.log(res.data);
})
.catch(err=>{
console.log(err);
})
}
vue.config.js 进行配置:
module.exports = {
devServer: {
port: 8080,
proxy: {
'/api': {
target: "http://kumanxuan1.f3322.net:8001/",
pathRewrite: {
'^/api': ''
}
}
}
}
}
由于配置文件修改了,这里一定要记得重新 npm run serve !!
在 src 下新建 request目录 ,在request目录下新建 request.js
request.js 中:
import axios from "axios"
const instance = axios.create({
baseURL:"http://kumanxuan1.f3322.net:8001/index/index",
timeout:5000
})
instance.interceptors.request.use(config=>{
console.log("每一次发起请求前,都会先执行这里的代码");
console.log(config); //config本次请求的配置信息
return config
},err=>{
return Promise.reject(err)
})
instance.interceptors.response.use(res=>{
console.log("每一次接收到响应,都会先执行这里的代码,再去执行成功的那个回调函数then");
return res
},err=>{
return Promise.reject(err)
})
export default instance
查看接口文档,首页接口地址为:preUrl+/index/index
为了更好地管理我们的这些接口,我们把所有请求都抽取出来在一个api.js中
在request目录下新建 api.js, api.js 中:
import request from './request'
// 请求首页的数据
export const GetHomeLists = () => request.get('/index/index')
Home.vue 中:
import {GetHomeLists} from "@/request/api"
created() {
GetHomeLists().then((res) => {
if (res.status === 200) {
console.log(res.data) // 成功拿到所有首页数据
}
})
}
组件文档地址: https://vant-contrib.gitee.io/vant/#/zh-CN/swipe
Home.vue中:
<template>
<div class="home">
<van-search v-model="searchVal" disable placeholder="请输入搜索关键词" />
<van-swipe class="my-swipe" :autoplay="3000" indicator-color="red">
<van-swipe-item v-for="item in banner" :key="item.id">
<img :src="item.image_url" alt="" width="100%">
van-swipe-item>
van-swipe>
div>
template>
<script>
import { GetHomeLists } from '@/request/api'
export default {
name: 'HomeView',
data() {
return {
searchVal: '',
banner: []
}
},
created() {
GetHomeLists().then((res) => {
const { banner } = res.data
this.banner = banner
console.log(this.banner)
}).catch(err => {
console.log('Error')
console.log(err)
})
}
}
script>
需求:点击首页搜索模块会从右侧弹出一个弹出层
由于在移动端开发中,在这个弹出层的时候按 “返回键” 可以直接回到首页,这意味着需要实现路由的跳转,而不是简单的盒子展示效果。
这就需要把这个弹出层当成一个页面来看,views中新建页面SearchPopup.vue
<template>
<div>弹出层div>
template>
<script>
export default {
data() {
return {}
}
}
script>
在首页中路由中开辟一个子路由:
{
path: '/home',
name: 'Home',
component: Home,
children:[
{
path: '/home/searchPopup',
name:'SearchPopup',
component:()=>import(/* webpackChunkName: "SearchPopup" */ '../views/SearchPopup.vue')
}
]
}
在Home组件中添加一个组件。用于展示这个子组件。
最后点击search组件跳转
<van-search v-model="SearchVal" shape="round" placeholder="请输入搜索关键词" disabled @click="$router.push('/home/searchPopup')"/>
SearchPopup.vue中:
<style lang="less" scoped>
.popup {
width: 100%;
height: 100%;
position: absolute;
right: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.5);
}
style>
transition的使用文档: https://cn.vuejs.org/guide/built-ins/transition.html#transition
官方原版:
在进入/离开的过渡中,会有 6 个 class 切换。
在Home.vue中添加:
<transition name="slide">
<router-view>router-view>
transition>
<style lang="less">
.slide-enter-from {
/*进场初始效果*/
right: -100%;
}
.slide-enter-active {
transition: all 0.2s;
}
.slide-enter-to {
/*进场最终效果*/
right: 0;
}
style>
<style lang="less">
.slide-enter-from,.slide-leave-to{
right:-100%;
}
.slide-enter-active,.slide-leave-active{
transition:all .2s linear;
}
.slide-enter-to,.slide-leave-from{
right:0;
}
style>
其实vant中直接提供了这个进场动画,所以也可以不需要我们去写这些类
文档: https://vant-contrib.gitee.io/vant/#/zh-CN/style
直接把类名换成:
<transition name="van-slide-right">
<router-view>router-view>
transition>
在SearchPopup.vue中
<template>
<div class="popup">
<van-search
v-model="searchVal"
show-action
:placeholder="placeholderVal"
@search="onSearch"
@cancel="onCancel"
/>
div>
template>
<script>
export default {
data() {
return {
searchVal: '',
placeholderVal: ''
}
},
methods: {
onSearch(val) {
console.log('按了回车')
},
onCancel() {
// 点击了取消
this.$router.go(-1)
}
}
}
script>
在components下新建模块HistoryHot.vue
<template>
<div class="history-hot">
<div class="his-hot">
<div class="hd">
<h4>历史记录h4>
<van-icon name="delete" />
div>
<div class="bd">
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
div>
div>
<div class="his-hot">
<div class="hd">
<h4>热门搜索h4>
div>
<div class="bd">
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
<van-tag plain type="default">标签van-tag>
div>
div>
div>
template>
<script>
export default {
data() {
return {}
}
}
script>
<style lang="less" scoped>
.his-hot {
margin-bottom: 0.2rem;
background-color: #fff;
padding: 2%;
.hd {
padding-top: 2%;
display: flex;
justify-content: space-between;
font-size: 0.22rem;
margin-bottom: 0.1rem;
h4 {
font-size: 0.2rem;
}
}
.van-tag {
padding: 0.04rem;
margin-right: 0.1rem;
}
}
style>
在SearchPopup.vue中使用上面HistoryHot组件
<template>
<div class="popup">
<van-search ... />
<HistoryHot />
div>
template>
<script>
import HistoryHot from "@/components/HistoryHot"
export default {
...
components:{
HistoryHot
}
}
script>
由于前面我们已经对axios请求进行封装,所以请求数据的三部曲如下:
1、在api.js中按需到出
2、在需要发送请求的组件中按需导入
3、发送请求
api.js中
// 历史记录和热门搜索数据的请求
export const GetPopupData = () => request.get('/search/index')
SearchPopup.vue中发送请求:
<template>
<div class="popup">
...
<HistoryHot
:searchHistoryData = "searchHistoryData"
:searchHotData="searchHotData"
/>
div>
template>
<script>
...
import {GetPopupData} from "@/request/api"
export default {
data () {
return {
...
placeholderVal:"",
searchHistoryData:"",
searchHotData:""
}
},
created() {
GetPopupData().then((res) => {
this.placeholderVal = res.data.defaultKeyword.keyword
this.searchHistoryData = res.data.historyKeywordList
this.searchHotData = res.data.hotKeywordList
})
},
...
}
script>
HistoryHot中:
<template>
<div class="history-hot">
<div class="his-hot">
...
<div class="bd">
<van-tag v-for="(item,index) in searchHistoryData" :key="index" plain type="default">{{item}}van-tag>
div>
div>
<div class="his-hot">
...
<div class="bd">
<van-tag v-for="(item,index) in searchHotData" :key="index" plain :type="item.is_hot?'danger':'default'">{{item.keyword}}van-tag>
div>
div>
div>
template>
<script>
export default {
...
props:["searchHistoryData", "searchHotData"]
}
script>
我们需要一个变量来决定搜索框下方显示哪个组件(上面分析的三种情况)
SearchPopup.vue中:
<HistoryHot
v-if="blockShow==1"
:searchHistoryData = "searchHistoryData"
:searchHotData="searchHotData"
/>
<SearchTipsList
v-else-if="blockShow==2"
:searchTipsArr="searchTipsArr"
/>
...
<script>
import SearchTipsList from "@/components/SearchTipsList"
data () {
return {
...
/*
blockShow决定区块展示,
如果是1,展示历史记录和热门搜索 HistoryHot
如果是2,展示搜索提示列表 SearchTipsList
如果是3,展示搜索出来内容
*/
blockShow:2,
searchTipsArr: [1,2,3,4,5], // 请求数组从父组件传到子组件
}
},
components:{
HistoryHot,
SearchTipsList
}
script>
components中新建SearchTipsList.vue组件:
<template>
<div>
<van-list v-model="loading" :finished="finished" finished-text="没有更多了">
<van-cell v-for="item in searchTipsArr" :key="item" :title="item" />
van-list>
div>
template>
<script>
export default {
data() {
return {
loading: false,
finished: true
}
},
props: ['searchTipsArr']
}
script>
注意:这里还需要在vantui/index.js中多注册一个Cell组件,否则报错
1、在api.js中按需到出
2、在需要发送请求的组件中按需导入
3、发送请求
api.js中: (注意,这里需要设置传参)
export const GetSearchTipsListData = (params) => request.get("/search/helper",{params});
SearchPopup.vue中:
<van-search
...
@input="onInput" // 这里添加input事件
/>
import {GetPopupData, GetSearchTipsListData} from "@/request/api"
methods: {
...
onInput(val) {
this.blockShow = 2
// 这个val就是用户输入的文字
GetSearchTipsListData({ keyword: val }).then((res) => {
console.log(res.data)
this.searchTipsArr = res.data
})
}
},
使用到的组件为:DropdownMenu 下拉菜单 https://vant-contrib.gitee.io/vant/#/zh-CN/dropdown-menu
components中新建SearchProducts.vue组件:
<template>
<div>
<van-dropdown-menu>
<van-dropdown-item title="综合" disabled v-model="value1" :options="option1" />
<van-dropdown-item title="价格" v-model="value2" :options="option2" />
<van-dropdown-item title="分类" v-model="value2" :options="option2" />
van-dropdown-menu>
div>
template>
<script>
export default {
data () {
return {
value1: 0,
value2: 'a',
option1: [
{ text: '全部商品', value: 0 },
{ text: '新款商品', value: 1 },
{ text: '活动商品', value: 2 },
],
option2: [
{ text: '默认排序', value: 'a' },
{ text: '好评排序', value: 'b' },
{ text: '销量排序', value: 'c' },
],
}
}
}
script>
SearchPopup.vue中:
<template>
<div class="popup">
...
<SearchTipsList
v-else-if="blockShow==2"
:searchTipsArr="searchTipsArr"
/>
<SearchProducts v-else/>
div>
template>
<script>
import SearchProducts from "@/components/SearchProducts"
...
...
components:{
HistoryHot,
SearchTipsList,
SearchProducts
}
}
script>
components中添加产品组件 Products.vue 组件, 并在SearchProducts.vue组件中引入和使用。
配合vant中的Empty组件
SearchProducts.vue中
<template>
<div>
...
<van-empty v-if="isEmpty" image="search" description="抱歉,搜索不到产品" />
<Products />
div>
template>
<script>
import Products from "./Products"
export default {
data () {
return {
...
isEmpty:false
}
},
components:{
Products
}
}
script>
Products.vue中:
<template>
<ul>
<li >
<img :src="imgSrc" style="display:block;" width="100%" alt="" />
<div class="van-ellipsis">产品名称div>
<div class="price">{{99 | RMBformat}}div>
li>
ul>
template>
<script>
export default {
data () {
return {
imgSrc:require("@/assets/logo.png")
}
}
}
script>
<style lang = "less" scoped>
ul{
padding: .1rem 2%;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
li{
width: 49%;
margin-bottom: .1rem;
background: #fff;
text-align: center;
line-height: .3rem;
.price{
color: darkred;
}
}
}
style>
main.js中添加全局过滤器:
Vue.filter("RMBformat",val=>{
return "¥ "+val.toFixed(2)+" 元"
})
api.js中
// 获取搜索产品内容
export const GetSearchData = (params) => request.get("/goods/list",{params});
Searchpopup.vue中
import {GetPopupData, GetSearchTipsListData, GetSearchData} from "@/request/api"
onSearch(val) {
GetSearchData().then(res=>{
if(res.errno===0){
this.blockShow=3;
console.log(res.data);
}
}).catch(err=>{
console.log(err);
})
},
Searchpopup.vue中
<SearchProducts v-else
:goodList="goodList"
:filterCategory="filterCategory"
/>
onSearch(val) {
GetSearchData().then(res=>{
if(res.errno==0){
this.goodsList = res.data.goodsList
this.filterCategory = res.data.filterCategory
}
}).catch(err=>{
console.log(err);
})
},
SearchProducts.vue中接收:
<Products :goodsList="goodsList"/>
props:["goodsList", "filterCategory"]
Products.vue中接收:
props:["goodsList"]
在新版本的vue-router中,重复点击同一个路由会出现以下报错:
解决方案如下:
方案1、直接在push方法最后添加异常捕获,例如:
<van-search v-model="SearchVal" shape="round" placeholder="请输入搜索关键词" disabled @click="$router.push('/home/searchPopup').catch(err=>{})"/>
方案2、直接修改原型方法push(推荐)
// 把这段代码直接粘贴到router/index.js中的Vue.use(VueRouter)之前
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function(location) {
return originalPush.call(this, location).catch(err => {})
};