1、安装VantUI及项目中测试Vant组件

1.1 安装VantUI

官网地址: https://vant-contrib.gitee.io/vant/#/zh-CN/
项目目录下安装vant:

npm i vant@2

1.2 项目中使用Vant组件

在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>

1.3 优化Vant的在项目的目录使用

将来在页面中会用到很多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"

1.4 设置自动按需引入

项目目录下安装插件:

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';

重启服务器,查看页面样式效果

1.5 搜索框的引入使用

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>

2、样式配置调整

2.1 背景颜色设置

页面背景颜色为:#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>

2.2 rem单位换算

App.vue的样式中:

html{
  font-size: 100px;  
}

问:如果此时要设置字体为16px, 需要写成多少rem?
此时1个rem为100px,?rem/1rem = 16px/100px 需要写成 .16rem

2.3 样式清除工作

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"

2.4 解决html字体大小样式被覆盖问题

安装完reset-css后,html字体大小样式会被覆盖

p{
  width: 1rem;
  height: 1rem;
  background-color: red;
}

设置完上面样式后达不到想要的效果,原因是样式被覆盖reset-css源代码覆盖了
可通过!important提升html标签字体大小属性的权重至最高,达到不被任何代码覆盖的效果

html{
  font-size: 100px!important;  
}

3、数据请求

3.1 安装axios并发起请求

安装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);
    })
}

3.2 代理配置

vue.config.js 进行配置:

module.exports = {
    devServer: {
        port: 8080,
        proxy: {
            '/api': {
                target: "http://kumanxuan1.f3322.net:8001/",
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    }
}

由于配置文件修改了,这里一定要记得重新 npm run serve !!

3.3 API与Request封装

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')

3.4 发起请求

Home.vue 中:

import {GetHomeLists} from "@/request/api"

created() {
  GetHomeLists().then((res) => {
    if (res.status === 200) {
      console.log(res.data) // 成功拿到所有首页数据
    }
  })
}

3.5 请求实践-轮播图

组件文档地址: 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>

4、点击搜索弹出层结构

需求:点击首页搜索模块会从右侧弹出一个弹出层
由于在移动端开发中,在这个弹出层的时候按 “返回键” 可以直接回到首页,这意味着需要实现路由的跳转,而不是简单的盒子展示效果。

4.1 结构和路由设置

这就需要把这个弹出层当成一个页面来看,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组件中添加一个<router-view></router-view>组件。用于展示这个子组件。
最后点击search组件跳转

<van-search v-model="SearchVal" shape="round" placeholder="请输入搜索关键词" disabled @click="$router.push('/home/searchPopup')"/>

4.2 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>

4.3 滑动进场

transition的使用文档: https://cn.vuejs.org/guide/built-ins/transition.html#transition

官方原版:

在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
  2. v-enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
  3. v-enter-to:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
  4. v-leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
  5. v-leave-active:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
  6. v-leave-to:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。

在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>

4.4 离场动画

<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>

4.5 popup中的搜索框组件的展示

在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>

5、搜索栏下方三个区域的展示

5.1 历史记录和热门搜索展示

在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>

5.2 历史记录和热门搜索的数据渲染

由于前面我们已经对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>

5.3 搜索提示列表

我们需要一个变量来决定搜索框下方显示哪个组件(上面分析的三种情况)
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:2searchTipsArr: [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组件,否则报错

5.4 请求搜索提示列表数据

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
      })
    }
},

6、搜索产品展示区块

6.1 展示组件及下拉菜单

使用到的组件为: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>

6.2 添加产品组件和空组件

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>

6.3 产品组件样式布局

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)+" 元"
})

6.4 发送搜索请求

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);
    })
},

6.5 传递商品列表数据和分类数据 到对应组件中去使用

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"]

7、关于重复点击同一个路由出现的报错问题解决

在新版本的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 => {})
};
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐